fix: resolve typescript lint errors and test failures

- Remove unnecessary `any` casts and unsafe type assertions in `useAgentStream.ts`.
- Introduce `MinimalTrackedToolCall` to safely type mock tool calls for inactivity monitors.
- Fix arrow-body-style lint errors in `AppContainer.tsx` and `useAgentStream.ts`.
- Update `nonInteractiveCli.test.ts` to include a required `build` method in mock tools to prevent TypeErrors during stream initialization.
- Remove redundant non-null assertion in `legacy-agent-session.ts`.
This commit is contained in:
Michael Bleigh
2026-03-27 13:59:35 -07:00
parent fd321abd3d
commit ecc9e50a1f
9 changed files with 243 additions and 393 deletions
@@ -40,6 +40,11 @@ function createMockDeps(
const mockConfig = {
getMaxSessionTurns: vi.fn().mockReturnValue(-1),
getModel: vi.fn().mockReturnValue('gemini-2.5-pro'),
getGeminiClient: vi.fn().mockReturnValue(mockClient),
getMessageBus: vi.fn().mockImplementation(() => ({
subscribe: vi.fn(),
unsubscribe: vi.fn(),
})),
};
return {
@@ -138,7 +143,7 @@ describe('LegacyAgentSession', () => {
describe('send', () => {
it('returns streamId', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -158,7 +163,7 @@ describe('LegacyAgentSession', () => {
});
it('records the sent user message in the trajectory before send resolves', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -235,7 +240,7 @@ describe('LegacyAgentSession', () => {
});
it('returns streamId before emitting agent_start', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -275,7 +280,7 @@ describe('LegacyAgentSession', () => {
it('throws if send is called while a stream is active', async () => {
let resolveHang: (() => void) | undefined;
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -303,7 +308,7 @@ describe('LegacyAgentSession', () => {
});
it('creates a new streamId after the previous stream completes', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock
@@ -365,7 +370,7 @@ describe('LegacyAgentSession', () => {
describe('stream - basic flow', () => {
it('emits agent_start, content messages, and agent_end', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -404,7 +409,7 @@ describe('LegacyAgentSession', () => {
describe('stream - tool calls', () => {
it('handles a tool call round-trip', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
// First turn: model requests a tool
@@ -431,7 +436,7 @@ describe('LegacyAgentSession', () => {
]),
);
const scheduleMock = deps.scheduler.schedule as ReturnType<typeof vi.fn>;
const scheduleMock = deps.scheduler!.schedule as ReturnType<typeof vi.fn>;
scheduleMock.mockResolvedValueOnce([
makeCompletedToolCall('call-1', 'read_file', 'file contents'),
]);
@@ -464,7 +469,7 @@ describe('LegacyAgentSession', () => {
});
it('handles tool errors and sends error message in content', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValueOnce(
@@ -501,7 +506,7 @@ describe('LegacyAgentSession', () => {
},
} as CompletedToolCall;
const scheduleMock = deps.scheduler.schedule as ReturnType<typeof vi.fn>;
const scheduleMock = deps.scheduler!.schedule as ReturnType<typeof vi.fn>;
scheduleMock.mockResolvedValueOnce([errorToolCall]);
const session = new LegacyAgentSession(deps);
@@ -522,7 +527,7 @@ describe('LegacyAgentSession', () => {
});
it('stops on STOP_EXECUTION tool error', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValueOnce(
@@ -550,7 +555,7 @@ describe('LegacyAgentSession', () => {
},
} as CompletedToolCall;
const scheduleMock = deps.scheduler.schedule as ReturnType<typeof vi.fn>;
const scheduleMock = deps.scheduler!.schedule as ReturnType<typeof vi.fn>;
scheduleMock.mockResolvedValueOnce([stopToolCall]);
const session = new LegacyAgentSession(deps);
@@ -566,7 +571,7 @@ describe('LegacyAgentSession', () => {
});
it('treats fatal tool errors as tool_response followed by agent_end failed', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValueOnce(
@@ -594,7 +599,7 @@ describe('LegacyAgentSession', () => {
},
} as CompletedToolCall;
const scheduleMock = deps.scheduler.schedule as ReturnType<typeof vi.fn>;
const scheduleMock = deps.scheduler!.schedule as ReturnType<typeof vi.fn>;
scheduleMock.mockResolvedValueOnce([fatalToolCall]);
const session = new LegacyAgentSession(deps);
@@ -623,7 +628,7 @@ describe('LegacyAgentSession', () => {
describe('stream - terminal events', () => {
it('handles AgentExecutionStopped', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -647,7 +652,7 @@ describe('LegacyAgentSession', () => {
});
it('handles AgentExecutionBlocked as non-terminal and continues the stream', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -694,7 +699,7 @@ describe('LegacyAgentSession', () => {
});
it('handles Error events', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -718,7 +723,7 @@ describe('LegacyAgentSession', () => {
});
it('handles LoopDetected as non-terminal warning event', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
// LoopDetected followed by more content — stream continues
@@ -772,7 +777,7 @@ describe('LegacyAgentSession', () => {
>;
configMock.mockReturnValue(0);
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -798,7 +803,7 @@ describe('LegacyAgentSession', () => {
});
it('treats GeminiClient MaxSessionTurns as a terminal max_turns stream end', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -827,7 +832,7 @@ describe('LegacyAgentSession', () => {
describe('abort', () => {
it('treats abort before the first model event as aborted without fatal error', async () => {
let releaseAbort: (() => void) | undefined;
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -866,7 +871,7 @@ describe('LegacyAgentSession', () => {
});
it('aborts the stream', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
// Stream that yields content then checks abort signal via a deferred
@@ -909,7 +914,7 @@ describe('LegacyAgentSession', () => {
it('treats abort during pending scheduler work as aborted without fatal error', async () => {
let resolveSchedule: ((value: CompletedToolCall[]) => void) | undefined;
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -925,7 +930,7 @@ describe('LegacyAgentSession', () => {
]),
);
const scheduleMock = deps.scheduler.schedule as ReturnType<typeof vi.fn>;
const scheduleMock = deps.scheduler!.schedule as ReturnType<typeof vi.fn>;
scheduleMock.mockReturnValue(
new Promise<CompletedToolCall[]>((resolve) => {
resolveSchedule = resolve;
@@ -961,7 +966,7 @@ describe('LegacyAgentSession', () => {
describe('events property', () => {
it('accumulates all events', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -985,7 +990,7 @@ describe('LegacyAgentSession', () => {
describe('subscription and stream scoping', () => {
it('subscribe receives live events for the next stream', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -1016,7 +1021,7 @@ describe('LegacyAgentSession', () => {
});
it('subscribe is live-only and does not replay old history when idle', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock
@@ -1068,7 +1073,7 @@ describe('LegacyAgentSession', () => {
});
it('streams only the requested streamId', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock
@@ -1126,7 +1131,7 @@ describe('LegacyAgentSession', () => {
});
it('resumes from eventId within the same stream only', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock
@@ -1187,7 +1192,7 @@ describe('LegacyAgentSession', () => {
describe('agent_end ordering', () => {
it('agent_end is always the final event yielded', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -1209,7 +1214,7 @@ describe('LegacyAgentSession', () => {
});
it('agent_end is final even after error events', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValue(
@@ -1231,7 +1236,7 @@ describe('LegacyAgentSession', () => {
describe('intermediate Finished events', () => {
it('does NOT emit agent_end when tool calls are pending', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
// First turn: tool request + Finished (should NOT produce agent_end)
@@ -1264,7 +1269,7 @@ describe('LegacyAgentSession', () => {
]),
);
const scheduleMock = deps.scheduler.schedule as ReturnType<typeof vi.fn>;
const scheduleMock = deps.scheduler!.schedule as ReturnType<typeof vi.fn>;
scheduleMock.mockResolvedValueOnce([
makeCompletedToolCall('call-1', 'read_file', 'data'),
]);
@@ -1280,7 +1285,7 @@ describe('LegacyAgentSession', () => {
});
it('emits usage for intermediate Finished events', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockReturnValueOnce(
@@ -1311,7 +1316,7 @@ describe('LegacyAgentSession', () => {
]),
);
const scheduleMock = deps.scheduler.schedule as ReturnType<typeof vi.fn>;
const scheduleMock = deps.scheduler!.schedule as ReturnType<typeof vi.fn>;
scheduleMock.mockResolvedValueOnce([
makeCompletedToolCall('call-1', 'read_file', 'contents'),
]);
@@ -1332,7 +1337,7 @@ describe('LegacyAgentSession', () => {
describe('error handling in runLoop', () => {
it('catches thrown errors and emits error + agent_end', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
sendMock.mockImplementation(() => {
@@ -1358,7 +1363,7 @@ describe('LegacyAgentSession', () => {
describe('_emitErrorAndAgentEnd metadata', () => {
it('preserves exitCode and code in _meta for FatalError', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
// Simulate a FatalError being thrown
@@ -1381,7 +1386,7 @@ describe('LegacyAgentSession', () => {
});
it('preserves exitCode for non-FatalError errors that carry one', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
const exitCodeError = new Error('custom exit');
@@ -1401,7 +1406,7 @@ describe('LegacyAgentSession', () => {
});
it('preserves code in _meta for errors with code property', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
const codedError = new Error('ENOENT');
@@ -1421,7 +1426,7 @@ describe('LegacyAgentSession', () => {
});
it('preserves status in _meta for errors with status property', async () => {
const sendMock = deps.client.sendMessageStream as ReturnType<
const sendMock = deps.client!.sendMessageStream as ReturnType<
typeof vi.fn
>;
const statusError = new Error('rate limited');
@@ -14,7 +14,7 @@ import type { Part } from '@google/genai';
import type { GeminiClient } from '../core/client.js';
import type { Config } from '../config/config.js';
import type { ToolCallRequestInfo } from '../scheduler/types.js';
import type { Scheduler } from '../scheduler/scheduler.js';
import { Scheduler } from '../scheduler/scheduler.js';
import { recordToolCallInteractions } from '../code_assist/telemetry.js';
import { ToolErrorType, isFatalToolError } from '../tools/tool-error.js';
import { debugLogger } from '../utils/debugLogger.js';
@@ -46,15 +46,18 @@ function isAbortLikeError(err: unknown): boolean {
return err instanceof Error && err.name === 'AbortError';
}
import type { EditorType } from '../utils/editor.js';
export interface LegacyAgentSessionDeps {
client: GeminiClient;
scheduler: Scheduler;
config: Config;
promptId: string;
client?: GeminiClient;
scheduler?: Scheduler;
promptId?: string;
streamId?: string;
getPreferredEditor?: () => EditorType | undefined;
}
class LegacyAgentProtocol implements AgentProtocol {
export class LegacyAgentProtocol implements AgentProtocol {
private _events: AgentEvent[] = [];
private _subscribers = new Set<(event: AgentEvent) => void>();
private _translationState: TranslationState;
@@ -71,10 +74,16 @@ class LegacyAgentProtocol implements AgentProtocol {
constructor(deps: LegacyAgentSessionDeps) {
this._translationState = createTranslationState(deps.streamId);
this._nextStreamIdOverride = deps.streamId;
this._client = deps.client;
this._scheduler = deps.scheduler;
this._config = deps.config;
this._promptId = deps.promptId;
this._client = deps.client ?? deps.config.getGeminiClient();
this._promptId = deps.promptId ?? deps.config.promptId ?? '';
this._scheduler =
deps.scheduler ??
new Scheduler({
context: deps.config,
schedulerId: 'legacy-agent-scheduler',
getPreferredEditor: deps.getPreferredEditor ?? (() => undefined),
});
}
get events(): readonly AgentEvent[] {
+1 -1
View File
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type { Kind } from 'src/tools/tools.js';
import type { Kind } from '../tools/tools.js';
export type WithMeta = { _meta?: Record<string, unknown> };