Disallow redundant typecasts. (#15030)

This commit is contained in:
Christian Gunderman
2025-12-12 17:43:43 -08:00
committed by GitHub
parent fcc3b2b5ec
commit 942bcfc61e
86 changed files with 235 additions and 371 deletions
+3 -3
View File
@@ -317,7 +317,7 @@ describe('AgentExecutor', () => {
onActivity,
);
const agentRegistry = executor['toolRegistry'] as ToolRegistry;
const agentRegistry = executor['toolRegistry'];
expect(agentRegistry).not.toBe(parentToolRegistry);
expect(agentRegistry.getAllToolNames()).toEqual(
@@ -754,7 +754,7 @@ describe('AgentExecutor', () => {
expect(turn2Parts).toBeDefined();
expect(turn2Parts).toHaveLength(1);
expect((turn2Parts as Part[])![0]).toEqual(
expect((turn2Parts as Part[])[0]).toEqual(
expect.objectContaining({
functionResponse: expect.objectContaining({
name: TASK_COMPLETE_TOOL_NAME,
@@ -944,7 +944,7 @@ describe('AgentExecutor', () => {
const turn2Params = getMockMessageParams(1);
const parts = turn2Params.message;
expect(parts).toBeDefined();
expect((parts as Part[])![0]).toEqual(
expect((parts as Part[])[0]).toEqual(
expect.objectContaining({
functionResponse: expect.objectContaining({
id: badCallId,
+3 -3
View File
@@ -707,7 +707,7 @@ export class AgentExecutor<TOutput extends z.ZodTypeAny> {
for (const [index, functionCall] of functionCalls.entries()) {
const callId = functionCall.id ?? `${promptId}-${index}`;
const args = (functionCall.args ?? {}) as Record<string, unknown>;
const args = functionCall.args ?? {};
this.emitActivity('TOOL_CALL_START', {
name: functionCall.name,
@@ -918,10 +918,10 @@ export class AgentExecutor<TOutput extends z.ZodTypeAny> {
toolNamesToLoad.push(toolRef);
} else if (typeof toolRef === 'object' && 'schema' in toolRef) {
// Tool instance with an explicit schema property.
toolsList.push(toolRef.schema as FunctionDeclaration);
toolsList.push(toolRef.schema);
} else {
// Raw `FunctionDeclaration` object.
toolsList.push(toolRef as FunctionDeclaration);
toolsList.push(toolRef);
}
}
// Add schemas from tools that were registered by name.
@@ -43,9 +43,9 @@ describe('policyCatalog', () => {
it('clones policy maps so edits do not leak between calls', () => {
const firstCall = getModelPolicyChain({ previewEnabled: false });
firstCall[0]!.actions.terminal = 'silent';
firstCall[0].actions.terminal = 'silent';
const secondCall = getModelPolicyChain({ previewEnabled: false });
expect(secondCall[0]!.actions.terminal).toBe('prompt');
expect(secondCall[0].actions.terminal).toBe('prompt');
});
it('passes when there is exactly one last-resort policy', () => {
+10 -10
View File
@@ -827,15 +827,15 @@ describe('Server Config (config.ts)', () => {
).ToolRegistry.prototype.registerTool;
// Check that registerTool was called for ShellTool
const wasShellToolRegistered = (registerToolMock as Mock).mock.calls.some(
const wasShellToolRegistered = registerToolMock.mock.calls.some(
(call) => call[0] instanceof vi.mocked(ShellTool),
);
expect(wasShellToolRegistered).toBe(true);
// Check that registerTool was NOT called for ReadFileTool
const wasReadFileToolRegistered = (
registerToolMock as Mock
).mock.calls.some((call) => call[0] instanceof vi.mocked(ReadFileTool));
const wasReadFileToolRegistered = registerToolMock.mock.calls.some(
(call) => call[0] instanceof vi.mocked(ReadFileTool),
);
expect(wasReadFileToolRegistered).toBe(false);
});
@@ -948,9 +948,9 @@ describe('Server Config (config.ts)', () => {
}
).ToolRegistry.prototype.registerTool;
const wasShellToolRegistered = (
registerToolMock as Mock
).mock.calls.some((call) => call[0] instanceof vi.mocked(ShellTool));
const wasShellToolRegistered = registerToolMock.mock.calls.some(
(call) => call[0] instanceof vi.mocked(ShellTool),
);
expect(wasShellToolRegistered).toBe(true);
});
@@ -968,9 +968,9 @@ describe('Server Config (config.ts)', () => {
}
).ToolRegistry.prototype.registerTool;
const wasShellToolRegistered = (
registerToolMock as Mock
).mock.calls.some((call) => call[0] instanceof vi.mocked(ShellTool));
const wasShellToolRegistered = registerToolMock.mock.calls.some(
(call) => call[0] instanceof vi.mocked(ShellTool),
);
expect(wasShellToolRegistered).toBe(true);
});
});
@@ -11,7 +11,6 @@ import { PolicyDecision, getHookSource } from '../policy/types.js';
import {
MessageBusType,
type Message,
type HookExecutionRequest,
type HookPolicyDecision,
} from './types.js';
import { safeJsonStringify } from '../utils/safeJsonStringify.js';
@@ -91,7 +90,7 @@ export class MessageBus extends EventEmitter {
}
} else if (message.type === MessageBusType.HOOK_EXECUTION_REQUEST) {
// Handle hook execution requests through policy evaluation
const hookRequest = message as HookExecutionRequest;
const hookRequest = message;
const decision = await this.policyEngine.checkHook(hookRequest);
// Map decision to allow/deny for observability (ASK_USER treated as deny for hooks)
+2 -2
View File
@@ -299,7 +299,7 @@ describe('BaseLlmClient', () => {
// Validate the telemetry event content - find the most recent call
const calls = vi.mocked(logMalformedJsonResponse).mock.calls;
const lastCall = calls[calls.length - 1];
const event = lastCall[1] as MalformedJsonResponseEvent;
const event = lastCall[1];
expect(event.model).toBe(defaultOptions.modelConfigKey.model);
});
@@ -347,7 +347,7 @@ describe('BaseLlmClient', () => {
expect(logMalformedJsonResponse).toHaveBeenCalled();
const calls = vi.mocked(logMalformedJsonResponse).mock.calls;
const lastCall = calls[calls.length - 1];
const event = lastCall[1] as MalformedJsonResponseEvent;
const event = lastCall[1];
// This is the key assertion: it should be the resolved model, not the alias
expect(event.model).toBe(resolvedModel);
@@ -149,10 +149,7 @@ describe('createContentGenerator', () => {
},
});
expect(generator).toEqual(
new LoggingContentGenerator(
(mockGenerator as GoogleGenAI).models,
mockConfig,
),
new LoggingContentGenerator(mockGenerator.models, mockConfig),
);
});
@@ -342,10 +339,7 @@ describe('createContentGenerator', () => {
},
});
expect(generator).toEqual(
new LoggingContentGenerator(
(mockGenerator as GoogleGenAI).models,
mockConfig,
),
new LoggingContentGenerator(mockGenerator.models, mockConfig),
);
});
});
@@ -1531,7 +1531,7 @@ describe('CoreToolScheduler request queueing', () => {
// Capture confirmation handlers for awaiting_approval tools
toolCalls.forEach((call) => {
if (call.status === 'awaiting_approval') {
const waitingCall = call as WaitingToolCall;
const waitingCall = call;
if (waitingCall.confirmationDetails?.onConfirm) {
const originalHandler = pendingConfirmations.find(
(h) => h === waitingCall.confirmationDetails.onConfirm,
+1 -1
View File
@@ -506,7 +506,7 @@ export class CoreToolScheduler {
// Preserve diff for cancelled edit operations
let resultDisplay: ToolResultDisplay | undefined = undefined;
if (currentCall.status === 'awaiting_approval') {
const waitingCall = currentCall as WaitingToolCall;
const waitingCall = currentCall;
if (waitingCall.confirmationDetails.type === 'edit') {
resultDisplay = {
fileDiff: waitingCall.confirmationDetails.fileDiff,
+17 -17
View File
@@ -270,9 +270,9 @@ describe('GeminiChat', () => {
// 3. Verify history was recorded correctly
const history = chat.getHistory();
expect(history.length).toBe(2); // user turn + model turn
const modelTurn = history[1]!;
const modelTurn = history[1];
expect(modelTurn?.parts?.length).toBe(1); // The empty part is discarded
expect(modelTurn?.parts![0]!.functionCall).toBeDefined();
expect(modelTurn?.parts![0].functionCall).toBeDefined();
});
it('should fail if the stream ends with an empty part and has no finishReason', async () => {
@@ -370,9 +370,9 @@ describe('GeminiChat', () => {
// 3. Verify history was recorded correctly with only the valid part.
const history = chat.getHistory();
expect(history.length).toBe(2); // user turn + model turn
const modelTurn = history[1]!;
const modelTurn = history[1];
expect(modelTurn?.parts?.length).toBe(1);
expect(modelTurn?.parts![0]!.text).toBe('Initial valid content...');
expect(modelTurn?.parts![0].text).toBe('Initial valid content...');
});
it('should consolidate subsequent text chunks after receiving an empty text chunk', async () => {
@@ -414,9 +414,9 @@ describe('GeminiChat', () => {
// 3. Assert: Check that the final history was correctly consolidated.
const history = chat.getHistory();
expect(history.length).toBe(2);
const modelTurn = history[1]!;
const modelTurn = history[1];
expect(modelTurn?.parts?.length).toBe(1);
expect(modelTurn?.parts![0]!.text).toBe('Hello World!');
expect(modelTurn?.parts![0].text).toBe('Hello World!');
});
it('should consolidate adjacent text parts that arrive in separate stream chunks', async () => {
@@ -476,14 +476,14 @@ describe('GeminiChat', () => {
// The history should contain the user's turn and ONE consolidated model turn.
expect(history.length).toBe(2);
const modelTurn = history[1]!;
const modelTurn = history[1];
expect(modelTurn.role).toBe('model');
// The model turn should have 3 distinct parts: the merged text, the function call, and the final text.
expect(modelTurn?.parts?.length).toBe(3);
expect(modelTurn?.parts![0]!.text).toBe('This is the first part.');
expect(modelTurn.parts![1]!.functionCall).toBeDefined();
expect(modelTurn.parts![2]!.text).toBe('This is the second part.');
expect(modelTurn?.parts![0].text).toBe('This is the first part.');
expect(modelTurn.parts![1].functionCall).toBeDefined();
expect(modelTurn.parts![2].text).toBe('This is the second part.');
});
it('should preserve text parts that stream in the same chunk as a thought', async () => {
// 1. Mock the API to return a single chunk containing both a thought and visible text.
@@ -525,14 +525,14 @@ describe('GeminiChat', () => {
// The history should contain two turns: the user's message and the model's response.
expect(history.length).toBe(2);
const modelTurn = history[1]!;
const modelTurn = history[1];
expect(modelTurn.role).toBe('model');
// CRUCIAL ASSERTION:
// The buggy code would fail here, resulting in parts.length being 0.
// The corrected code will pass, preserving the single visible text part.
expect(modelTurn?.parts?.length).toBe(1);
expect(modelTurn?.parts![0]!.text).toBe(
expect(modelTurn?.parts![0].text).toBe(
'This is the visible text that should not be lost.',
);
});
@@ -1979,8 +1979,8 @@ describe('GeminiChat', () => {
);
const history = chat.getHistory();
const modelTurn = history[1]!;
expect(modelTurn.parts![0]!.text).toBe('Success on retry');
const modelTurn = history[1];
expect(modelTurn.parts![0].text).toBe('Success on retry');
});
it('should switch to DEFAULT_GEMINI_FLASH_MODEL and use thinkingBudget when falling back from a gemini-3 model', async () => {
@@ -2154,11 +2154,11 @@ describe('GeminiChat', () => {
const history = chat.getHistory();
expect(history.length).toBe(2); // user turn + final model turn
const modelTurn = history[1]!;
const modelTurn = history[1];
// The model turn should only contain the text from the successful attempt
expect(modelTurn!.parts![0]!.text).toBe('Successful final response');
expect(modelTurn.parts![0].text).toBe('Successful final response');
// It should NOT contain any text from the failed attempt
expect(modelTurn!.parts![0]!.text).not.toContain(
expect(modelTurn.parts![0].text).not.toContain(
'This valid part should be discarded',
);
});
+4 -10
View File
@@ -357,9 +357,7 @@ export class GeminiChat {
// Check if we have more attempts left.
if (attempt < maxAttempts - 1) {
const delayMs = INVALID_CONTENT_RETRY_OPTIONS.initialDelayMs;
const retryType = isContentError
? (error as InvalidStreamError).type
: 'NETWORK_ERROR';
const retryType = isContentError ? error.type : 'NETWORK_ERROR';
logContentRetry(
this.config,
@@ -382,11 +380,7 @@ export class GeminiChat {
) {
logContentRetryFailure(
this.config,
new ContentRetryFailureEvent(
maxAttempts,
(lastError as InvalidStreamError).type,
model,
),
new ContentRetryFailureEvent(maxAttempts, lastError.type, model),
);
}
throw lastError;
@@ -712,7 +706,7 @@ export class GeminiChat {
if (content.role === 'model' && content.parts) {
const newParts = content.parts.slice();
for (let j = 0; j < newParts.length; j++) {
const part = newParts[j]!;
const part = newParts[j];
if (part.functionCall) {
if (!part.thoughtSignature) {
newParts[j] = {
@@ -913,7 +907,7 @@ export class GeminiChat {
name: call.request.name,
args: call.request.args,
result: call.response?.responseParts || null,
status: call.status as 'error' | 'success' | 'cancelled',
status: call.status,
timestamp: new Date().toISOString(),
resultDisplay,
};
+1 -1
View File
@@ -408,7 +408,7 @@ export class Logger {
}
// 2. Attempt to delete the old raw path for backward compatibility.
const oldPath = path.join(this.geminiDir!, `checkpoint-${tag}.json`);
const oldPath = path.join(this.geminiDir, `checkpoint-${tag}.json`);
if (newPath !== oldPath) {
try {
await fs.unlink(oldPath);
+2 -2
View File
@@ -264,7 +264,7 @@ export class Turn {
}
// Assuming other events are chunks with a `value` property
const resp = streamEvent.value as GenerateContentResponse;
const resp = streamEvent.value;
if (!resp) continue; // Skip if there's no response body
this.debugResponses.push(resp);
@@ -374,7 +374,7 @@ export class Turn {
fnCall.id ??
`${fnCall.name}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
const name = fnCall.name || 'undefined_tool_name';
const args = (fnCall.args || {}) as Record<string, unknown>;
const args = fnCall.args || {};
const toolCallRequest: ToolCallRequestInfo = {
callId,
+1 -1
View File
@@ -137,7 +137,7 @@ export class HookRegistry {
continue;
}
const typedEventName = eventName as HookEventName;
const typedEventName = eventName;
if (!Array.isArray(definitions)) {
debugLogger.warn(
+2 -2
View File
@@ -21,9 +21,9 @@ type MockChildProcessWithoutNullStreams = ChildProcessWithoutNullStreams & {
// Mock child_process with importOriginal for partial mocking
vi.mock('node:child_process', async (importOriginal) => {
const actual = (await importOriginal()) as object;
const actual = await importOriginal();
return {
...actual,
...(actual as object),
spawn: vi.fn(),
};
});
+2 -2
View File
@@ -21,9 +21,9 @@ type MockChildProcessWithoutNullStreams = ChildProcessWithoutNullStreams & {
// Mock child_process with importOriginal for partial mocking
vi.mock('node:child_process', async (importOriginal) => {
const actual = (await importOriginal()) as object;
const actual = await importOriginal();
return {
...actual,
...(actual as object),
spawn: vi.fn(),
};
});
+2 -3
View File
@@ -7,10 +7,9 @@
import { vi } from 'vitest';
vi.mock('node:child_process', async (importOriginal) => {
const actual =
(await importOriginal()) as typeof import('node:child_process');
const actual = await importOriginal();
return {
...actual,
...(actual as object),
execSync: vi.fn(),
spawnSync: vi.fn(() => ({ status: 0 })),
};
+2 -2
View File
@@ -302,8 +302,8 @@ export class MCPOAuthProvider {
<html>
<body>
<h1>Authentication Failed</h1>
<p>Error: ${(error as string).replace(/</g, '&lt;').replace(/>/g, '&gt;')}</p>
<p>${((url.searchParams.get('error_description') || '') as string).replace(/</g, '&lt;').replace(/>/g, '&gt;')}</p>
<p>Error: ${error.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</p>
<p>${(url.searchParams.get('error_description') || '').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</p>
<p>You can close this window.</p>
</body>
</html>
@@ -519,7 +519,7 @@ export class LoopDetectionService {
signal: AbortSignal,
): Promise<Record<string, unknown> | null> {
try {
const result = (await this.config.getBaseLlmClient().generateJson({
const result = await this.config.getBaseLlmClient().generateJson({
modelConfigKey: { model },
contents,
schema: LOOP_DETECTION_SCHEMA,
@@ -527,7 +527,7 @@ export class LoopDetectionService {
abortSignal: signal,
promptId: this.promptId,
maxAttempts: 2,
})) as Record<string, unknown>;
});
if (
result &&
@@ -258,10 +258,7 @@ export class ModelConfigService {
// TODO(joshualitt): Consider knobs here, i.e. opt-in to deep merging
// arrays on a case-by-case basis.
if (this.isObject(accValue) && this.isObject(objValue)) {
acc[key] = this.deepMerge(
accValue as Record<string, unknown>,
objValue as Record<string, unknown>,
);
acc[key] = this.deepMerge(accValue, objValue);
} else {
acc[key] = objValue;
}
@@ -36,10 +36,9 @@ vi.mock('@lydell/node-pty', () => ({
spawn: mockPtySpawn,
}));
vi.mock('node:child_process', async (importOriginal) => {
const actual =
(await importOriginal()) as typeof import('node:child_process');
const actual = await importOriginal();
return {
...actual,
...(actual as object),
spawn: mockCpSpawn,
};
});
@@ -90,7 +89,7 @@ const createMockSerializeTerminalToObjectReturnValue = (
text: string | string[],
): AnsiOutput => {
const lines = Array.isArray(text) ? text : text.split('\n');
const len = (shellExecutionConfig.terminalHeight ?? 24) as number;
const len = shellExecutionConfig.terminalHeight ?? 24;
const expected: AnsiOutput = Array.from({ length: len }, (_, i) => [
{
text: (lines[i] || '').trim(),
@@ -108,7 +107,7 @@ const createMockSerializeTerminalToObjectReturnValue = (
const createExpectedAnsiOutput = (text: string | string[]): AnsiOutput => {
const lines = Array.isArray(text) ? text : text.split('\n');
const len = (shellExecutionConfig.terminalHeight ?? 24) as number;
const len = shellExecutionConfig.terminalHeight ?? 24;
const expected: AnsiOutput = Array.from({ length: len }, (_, i) => [
{
text: expect.stringMatching((lines[i] || '').trim()),
@@ -419,7 +418,7 @@ describe('ShellExecutionService', () => {
it('should write to the pty and trigger a render', async () => {
vi.useFakeTimers();
await simulateExecution('interactive-app', (pty) => {
ShellExecutionService.writeToPty(pty.pid!, 'input');
ShellExecutionService.writeToPty(pty.pid, 'input');
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
});
@@ -434,7 +433,7 @@ describe('ShellExecutionService', () => {
it('should resize the pty and the headless terminal', async () => {
await simulateExecution('ls -l', (pty) => {
pty.onData.mock.calls[0][0]('file1.txt\n');
ShellExecutionService.resizePty(pty.pid!, 100, 40);
ShellExecutionService.resizePty(pty.pid, 100, 40);
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
});
@@ -448,7 +447,7 @@ describe('ShellExecutionService', () => {
.mockReturnValue(false);
await simulateExecution('ls -l', (pty) => {
ShellExecutionService.resizePty(pty.pid!, 100, 40);
ShellExecutionService.resizePty(pty.pid, 100, 40);
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
});
@@ -468,7 +467,7 @@ describe('ShellExecutionService', () => {
// We don't expect this test to throw an error
await expect(
simulateExecution('ls -l', (pty) => {
ShellExecutionService.resizePty(pty.pid!, 100, 40);
ShellExecutionService.resizePty(pty.pid, 100, 40);
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
}),
).resolves.not.toThrow();
@@ -484,7 +483,7 @@ describe('ShellExecutionService', () => {
await expect(
simulateExecution('ls -l', (pty) => {
ShellExecutionService.resizePty(pty.pid!, 100, 40);
ShellExecutionService.resizePty(pty.pid, 100, 40);
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
}),
).rejects.toThrow('Some other error');
@@ -493,7 +492,7 @@ describe('ShellExecutionService', () => {
it('should scroll the headless terminal', async () => {
await simulateExecution('ls -l', (pty) => {
pty.onData.mock.calls[0][0]('file1.txt\n');
ShellExecutionService.scrollPty(pty.pid!, 10);
ShellExecutionService.scrollPty(pty.pid, 10);
pty.onExit.mock.calls[0][0]({ exitCode: 0, signal: null });
});
@@ -715,9 +715,7 @@ export class ShellExecutionService {
error,
aborted: abortSignal.aborted,
pid: ptyProcess.pid,
executionMethod:
(ptyInfo?.name as 'node-pty' | 'lydell-node-pty') ??
'node-pty',
executionMethod: ptyInfo?.name ?? 'node-pty',
});
};
@@ -136,7 +136,7 @@ describe('Global Activity Detector Functions', () => {
vi.useRealTimers();
});
it('should record activity on existing detector', () => {
const detector = getActivityDetector()!;
const detector = getActivityDetector();
const beforeTime = detector.getLastActivityTime();
vi.advanceTimersByTime(100);
+2 -2
View File
@@ -61,7 +61,7 @@ export async function resolveTelemetrySettings(options: {
settings.enabled;
const rawTarget =
(argv.telemetryTarget as string | TelemetryTarget | undefined) ??
argv.telemetryTarget ??
env['GEMINI_TELEMETRY_TARGET'] ??
(settings.target as string | TelemetryTarget | undefined);
const target = parseTelemetryTargetValue(rawTarget);
@@ -80,7 +80,7 @@ export async function resolveTelemetrySettings(options: {
settings.otlpEndpoint;
const rawProtocol =
(argv.telemetryOtlpProtocol as string | undefined) ??
argv.telemetryOtlpProtocol ??
env['GEMINI_TELEMETRY_OTLP_PROTOCOL'] ??
settings.otlpProtocol;
const otlpProtocol = (['grpc', 'http'] as const).find(
+1 -1
View File
@@ -101,7 +101,7 @@ describe('Telemetry Metrics', () => {
vi.resetModules();
vi.doMock('@opentelemetry/api', () => {
const actualApi = originalOtelMockFactory();
(actualApi.metrics.getMeter as Mock).mockReturnValue(mockMeterInstance);
actualApi.metrics.getMeter.mockReturnValue(mockMeterInstance);
return actualApi;
});
@@ -291,8 +291,7 @@ describe('StartupProfiler', () => {
profiler.flush(mockConfig);
const calls = (recordStartupPerformance as ReturnType<typeof vi.fn>).mock
.calls;
const calls = recordStartupPerformance.mock.calls;
const outerCall = calls.find((call) => call[2].phase === 'outer');
const innerCall = calls.find((call) => call[2].phase === 'inner');
+1 -1
View File
@@ -88,7 +88,7 @@ export async function runInDevTraceSpan<R>(
span.setAttribute('output-json', safeJsonStringify(meta.output));
}
for (const [key, value] of Object.entries(meta.attributes)) {
span.setAttribute(key, value as AttributeValue);
span.setAttribute(key, value);
}
if (meta.error) {
span.setStatus({
+2 -5
View File
@@ -15,7 +15,6 @@ import type { ApprovalMode } from '../policy/types.js';
import type { CompletedToolCall } from '../core/coreToolScheduler.js';
import { DiscoveredMCPTool } from '../tools/mcp-tool.js';
import type { FileDiff } from '../tools/tools.js';
import { AuthType } from '../core/contentGenerator.js';
import type { LogAttributes, LogRecord } from '@opentelemetry/api-logs';
import {
@@ -115,9 +114,7 @@ export class StartSessionEvent implements BaseTelemetryEvent {
.getAllTools()
.filter((tool) => tool instanceof DiscoveredMCPTool);
this.mcp_tools_count = mcpTools.length;
this.mcp_tools = mcpTools
.map((tool) => (tool as DiscoveredMCPTool).name)
.join(',');
this.mcp_tools = mcpTools.map((tool) => tool.name).join(',');
}
}
@@ -299,7 +296,7 @@ export class ToolCallEvent implements BaseTelemetryEvent {
call.response.resultDisplay !== null &&
'diffStat' in call.response.resultDisplay
) {
const diffStat = (call.response.resultDisplay as FileDiff).diffStat;
const diffStat = call.response.resultDisplay.diffStat;
if (diffStat) {
this.metadata = {
model_added_lines: diffStat.model_added_lines,
@@ -33,12 +33,12 @@ export class MockMessageBus {
// Capture hook-specific messages
if (message.type === MessageBusType.HOOK_EXECUTION_REQUEST) {
this.hookRequests.push(message as HookExecutionRequest);
this.hookRequests.push(message);
// Auto-respond with success for testing
const response: HookExecutionResponse = {
type: MessageBusType.HOOK_EXECUTION_RESPONSE,
correlationId: (message as HookExecutionRequest).correlationId,
correlationId: message.correlationId,
success: true,
output: {
decision: 'allow',
+2 -2
View File
@@ -156,12 +156,12 @@ describe('EditTool', () => {
const problematicSnippet =
snippetMatch && snippetMatch[1] ? snippetMatch[1] : '';
if (((schema as any).properties as any)?.corrected_target_snippet) {
if ((schema as any).properties?.corrected_target_snippet) {
return Promise.resolve({
corrected_target_snippet: problematicSnippet,
});
}
if (((schema as any).properties as any)?.corrected_new_string) {
if ((schema as any).properties?.corrected_new_string) {
// For new_string correction, we might need more sophisticated logic,
// but for now, returning original is a safe default if not specified by a test.
const originalNewStringMatch = promptText.match(
+1 -1
View File
@@ -523,7 +523,7 @@ class GrepToolInvocation extends BaseToolInvocation<
const allMatches: GrepMatch[] = [];
for await (const filePath of filesStream) {
const fileAbsolutePath = filePath as string;
const fileAbsolutePath = filePath;
try {
const content = await fsPromises.readFile(fileAbsolutePath, 'utf8');
const lines = content.split(/\r?\n/);
+2 -2
View File
@@ -184,11 +184,11 @@ export async function modifyWithEditor<ToolParams>(
overrides !== undefined && 'proposedContent' in overrides;
const currentContent = hasCurrentOverride
? (overrides!.currentContent ?? '')
? (overrides.currentContent ?? '')
: await modifyContext.getCurrentContent(originalParams);
const proposedContent = hasProposedOverride
? (overrides!.proposedContent ?? '')
? (overrides.proposedContent ?? '')
: await modifyContext.getProposedContent(originalParams);
const { oldPath, newPath, dirPath } = createTempFilesForModify(
+23 -79
View File
@@ -16,7 +16,6 @@ import type { Config } from '../config/config.js';
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
import { StandardFileSystemService } from '../services/fileSystemService.js';
import { createMockWorkspaceContext } from '../test-utils/mockWorkspaceContext.js';
import type { ToolInvocation, ToolResult } from './tools.js';
import { WorkspaceContext } from '../utils/workspaceContext.js';
vi.mock('../telemetry/loggers.js', () => ({
@@ -72,10 +71,7 @@ describe('ReadFileTool', () => {
};
const result = tool.build(params);
expect(typeof result).not.toBe('string');
const invocation = result as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = result;
expect(invocation.toolLocations()[0].path).toBe(
path.join(tempRootDir, 'test.txt'),
);
@@ -146,11 +142,9 @@ describe('ReadFileTool', () => {
};
const invocation = tool.build(params);
expect(typeof invocation).not.toBe('string');
expect(
(
invocation as ToolInvocation<ReadFileToolParams, ToolResult>
).getDescription(),
).toBe(path.join('sub', 'dir', 'file.txt'));
expect(invocation.getDescription()).toBe(
path.join('sub', 'dir', 'file.txt'),
);
});
it('should return shortened path when file path is deep', () => {
@@ -170,9 +164,7 @@ describe('ReadFileTool', () => {
const params: ReadFileToolParams = { file_path: deepPath };
const invocation = tool.build(params);
expect(typeof invocation).not.toBe('string');
const desc = (
invocation as ToolInvocation<ReadFileToolParams, ToolResult>
).getDescription();
const desc = invocation.getDescription();
expect(desc).toContain('...');
expect(desc).toContain('file.txt');
});
@@ -184,22 +176,16 @@ describe('ReadFileTool', () => {
};
const invocation = tool.build(params);
expect(typeof invocation).not.toBe('string');
expect(
(
invocation as ToolInvocation<ReadFileToolParams, ToolResult>
).getDescription(),
).toBe(path.join('sub', 'dir', 'file.txt'));
expect(invocation.getDescription()).toBe(
path.join('sub', 'dir', 'file.txt'),
);
});
it('should return . if path is the root directory', () => {
const params: ReadFileToolParams = { file_path: tempRootDir };
const invocation = tool.build(params);
expect(typeof invocation).not.toBe('string');
expect(
(
invocation as ToolInvocation<ReadFileToolParams, ToolResult>
).getDescription(),
).toBe('.');
expect(invocation.getDescription()).toBe('.');
});
});
@@ -209,10 +195,7 @@ describe('ReadFileTool', () => {
const fileContent = 'This is a test file.';
await fsp.writeFile(filePath, fileContent, 'utf-8');
const params: ReadFileToolParams = { file_path: 'textfile.txt' };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
expect(await invocation.execute(abortSignal)).toEqual({
llmContent: fileContent,
@@ -223,10 +206,7 @@ describe('ReadFileTool', () => {
it('should return error if file does not exist', async () => {
const filePath = path.join(tempRootDir, 'nonexistent.txt');
const params: ReadFileToolParams = { file_path: filePath };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
const result = await invocation.execute(abortSignal);
expect(result).toEqual({
@@ -245,10 +225,7 @@ describe('ReadFileTool', () => {
const fileContent = 'This is a test file.';
await fsp.writeFile(filePath, fileContent, 'utf-8');
const params: ReadFileToolParams = { file_path: filePath };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
expect(await invocation.execute(abortSignal)).toEqual({
llmContent: fileContent,
@@ -260,10 +237,7 @@ describe('ReadFileTool', () => {
const dirPath = path.join(tempRootDir, 'directory');
await fsp.mkdir(dirPath);
const params: ReadFileToolParams = { file_path: dirPath };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
const result = await invocation.execute(abortSignal);
expect(result).toEqual({
@@ -283,10 +257,7 @@ describe('ReadFileTool', () => {
const largeContent = 'x'.repeat(21 * 1024 * 1024);
await fsp.writeFile(filePath, largeContent, 'utf-8');
const params: ReadFileToolParams = { file_path: filePath };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
const result = await invocation.execute(abortSignal);
expect(result).toHaveProperty('error');
@@ -302,10 +273,7 @@ describe('ReadFileTool', () => {
const fileContent = `Short line\n${longLine}\nAnother short line`;
await fsp.writeFile(filePath, fileContent, 'utf-8');
const params: ReadFileToolParams = { file_path: filePath };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toContain(
@@ -323,10 +291,7 @@ describe('ReadFileTool', () => {
]);
await fsp.writeFile(imagePath, pngHeader);
const params: ReadFileToolParams = { file_path: imagePath };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toEqual({
@@ -344,10 +309,7 @@ describe('ReadFileTool', () => {
const pdfHeader = Buffer.from('%PDF-1.4');
await fsp.writeFile(pdfPath, pdfHeader);
const params: ReadFileToolParams = { file_path: pdfPath };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toEqual({
@@ -365,10 +327,7 @@ describe('ReadFileTool', () => {
const binaryData = Buffer.from([0x00, 0xff, 0x00, 0xff]);
await fsp.writeFile(binPath, binaryData);
const params: ReadFileToolParams = { file_path: binPath };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toBe(
@@ -382,10 +341,7 @@ describe('ReadFileTool', () => {
const svgContent = '<svg><circle cx="50" cy="50" r="40"/></svg>';
await fsp.writeFile(svgPath, svgContent, 'utf-8');
const params: ReadFileToolParams = { file_path: svgPath };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toBe(svgContent);
@@ -398,10 +354,7 @@ describe('ReadFileTool', () => {
const largeContent = '<svg>' + 'x'.repeat(1024 * 1024 + 1) + '</svg>';
await fsp.writeFile(svgPath, largeContent, 'utf-8');
const params: ReadFileToolParams = { file_path: svgPath };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toBe(
@@ -416,10 +369,7 @@ describe('ReadFileTool', () => {
const emptyPath = path.join(tempRootDir, 'empty.txt');
await fsp.writeFile(emptyPath, '', 'utf-8');
const params: ReadFileToolParams = { file_path: emptyPath };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toBe('');
@@ -437,10 +387,7 @@ describe('ReadFileTool', () => {
offset: 5, // Start from line 6
limit: 3,
};
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toContain(
@@ -465,10 +412,7 @@ describe('ReadFileTool', () => {
await fsp.writeFile(tempFilePath, tempFileContent, 'utf-8');
const params: ReadFileToolParams = { file_path: tempFilePath };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const invocation = tool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toBe(tempFileContent);
+2 -2
View File
@@ -147,12 +147,12 @@ describe('SmartEditTool', () => {
const problematicSnippet =
snippetMatch && snippetMatch[1] ? snippetMatch[1] : '';
if (((schema as any).properties as any)?.corrected_target_snippet) {
if ((schema as any).properties?.corrected_target_snippet) {
return Promise.resolve({
corrected_target_snippet: problematicSnippet,
});
}
if (((schema as any).properties as any)?.corrected_new_string) {
if ((schema as any).properties?.corrected_new_string) {
const originalNewStringMatch = promptText.match(
/original_new_string \(what was intended to replace original_old_string\):\n```\n([\s\S]*?)\n```/,
);
+1 -1
View File
@@ -536,7 +536,7 @@ describe('WriteFileTool', () => {
expect(confirmation.onConfirm).toBeDefined();
// Call `onConfirm` to trigger the logic that updates the content
await confirmation.onConfirm!(ToolConfirmationOutcome.ProceedOnce);
await confirmation.onConfirm(ToolConfirmationOutcome.ProceedOnce);
// Now, check if the original `params` object (captured by the invocation) was modified
expect(invocation.params.content).toBe('ide-modified-content');
+1 -1
View File
@@ -48,7 +48,7 @@ export function getToolCallDataSchema(historyItemSchema?: z.ZodTypeAny) {
export function generateCheckpointFileName(
toolCall: ToolCallRequestInfo,
): string | null {
const toolArgs = toolCall.args as Record<string, unknown>;
const toolArgs = toolCall.args;
const toolFilePath = toolArgs['file_path'] as string;
if (!toolFilePath) {
@@ -40,7 +40,7 @@ export const read = (key: string): string[] | undefined => crawlCache.get(key);
export const write = (key: string, results: string[], ttlMs: number): void => {
// Clear any existing timer for this key to prevent premature deletion
if (cacheTimers.has(key)) {
clearTimeout(cacheTimers.get(key)!);
clearTimeout(cacheTimers.get(key));
}
// Store the new data
@@ -129,7 +129,7 @@ class RecursiveFileSearch implements FileSearch {
let filteredCandidates;
const { files: candidates, isExactMatch } =
await this.resultCache!.get(pattern);
await this.resultCache.get(pattern);
if (isExactMatch) {
// Use the cached result.
@@ -151,7 +151,7 @@ class RecursiveFileSearch implements FileSearch {
}
if (shouldCache) {
this.resultCache!.set(pattern, filteredCandidates);
this.resultCache.set(pattern, filteredCandidates);
}
}
+1 -1
View File
@@ -29,7 +29,7 @@ export function doesToolInvocationMatch(
if (isTool(toolOrToolName)) {
toolNames = [toolOrToolName.name, toolOrToolName.constructor.name];
} else {
toolNames = [toolOrToolName as string];
toolNames = [toolOrToolName];
}
if (toolNames.some((name) => SHELL_TOOL_NAMES.includes(name))) {