diff --git a/apps/vscode-e2e/fixtures/strict-reasoning.json b/apps/vscode-e2e/fixtures/strict-reasoning.json new file mode 100644 index 0000000000..d7f9ed338a --- /dev/null +++ b/apps/vscode-e2e/fixtures/strict-reasoning.json @@ -0,0 +1,88 @@ +{ + "fixtures": [ + { + "match": { + "userMessage": "strict-reasoning-e2e-default" + }, + "response": { + "toolCalls": [ + { + "name": "attempt_completion", + "arguments": "{\"result\":\"4\"}", + "id": "call_strict_reasoning_default_001" + } + ] + } + }, + { + "match": { + "userMessage": "strict-reasoning-e2e-disabled" + }, + "response": { + "toolCalls": [ + { + "name": "attempt_completion", + "arguments": "{\"result\":\"4\"}", + "id": "call_strict_reasoning_disabled_001" + } + ] + } + }, + { + "match": { + "userMessage": "strict-reasoning-e2e-enabled" + }, + "response": { + "toolCalls": [ + { + "name": "attempt_completion", + "arguments": "{\"result\":\"4\"}", + "id": "call_strict_reasoning_enabled_001" + } + ] + } + }, + { + "match": { + "userMessage": "strict-reasoning-e2e-roundtrip-on" + }, + "response": { + "toolCalls": [ + { + "name": "attempt_completion", + "arguments": "{\"result\":\"4\"}", + "id": "call_strict_reasoning_roundtrip_on_001" + } + ] + } + }, + { + "match": { + "userMessage": "strict-reasoning-e2e-roundtrip-off" + }, + "response": { + "toolCalls": [ + { + "name": "attempt_completion", + "arguments": "{\"result\":\"4\"}", + "id": "call_strict_reasoning_roundtrip_off_001" + } + ] + } + }, + { + "match": { + "userMessage": "strict-reasoning-e2e-output" + }, + "response": { + "toolCalls": [ + { + "name": "attempt_completion", + "arguments": "{\"result\":\"4\"}", + "id": "call_strict_reasoning_output_001" + } + ] + } + } + ] +} diff --git a/apps/vscode-e2e/src/suite/strict-reasoning.test.ts b/apps/vscode-e2e/src/suite/strict-reasoning.test.ts new file mode 100644 index 0000000000..5ff36b6840 --- /dev/null +++ b/apps/vscode-e2e/src/suite/strict-reasoning.test.ts @@ -0,0 +1,336 @@ +import * as assert from "assert" + +import { RooCodeEventName, type ClineMessage } from "@roo-code/types" + +import { setDefaultSuiteTimeout } from "./test-utils" +import { waitUntilCompleted } from "./utils" + +/** + * E2E coverage for PR b05a-strict-reasoning-v2 ("Strict tool schemas"). + * + * The PR adds the profile-scoped `openAiToolStrictMode` toggle to the OpenAI + * Compatible provider (and all providers that share the OpenAI protocol within + * the same profile). When enabled, non-MCP function tools are sent with + * `strict: true` and their JSON schemas are hardened (`additionalProperties: + * false`, every property marked required). When disabled (the default), + * tools are sent with `strict: false` and their original best-effort schemas + * are preserved. MCP tools (`mcp--*` names) must ALWAYS remain non-strict + * regardless of the toggle. + * + * These tests run the built extension bundle against the aimock server, + * intercept the outbound `/v1/chat/completions` request bodies, and assert + * on the serialized `tools` array — proving the toggle is threaded through + * configuration → provider handler → wire format end-to-end. + */ + +type CapturedToolFunction = { + name?: string + strict?: boolean + parameters?: { + additionalProperties?: unknown + properties?: Record + required?: string[] + } +} + +type CapturedChatRequest = { + userMessageText?: string + tools: CapturedToolFunction[] + rawBody?: unknown +} + +const getRequestUrl = (input: RequestInfo | URL): string => + typeof input === "string" ? input : input instanceof URL ? input.href : (input as Request).url + +const messageContentText = (content: unknown): string => { + if (typeof content === "string") { + return content + } + + if (Array.isArray(content)) { + return content + .map((part) => (typeof part === "object" && part !== null ? ((part as { text?: string }).text ?? "") : "")) + .join("") + } + + return "" +} + +/** + * Installs a fetch interceptor that records the `tools` array of every + * chat-completions request sent to the OpenAI-compatible base URL. + * Returns a restore function. + */ +const installToolCapture = (capture: CapturedChatRequest[], baseUrl: string): (() => void) => { + const originalFetch = globalThis.fetch + const targetOrigin = new URL(baseUrl).origin + + globalThis.fetch = async function (input: RequestInfo | URL, init?: RequestInit): Promise { + try { + const url = getRequestUrl(input) + + if (new URL(url).origin === targetOrigin && init?.body && typeof init.body === "string") { + const body = JSON.parse(init.body) as { + tools?: Array<{ type?: string; function?: CapturedToolFunction }> + messages?: Array<{ role?: string; content?: unknown }> + } + + if (Array.isArray(body.tools) && body.tools.length > 0) { + const lastUser = [...(body.messages ?? [])].reverse().find((m) => m.role === "user") + + capture.push({ + userMessageText: messageContentText(lastUser?.content), + tools: body.tools + .filter((t) => t?.type === "function" && t.function) + .map((t) => t.function as CapturedToolFunction), + rawBody: body, + }) + } + } + } catch { + // Ignore non-JSON or unrelated traffic. + } + + return originalFetch.call(globalThis, input, init as RequestInit) + } as typeof globalThis.fetch + + return () => { + globalThis.fetch = originalFetch + } +} + +/** Finds the captured request whose last user message contains the probe tag. */ +const findProbeRequest = (requests: CapturedChatRequest[], probeTag: string) => + requests.find((r) => r.userMessageText?.includes(probeTag)) + +suite("Strict tool schema mode (openAiToolStrictMode)", function () { + setDefaultSuiteTimeout(this) + + let restoreFetch: (() => void) | undefined + const requests: CapturedChatRequest[] = [] + + let baseUrl: string + + setup(function () { + // These assertions require the aimock server so the OpenAI Compatible + // provider has a deterministic endpoint to stream from. Without + // AIMOCK_URL there is no real OpenAI key configured in CI, so skip. + if (!process.env.AIMOCK_URL) { + this.skip() + } + }) + + suiteSetup(async () => { + const aimockUrl = process.env.AIMOCK_URL! + baseUrl = `${aimockUrl}/v1` + restoreFetch = installToolCapture(requests, baseUrl) + }) + + suiteTeardown(async () => { + restoreFetch?.() + restoreFetch = undefined + + // Restore the default OpenRouter configuration so later suites (provider + // suites run after tool suites) see the expected default profile. + const aimockUrl = process.env.AIMOCK_URL + const isRecord = process.env.AIMOCK_RECORD === "true" + + await globalThis.api.setConfiguration({ + apiProvider: "openrouter" as const, + openRouterApiKey: aimockUrl && !isRecord ? "mock-key" : process.env.OPENROUTER_API_KEY!, + openRouterModelId: "openai/gpt-4.1", + ...(aimockUrl && { openRouterBaseUrl: `${aimockUrl}/v1` }), + }) + }) + + const configureOpenAiCompatible = async (strictMode: boolean | undefined) => { + await globalThis.api.setConfiguration({ + apiProvider: "openai" as const, + openAiApiKey: "mock-key", + openAiBaseUrl: baseUrl, + openAiModelId: "openai/gpt-4.1", + openAiStreamingEnabled: true, + ...(strictMode !== undefined && { openAiToolStrictMode: strictMode }), + }) + } + + const runProbeTask = async (probeTag: string) => { + const api = globalThis.api + + const taskId = await api.startNewTask({ + configuration: { mode: "ask", autoApprovalEnabled: true }, + text: `${probeTag}: what is 2+2? Reply with only the number.`, + }) + + await waitUntilCompleted({ api, taskId }) + + const captured = findProbeRequest(requests, probeTag) + assert.ok(captured, `Should have captured an outbound request containing probe tag "${probeTag}"`) + assert.ok(captured.tools.length > 0, "Captured request should include function tools") + + return captured + } + + test("strict mode disabled (default): non-MCP tools are sent with strict:false and unhardened schemas", async () => { + requests.length = 0 + await configureOpenAiCompatible(undefined) + + const captured = await runProbeTask("strict-reasoning-e2e-default") + + const nonMcpTools = captured.tools.filter((t) => !t.name?.startsWith("mcp--")) + assert.ok(nonMcpTools.length > 0, "Request should contain at least one non-MCP function tool") + + for (const tool of nonMcpTools) { + assert.strictEqual( + tool.strict, + false, + `Tool "${tool.name}" should be strict:false when openAiToolStrictMode is unset`, + ) + + // Schema hardening must NOT be applied: if properties exist, required + // must not be force-expanded to cover every property key. + if (tool.parameters?.properties) { + const allKeys = Object.keys(tool.parameters.properties) + const required = tool.parameters.required ?? [] + + if (allKeys.length > 0) { + assert.ok( + required.length <= allKeys.length, + `Tool "${tool.name}" required list should not exceed property count`, + ) + } + } + } + + // MCP tools (if any were registered) must always remain non-strict. + const mcpTools = captured.tools.filter((t) => t.name?.startsWith("mcp--")) + for (const tool of mcpTools) { + assert.strictEqual(tool.strict, false, `MCP tool "${tool.name}" must always be strict:false`) + } + }) + + test("strict mode explicitly disabled: identical behavior to default", async () => { + requests.length = 0 + await configureOpenAiCompatible(false) + + const captured = await runProbeTask("strict-reasoning-e2e-disabled") + + const nonMcpTools = captured.tools.filter((t) => !t.name?.startsWith("mcp--")) + assert.ok(nonMcpTools.length > 0, "Request should contain at least one non-MCP function tool") + + for (const tool of nonMcpTools) { + assert.strictEqual( + tool.strict, + false, + `Tool "${tool.name}" should be strict:false when openAiToolStrictMode is false`, + ) + } + }) + + test("strict mode enabled: non-MCP tools are sent with strict:true and hardened schemas", async () => { + requests.length = 0 + await configureOpenAiCompatible(true) + + const captured = await runProbeTask("strict-reasoning-e2e-enabled") + + const nonMcpTools = captured.tools.filter((t) => !t.name?.startsWith("mcp--")) + assert.ok(nonMcpTools.length > 0, "Request should contain at least one non-MCP function tool") + + for (const tool of nonMcpTools) { + assert.strictEqual( + tool.strict, + true, + `Tool "${tool.name}" should be strict:true when openAiToolStrictMode is true`, + ) + + // Strict mode hardening: object schemas must declare + // additionalProperties:false and list every property in `required`. + if (tool.parameters?.properties) { + const allKeys = Object.keys(tool.parameters.properties) + const required = tool.parameters.required ?? [] + + assert.strictEqual( + tool.parameters.additionalProperties, + false, + `Tool "${tool.name}" should set additionalProperties:false under strict mode`, + ) + + for (const key of allKeys) { + assert.ok( + required.includes(key), + `Tool "${tool.name}" should mark property "${key}" as required under strict mode`, + ) + } + } + } + + // MCP tools must remain non-strict even with the toggle enabled. + const mcpTools = captured.tools.filter((t) => t.name?.startsWith("mcp--")) + for (const tool of mcpTools) { + assert.strictEqual( + tool.strict, + false, + `MCP tool "${tool.name}" must remain strict:false even when openAiToolStrictMode is true`, + ) + } + }) + + test("strict mode toggle round-trips: enable → disable restores non-strict behavior", async () => { + // Enable strict mode and capture. + requests.length = 0 + await configureOpenAiCompatible(true) + const strictOn = await runProbeTask("strict-reasoning-e2e-roundtrip-on") + assert.ok( + strictOn.tools.some((t) => !t.name?.startsWith("mcp--") && t.strict === true), + "With strict mode on, at least one non-MCP tool should be strict:true", + ) + + // Disable strict mode and capture again. + requests.length = 0 + await configureOpenAiCompatible(false) + const strictOff = await runProbeTask("strict-reasoning-e2e-roundtrip-off") + + for (const tool of strictOff.tools.filter((t) => !t.name?.startsWith("mcp--"))) { + assert.strictEqual( + tool.strict, + false, + `Tool "${tool.name}" should return to strict:false after the toggle is disabled`, + ) + } + }) + + test("task completes and produces assistant output regardless of strict mode setting", async () => { + requests.length = 0 + await configureOpenAiCompatible(true) + + const api = globalThis.api + const messages: ClineMessage[] = [] + + const messageHandler = ({ message }: { taskId: string; message: ClineMessage }) => { + if (message.type === "say" && message.partial === false) { + messages.push(message) + } + } + + api.on(RooCodeEventName.Message, messageHandler) + + try { + const taskId = await api.startNewTask({ + configuration: { mode: "ask", autoApprovalEnabled: true }, + text: "strict-reasoning-e2e-output: what is 2+2? Reply with only the number.", + }) + + await waitUntilCompleted({ api, taskId }) + + // The mock replies via attempt_completion; assert the task surfaced a + // completion_result (or text) message — i.e. strict-mode serialization + // did not break the response handling / display path. + assert.ok( + messages.some((m) => (m.say === "completion_result" || m.say === "text") && (m.text?.length ?? 0) > 0), + "Task should produce a visible assistant completion message under strict mode", + ) + } finally { + api.off(RooCodeEventName.Message, messageHandler) + } + }) +}) diff --git a/packages/types/src/__tests__/provider-settings.test.ts b/packages/types/src/__tests__/provider-settings.test.ts index cd786a6529..77e7527b40 100644 --- a/packages/types/src/__tests__/provider-settings.test.ts +++ b/packages/types/src/__tests__/provider-settings.test.ts @@ -166,3 +166,75 @@ describe("getApiProtocol", () => { }) }) }) + +describe("openAiToolStrictMode", () => { + it("should be optional and absent by default", () => { + const result = providerSettingsSchemaDiscriminated.parse({ + apiProvider: "openai", + openAiModelId: "test-model", + }) + expect(result.apiProvider).toBe("openai") + if (result.apiProvider === "openai") { + expect(result.openAiToolStrictMode).toBeUndefined() + } + }) + + it("should accept true when provided", () => { + const result = providerSettingsSchemaDiscriminated.parse({ + apiProvider: "openai", + openAiModelId: "test-model", + openAiToolStrictMode: true, + }) + expect(result.apiProvider).toBe("openai") + if (result.apiProvider === "openai") { + expect(result.openAiToolStrictMode).toBe(true) + } + }) + + it("should accept false when provided", () => { + const result = providerSettingsSchemaDiscriminated.parse({ + apiProvider: "openai", + openAiModelId: "test-model", + openAiToolStrictMode: false, + }) + expect(result.apiProvider).toBe("openai") + if (result.apiProvider === "openai") { + expect(result.openAiToolStrictMode).toBe(false) + } + }) + + it("should not break existing profile deserialization when absent", () => { + const existingProfile = { + apiProvider: "openai" as const, + openAiBaseUrl: "https://api.example.com/v1", + openAiApiKey: "sk-test", + openAiModelId: "gpt-4", + openAiStreamingEnabled: true, + } + const result = providerSettingsSchemaDiscriminated.parse(existingProfile) + expect(result.apiProvider).toBe("openai") + if (result.apiProvider === "openai") { + expect(result.openAiModelId).toBe("gpt-4") + expect(result.openAiToolStrictMode).toBeUndefined() + } + }) + + it("should only exist on the openai (OpenAI Compatible) provider profile", () => { + const openAiResult = providerSettingsSchemaDiscriminated.parse({ + apiProvider: "openai", + openAiToolStrictMode: true, + }) + expect(openAiResult.apiProvider).toBe("openai") + if (openAiResult.apiProvider === "openai") { + expect(openAiResult.openAiToolStrictMode).toBe(true) + } + + // Anthropic provider should not have this field + const anthropicResult = providerSettingsSchemaDiscriminated.parse({ + apiProvider: "anthropic", + apiKey: "sk-test", + }) + expect(anthropicResult.apiProvider).toBe("anthropic") + expect((anthropicResult as Record).openAiToolStrictMode).toBeUndefined() + }) +}) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 99b75de2e4..e8d598bd6c 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -248,6 +248,7 @@ const openAiSchema = baseProviderSettingsSchema.extend({ openAiStreamingEnabled: z.boolean().optional(), openAiHostHeader: z.string().optional(), // Keep temporarily for backward compatibility during migration. openAiHeaders: z.record(z.string(), z.string()).optional(), + openAiToolStrictMode: z.boolean().optional(), // Profile-scoped strict function-tool schema toggle for OpenAI Compatible provider. Absent = false (backward compatible). }) const ollamaSchema = baseProviderSettingsSchema.extend({ diff --git a/packages/types/src/tool.ts b/packages/types/src/tool.ts index 4f90b63e9f..d89a8107c1 100644 --- a/packages/types/src/tool.ts +++ b/packages/types/src/tool.ts @@ -46,6 +46,7 @@ export const toolNames = [ "skill", "generate_image", "custom_tool", + "invalid_tool_call", ] as const export const toolNamesSchema = z.enum(toolNames) diff --git a/progress.txt b/progress.txt deleted file mode 100644 index b3983826b3..0000000000 --- a/progress.txt +++ /dev/null @@ -1,59 +0,0 @@ -# Reapplication Progress — rc6 branch cleanup -# Updated: 2026-02-15 - -## Completed Batches - -### Batch 1 — Clean cherry-picks (PR #11473) -- 22 PRs merged cleanly -- Status: MERGED to main - -### Batch 2 — Minor conflicts (PR #11474) -- 9 PRs with minor conflicts resolved -- Status: MERGED to main - -### Batch 3 — Skills Infrastructure & Browser Use Removal (4 PRs) -- PR #11102: skill mode dropdown (44 conflicts resolved) -- PR #11157: improve Skills/Slash Commands UI (6 conflicts resolved) -- PR #11414: remove built-in skills mechanism (4 conflicts resolved) -- PR #11392: remove browser use entirely (5 conflicts resolved) -- Status: ON BRANCH reapply/batch-3-4-5-major-conflicts - -### Batch 4 — Provider Removals (2 PRs) -- PR #11253: remove URL context/Grounding checkboxes (4 conflicts resolved) -- PR #11297: remove 9 low-usage providers + retired UX (14 conflicts resolved) -- Status: ON BRANCH reapply/batch-3-4-5-major-conflicts - -### Batch 5 — Azure Foundry -- PR #11315 and #11374: EXCLUDED — depends on AI-SDK (@ai-sdk/azure, from "ai") -- These PRs are AI-SDK-entangled and cannot be cherry-picked to the pre-AI-SDK codebase -- Status: DEFERRED (AI-SDK dependent) - -## Post-cherry-pick Fixes Applied -1. Restored gemini.ts + vertex.ts to pre-AI-SDK state (cherry-picks brought AI-SDK versions) -2. Restored ai-sdk.spec.ts, gemini-handler.spec.ts, vertex.spec.ts to pre-AI-SDK versions -3. Fixed processUserContentMentions.ts ghost import (rooMessage.ts doesn't exist) -4. Added missing skills type exports to @roo-code/types (SkillMetadata, validateSkillName, etc.) -5. Added SkillsSettings import to SettingsView.tsx -6. Added Dialog/Select/Collapsible mocks to SettingsView test files -7. Fixed Task.ts type mismatches (replaced local types with Anthropic SDK types) -8. Added skills state to ExtensionStateContext - -## Deferred PRs (AI-SDK Entangled) -- #11379: delegation (AI-SDK) -- #11418: delegation (AI-SDK) -- #11422: delegation (AI-SDK) -- #11315: Azure Foundry provider (AI-SDK) -- #11374: Azure Foundry fix (AI-SDK) - -## Validation Results -- Backend tests: ALL PASSED (5224 tests) -- UI tests: ALL PASSED (1267 tests) -- Type checks: ALL PASSED (14/14 packages) -- AI-SDK contamination: CLEAN (0 matches) - -## Notes -- Pre-push hook fails on `roo-cline:bundle` because `generate-built-in-skills.ts` was removed - by PR #11414 but `package.json` still references it in `prebundle`. This is expected and - will be resolved when the PR is merged to main and the script reference is cleaned up. -- Push was done with `--no-verify` after independent verification of types, backend tests, - and UI tests all passed cleanly. diff --git a/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts b/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts index ec87e29ab6..870ce43bde 100644 --- a/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts +++ b/src/api/providers/__tests__/base-openai-compatible-provider.spec.ts @@ -388,12 +388,111 @@ describe("BaseOpenAiCompatibleProvider", () => { }, ]), ) - + const stream = handler.createMessage("system prompt", []) const chunks = await collectStream(stream) - + const endChunks = chunks.filter((chunk) => chunk.type === "tool_call_end") expect(endChunks).toHaveLength(0) }) }) -}) + + describe("request parameter construction", () => { + const toolDef: OpenAI.Chat.ChatCompletionTool[] = [ + { + type: "function", + function: { + name: "read_file", + description: "Read a file", + parameters: { type: "object", properties: {} }, + }, + }, + ] + + it("should omit parallel_tool_calls when no tools are provided", async () => { + mockCreate.mockImplementationOnce(() => + asyncStreamFrom([{ choices: [{ delta: { content: "ok" }, finish_reason: "stop" }] }]), + ) + + const stream = handler.createMessage("system prompt", []) + await collectStream(stream) + + const callArgs = mockCreate.mock.calls[0][0] + expect(callArgs).not.toHaveProperty("parallel_tool_calls") + }) + + it("should include parallel_tool_calls: true by default when tools are present", async () => { + mockCreate.mockImplementationOnce(() => + asyncStreamFrom([{ choices: [{ delta: { content: "ok" }, finish_reason: "stop" }] }]), + ) + + const stream = handler.createMessage("system prompt", [], { taskId: "t1", tools: toolDef }) + await collectStream(stream) + + const callArgs = mockCreate.mock.calls[0][0] + expect(callArgs.parallel_tool_calls).toBe(true) + }) + + it("should honor parallelToolCalls: false from metadata when tools are present", async () => { + mockCreate.mockImplementationOnce(() => + asyncStreamFrom([{ choices: [{ delta: { content: "ok" }, finish_reason: "stop" }] }]), + ) + + const stream = handler.createMessage("system prompt", [], { + taskId: "t1", + tools: toolDef, + parallelToolCalls: false, + }) + await collectStream(stream) + + const callArgs = mockCreate.mock.calls[0][0] + expect(callArgs.parallel_tool_calls).toBe(false) + }) + }) + + describe("openAiToolStrictMode pass-through", () => { + class StrictModeProvider extends TestOpenAiCompatibleProvider { + constructor(strict: boolean | undefined) { + super("test-api-key") + this.options.openAiToolStrictMode = strict + } + } + + const toolDef: OpenAI.Chat.ChatCompletionTool[] = [ + { + type: "function", + function: { + name: "read_file", + description: "Read a file", + parameters: { type: "object", properties: {} }, + }, + }, + ] + + it("should default to strict: false when openAiToolStrictMode is unset", async () => { + const strictHandler = new StrictModeProvider(undefined) + mockCreate.mockImplementationOnce(() => + asyncStreamFrom([{ choices: [{ delta: { content: "ok" }, finish_reason: "stop" }] }]), + ) + + const stream = strictHandler.createMessage("system prompt", [], { taskId: "t1", tools: toolDef }) + await collectStream(stream) + + const callArgs = mockCreate.mock.calls[0][0] + expect(callArgs.tools[0].function.strict).toBe(false) + }) + + it("should pass strict: true when openAiToolStrictMode is enabled", async () => { + const strictHandler = new StrictModeProvider(true) + mockCreate.mockImplementationOnce(() => + asyncStreamFrom([{ choices: [{ delta: { content: "ok" }, finish_reason: "stop" }] }]), + ) + + const stream = strictHandler.createMessage("system prompt", [], { taskId: "t1", tools: toolDef }) + await collectStream(stream) + + const callArgs = mockCreate.mock.calls[0][0] + expect(callArgs.tools[0].function.strict).toBe(true) + }) + }) + }) diff --git a/src/api/providers/__tests__/base-provider.spec.ts b/src/api/providers/__tests__/base-provider.spec.ts index ced452f5a5..66109e7cf3 100644 --- a/src/api/providers/__tests__/base-provider.spec.ts +++ b/src/api/providers/__tests__/base-provider.spec.ts @@ -28,8 +28,8 @@ class TestProvider extends BaseProvider { } // Expose protected method for testing - public testConvertToolsForOpenAI(tools: any[] | undefined): any[] | undefined { - return this.convertToolsForOpenAI(tools) + public testConvertToolsForOpenAI(tools: any[] | undefined, strictMode: boolean = false): any[] | undefined { + return this.convertToolsForOpenAI(tools, strictMode) } } @@ -176,6 +176,16 @@ describe("BaseProvider", () => { expect(result.additionalProperties).toBe(false) expect(result.required).toEqual([]) }) + + it("should add empty properties and required arrays to zero-argument object schemas", () => { + const result = provider.testConvertToolSchemaForOpenAI({ type: "object" }) + + expect(result).toMatchObject({ + additionalProperties: false, + properties: {}, + required: [], + }) + }) }) describe("convertToolsForOpenAI", () => { @@ -184,100 +194,230 @@ describe("BaseProvider", () => { expect(result).toBeUndefined() }) - it("should set strict: true for non-MCP tools", () => { + it("should preserve non-function tools unchanged", () => { const tools = [ { - type: "function", - function: { - name: "read_file", - description: "Read a file", - parameters: { type: "object", properties: {} }, - }, + type: "other_type", + data: "some data", }, ] const result = provider.testConvertToolsForOpenAI(tools) - expect(result?.[0].function.strict).toBe(true) + expect(result?.[0]).toEqual(tools[0]) }) - it("should set strict: false for MCP tools (mcp-- prefix)", () => { - const tools = [ - { - type: "function", - function: { - name: "mcp--github--get_me", - description: "Get current user", - parameters: { type: "object", properties: {} }, + describe("strictMode = false (default)", () => { + it("should set strict: false for non-MCP tools", () => { + const tools = [ + { + type: "function", + function: { + name: "read_file", + description: "Read a file", + parameters: { type: "object", properties: {} }, + }, }, - }, - ] + ] - const result = provider.testConvertToolsForOpenAI(tools) + const result = provider.testConvertToolsForOpenAI(tools) - expect(result?.[0].function.strict).toBe(false) - }) + expect(result?.[0].function.strict).toBe(false) + }) - it("should apply schema conversion to non-MCP tools", () => { - const tools = [ - { - type: "function", - function: { - name: "read_file", - description: "Read a file", - parameters: { - type: "object", - properties: { - path: { type: "string" }, + it("should preserve original best-effort schema for non-MCP tools (no hardening)", () => { + const tools = [ + { + type: "function", + function: { + name: "read_file", + description: "Read a file", + parameters: { + type: "object", + properties: { + path: { type: "string" }, + encoding: { type: ["string", "null"] }, + }, + // Note: no required array, no additionalProperties }, }, }, - }, - ] + ] + + const result = provider.testConvertToolsForOpenAI(tools) + + // Schema should NOT be hardened when strict is false + expect(result?.[0].function.parameters.additionalProperties).toBeUndefined() + expect(result?.[0].function.parameters.required).toBeUndefined() + // Nullable type should be preserved as-is + expect(result?.[0].function.parameters.properties.encoding.type).toEqual(["string", "null"]) + }) + + it("should set strict: false for MCP tools", () => { + const tools = [ + { + type: "function", + function: { + name: "mcp--github--get_me", + description: "Get current user", + parameters: { type: "object", properties: {} }, + }, + }, + ] - const result = provider.testConvertToolsForOpenAI(tools) + const result = provider.testConvertToolsForOpenAI(tools) - expect(result?.[0].function.parameters.additionalProperties).toBe(false) - expect(result?.[0].function.parameters.required).toEqual(["path"]) - }) + expect(result?.[0].function.strict).toBe(false) + }) - it("should not apply schema conversion to MCP tools in base-provider", () => { - // Note: In base-provider, MCP tools are passed through unchanged - // The openai-native provider has its own handling for MCP tools - const tools = [ - { - type: "function", - function: { - name: "mcp--github--get_me", - description: "Get current user", - parameters: { - type: "object", - properties: { - token: { type: "string" }, + it("should preserve original schema for MCP tools (no hardening)", () => { + const tools = [ + { + type: "function", + function: { + name: "mcp--github--get_me", + description: "Get current user", + parameters: { + type: "object", + properties: { + token: { type: "string" }, + }, + required: ["token"], }, - required: ["token"], }, }, - }, - ] + ] - const result = provider.testConvertToolsForOpenAI(tools) + const result = provider.testConvertToolsForOpenAI(tools) - // MCP tools pass through original parameters in base-provider - expect(result?.[0].function.parameters.additionalProperties).toBeUndefined() + expect(result?.[0].function.parameters.additionalProperties).toBeUndefined() + expect(result?.[0].function.parameters.required).toEqual(["token"]) + }) }) - it("should preserve non-function tools unchanged", () => { - const tools = [ - { - type: "other_type", - data: "some data", - }, - ] + describe("strictMode = true", () => { + it("should set strict: true for non-MCP tools", () => { + const tools = [ + { + type: "function", + function: { + name: "read_file", + description: "Read a file", + parameters: { type: "object", properties: {} }, + }, + }, + ] - const result = provider.testConvertToolsForOpenAI(tools) + const result = provider.testConvertToolsForOpenAI(tools, true) - expect(result?.[0]).toEqual(tools[0]) + expect(result?.[0].function.strict).toBe(true) + }) + + it("should apply schema hardening to non-MCP tools", () => { + const tools = [ + { + type: "function", + function: { + name: "read_file", + description: "Read a file", + parameters: { + type: "object", + properties: { + path: { type: "string" }, + }, + }, + }, + }, + ] + + const result = provider.testConvertToolsForOpenAI(tools, true) + + expect(result?.[0].function.parameters.additionalProperties).toBe(false) + expect(result?.[0].function.parameters.required).toEqual(["path"]) + }) + + it("should harden nested objects and arrays in non-MCP tools", () => { + const tools = [ + { + type: "function", + function: { + name: "create_user", + description: "Create a user", + parameters: { + type: "object", + properties: { + user: { + type: "object", + properties: { + name: { type: "string" }, + }, + }, + tags: { + type: "array", + items: { + type: "object", + properties: { + label: { type: "string" }, + }, + }, + }, + }, + }, + }, + }, + ] + + const result = provider.testConvertToolsForOpenAI(tools, true) + + expect(result?.[0].function.parameters.additionalProperties).toBe(false) + expect(result?.[0].function.parameters.properties.user.additionalProperties).toBe(false) + expect(result?.[0].function.parameters.properties.tags.items.additionalProperties).toBe(false) + }) + + it("should ALWAYS set strict: false for MCP tools even when strictMode is true", () => { + const tools = [ + { + type: "function", + function: { + name: "mcp--github--get_me", + description: "Get current user", + parameters: { type: "object", properties: {} }, + }, + }, + ] + + const result = provider.testConvertToolsForOpenAI(tools, true) + + expect(result?.[0].function.strict).toBe(false) + }) + + it("should preserve original schema for MCP tools even when strictMode is true", () => { + const tools = [ + { + type: "function", + function: { + name: "mcp--github--get_me", + description: "Get current user", + parameters: { + type: "object", + properties: { + token: { type: "string" }, + optional_param: { type: ["string", "null"] }, + }, + required: ["token"], + }, + }, + }, + ] + + const result = provider.testConvertToolsForOpenAI(tools, true) + + // MCP schema should NOT be hardened + expect(result?.[0].function.parameters.additionalProperties).toBeUndefined() + expect(result?.[0].function.parameters.required).toEqual(["token"]) + // Nullable type preserved + expect(result?.[0].function.parameters.properties.optional_param.type).toEqual(["string", "null"]) + }) }) }) }) diff --git a/src/api/providers/__tests__/openai-compatible.spec.ts b/src/api/providers/__tests__/openai-compatible.spec.ts new file mode 100644 index 0000000000..7cea913d63 --- /dev/null +++ b/src/api/providers/__tests__/openai-compatible.spec.ts @@ -0,0 +1,108 @@ +// npx vitest run api/providers/__tests__/openai-compatible.spec.ts + +import OpenAI from "openai" + +import type { ModelInfo } from "@roo-code/types" + +import { OpenAICompatibleHandler, type OpenAICompatibleConfig } from "../openai-compatible" + +const mockStreamText = vi.fn() + +// Mock the AI SDK streamText to capture request options without network calls. +vi.mock("ai", async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + streamText: (...args: unknown[]) => mockStreamText(...args), + generateText: vi.fn(), + } +}) + +// Mock @ai-sdk/openai-compatible so no real provider is constructed. +vi.mock("@ai-sdk/openai-compatible", () => ({ + createOpenAICompatible: vi.fn(() => { + return (modelId: string) => ({ modelId }) + }), +})) + +const testModelInfo: ModelInfo = { + maxTokens: 4096, + contextWindow: 128000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, +} + +const toolDef: OpenAI.Chat.ChatCompletionTool[] = [ + { + type: "function", + function: { + name: "read_file", + description: "Read a file", + parameters: { type: "object", properties: {} }, + }, + }, +] + +class TestHandler extends OpenAICompatibleHandler { + /** Records the (tools, strict) args of every internal convertToolsForOpenAI call. */ + public convertCalls: Array<{ tools: OpenAI.Chat.ChatCompletionTool[] | undefined; strict: boolean }> = [] + + constructor(strict: boolean | undefined) { + const config: OpenAICompatibleConfig = { + providerName: "test", + baseURL: "https://test.example.com/v1", + apiKey: "test-key", + modelId: "test-model", + modelInfo: testModelInfo, + } + super({ openAiToolStrictMode: strict }, config) + } + + override getModel(): { id: string; info: ModelInfo } { + return { id: "test-model", info: testModelInfo } + } + + protected override convertToolsForOpenAI( + tools: OpenAI.Chat.ChatCompletionTool[] | undefined, + strict = false, + ) { + this.convertCalls.push({ tools, strict }) + return super.convertToolsForOpenAI(tools, strict) + } +} + +describe("OpenAICompatibleHandler", () => { + beforeEach(() => { + vi.clearAllMocks() + mockStreamText.mockReturnValue({ + fullStream: (async function* () { + yield { type: "text-delta", textDelta: "ok" } + })(), + usage: Promise.resolve({ inputTokens: 1, outputTokens: 1 }), + }) + }) + + it("should pass strict: false to convertToolsForOpenAI when openAiToolStrictMode is unset", async () => { + const handler = new TestHandler(undefined) + + const stream = handler.createMessage("system", []) + for await (const _ of stream) { + // drain + } + + expect(handler.convertCalls).toEqual([{ tools: undefined, strict: false }]) + }) + + it("should pass strict: true to convertToolsForOpenAI when openAiToolStrictMode is enabled", async () => { + const handler = new TestHandler(true) + + const stream = handler.createMessage("system", [], { taskId: "t1", tools: toolDef }) + for await (const _ of stream) { + // drain + } + + expect(handler.convertCalls).toEqual([{ tools: toolDef, strict: true }]) + }) +}) diff --git a/src/api/providers/__tests__/openai.spec.ts b/src/api/providers/__tests__/openai.spec.ts index e8146a999a..ef319811ed 100644 --- a/src/api/providers/__tests__/openai.spec.ts +++ b/src/api/providers/__tests__/openai.spec.ts @@ -885,7 +885,6 @@ describe("OpenAiHandler", () => { // No custom temperature set → `temperature` is omitted. tools: undefined, tool_choice: undefined, - parallel_tool_calls: true, }, { path: "/models/chat/completions" }, ) @@ -893,6 +892,7 @@ describe("OpenAiHandler", () => { // Verify max_tokens is NOT included when not explicitly set const callArgs = mockCreate.mock.calls[0][0] expect(callArgs).not.toHaveProperty("max_completion_tokens") + expect(callArgs).not.toHaveProperty("parallel_tool_calls") }) it("should handle non-streaming responses with Azure AI Inference Service", async () => { @@ -931,7 +931,6 @@ describe("OpenAiHandler", () => { ], tools: undefined, tool_choice: undefined, - parallel_tool_calls: true, }, { path: "/models/chat/completions" }, ) @@ -939,6 +938,7 @@ describe("OpenAiHandler", () => { // Verify max_tokens is NOT included when not explicitly set const callArgs = mockCreate.mock.calls[0][0] expect(callArgs).not.toHaveProperty("max_completion_tokens") + expect(callArgs).not.toHaveProperty("parallel_tool_calls") }) it("should handle completePrompt with Azure AI Inference Service", async () => { @@ -1014,6 +1014,7 @@ describe("OpenAiHandler", () => { it("should handle O3 model with streaming and include max_completion_tokens when includeMaxTokens is true", async () => { const o3Handler = new OpenAiHandler({ ...o3Options, + reasoningEffort: "high", includeMaxTokens: true, modelMaxTokens: 32000, modelTemperature: 0.5, @@ -1041,7 +1042,7 @@ describe("OpenAiHandler", () => { ], stream: true, stream_options: { include_usage: true }, - reasoning_effort: "medium", + reasoning_effort: "high", temperature: undefined, // O3 models do not support deprecated max_tokens but do support max_completion_tokens max_completion_tokens: 32000, @@ -1200,6 +1201,7 @@ describe("OpenAiHandler", () => { it("should handle O3 model non-streaming with reasoning_effort and max_completion_tokens when includeMaxTokens is true", async () => { const o3Handler = new OpenAiHandler({ ...o3Options, + reasoningEffort: "high", openAiStreamingEnabled: false, includeMaxTokens: true, modelTemperature: 0.3, @@ -1225,7 +1227,7 @@ describe("OpenAiHandler", () => { }, { role: "user", content: "Hello!" }, ], - reasoning_effort: "medium", + reasoning_effort: "high", temperature: undefined, // O3 models do not support deprecated max_tokens but do support max_completion_tokens max_completion_tokens: 65536, // Using default maxTokens from o3Options diff --git a/src/api/providers/__tests__/xai.spec.ts b/src/api/providers/__tests__/xai.spec.ts index 44c3f26f6c..ab02b3b2f4 100644 --- a/src/api/providers/__tests__/xai.spec.ts +++ b/src/api/providers/__tests__/xai.spec.ts @@ -10,19 +10,11 @@ vitest.mock("@roo-code/telemetry", () => ({ }, })) -const mockResponsesCreate = vitest.fn() - -vitest.mock("openai", () => { - const mockConstructor = vitest.fn() - - return { - __esModule: true, - default: mockConstructor.mockImplementation(function () { - return { - responses: { create: mockResponsesCreate }, - } - }), - } +const mockResponsesCreate = vitest.hoisted(() => vitest.fn()) + +vitest.mock("openai", async () => { + const { mockOpenAiResponsesClient } = await import("../../../test-utils/api") + return mockOpenAiResponsesClient(mockResponsesCreate) }) import OpenAI from "openai" diff --git a/src/api/providers/base-openai-compatible-provider.ts b/src/api/providers/base-openai-compatible-provider.ts index f4928b0b0a..b9ddea3c8c 100644 --- a/src/api/providers/base-openai-compatible-provider.ts +++ b/src/api/providers/base-openai-compatible-provider.ts @@ -93,9 +93,14 @@ export abstract class BaseOpenAiCompatibleProvider messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], stream: true, stream_options: { include_usage: true }, - tools: this.convertToolsForOpenAI(metadata?.tools), + tools: this.convertToolsForOpenAI(metadata?.tools, this.options.openAiToolStrictMode ?? false), tool_choice: metadata?.tool_choice, - parallel_tool_calls: metadata?.parallelToolCalls ?? true, + // Only send parallel_tool_calls when tools are present; some + // OpenAI-compatible providers (e.g. Upstage solar-open2) reject + // this field when no tools are supplied. + ...(metadata?.tools && metadata.tools.length > 0 + ? { parallel_tool_calls: metadata?.parallelToolCalls ?? true } + : {}), } // Add thinking parameter if reasoning is enabled and model supports it diff --git a/src/api/providers/base-provider.ts b/src/api/providers/base-provider.ts index 89366fb619..de25ad3c8f 100644 --- a/src/api/providers/base-provider.ts +++ b/src/api/providers/base-provider.ts @@ -23,11 +23,23 @@ export abstract class BaseProvider implements ApiHandler { abstract getModel(): { id: string; info: ModelInfo } /** - * Converts an array of tools to be compatible with OpenAI's strict mode. - * Filters for function tools, applies schema conversion to their parameters, - * and ensures all tools have consistent strict: true values. + * Converts an array of tools for OpenAI-compatible providers. + * Filters for function tools and applies schema conversion to their parameters. + * + * When `strictMode` is true, non-MCP function tools get `strict: true` and + * their schemas are hardened via `convertToolSchemaForOpenAI()` (adds + * `additionalProperties: false`, marks all properties required, etc.). + * + * When `strictMode` is false (default), non-MCP function tools get + * `strict: false` and their original best-effort schemas are preserved + * without hardening. This is semantically consistent: `strict: false` + * should not imply strict-schema transformations. + * + * MCP tools are ALWAYS `strict: false` with original parameters preserved, + * regardless of the `strictMode` setting, because MCP schemas may contain + * optional properties that must remain optional. */ - protected convertToolsForOpenAI(tools: any[] | undefined): any[] | undefined { + protected convertToolsForOpenAI(tools: any[] | undefined, strictMode: boolean = false): any[] | undefined { if (!tools) { return undefined } @@ -37,18 +49,40 @@ export abstract class BaseProvider implements ApiHandler { return tool } - // MCP tools use the 'mcp--' prefix - disable strict mode for them + // MCP tools use the 'mcp--' prefix - always disable strict mode // to preserve optional parameters from the MCP server schema const isMcp = isMcpTool(tool.function.name) + if (isMcp) { + return { + ...tool, + function: { + ...tool.function, + strict: false, + parameters: tool.function.parameters, + }, + } + } + + // Non-MCP function tools respect the strictMode setting + if (strictMode) { + return { + ...tool, + function: { + ...tool.function, + strict: true, + parameters: this.convertToolSchemaForOpenAI(tool.function.parameters), + }, + } + } + + // strictMode false: preserve original best-effort schema return { ...tool, function: { ...tool.function, - strict: !isMcp, - parameters: isMcp - ? tool.function.parameters - : this.convertToolSchemaForOpenAI(tool.function.parameters), + strict: false, + parameters: tool.function.parameters, }, } }) @@ -76,6 +110,11 @@ export abstract class BaseProvider implements ApiHandler { result.additionalProperties = false } + if (result.properties === undefined) { + result.properties = {} + result.required = [] + } + if (result.properties) { const allKeys = Object.keys(result.properties) // OpenAI strict mode requires ALL properties to be in required array diff --git a/src/api/providers/deepseek.ts b/src/api/providers/deepseek.ts index 2e85c016b0..023fc929fd 100644 --- a/src/api/providers/deepseek.ts +++ b/src/api/providers/deepseek.ts @@ -155,7 +155,7 @@ export class DeepSeekHandler extends OpenAiHandler { stream_options: { include_usage: true }, ...(thinking && { thinking }), ...(deepSeekReasoningEffort && { reasoning_effort: deepSeekReasoningEffort }), - tools: this.convertToolsForOpenAI(metadata?.tools), + tools: this.convertToolsForOpenAI(metadata?.tools, this.options.openAiToolStrictMode ?? false), tool_choice: metadata?.tool_choice, parallel_tool_calls: metadata?.parallelToolCalls ?? true, } diff --git a/src/api/providers/friendli.ts b/src/api/providers/friendli.ts index a5507e355a..8507c58ba7 100644 --- a/src/api/providers/friendli.ts +++ b/src/api/providers/friendli.ts @@ -169,7 +169,7 @@ export class FriendliHandler extends BaseOpenAiCompatibleProvider 0 + ? { parallel_tool_calls: metadata?.parallelToolCalls ?? true } + : {}), } // Add max_tokens if needed @@ -231,9 +236,14 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl ? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) : [systemMessage, ...convertToOpenAiMessages(messages)], // Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS) - tools: this.convertToolsForOpenAI(metadata?.tools), + tools: this.convertToolsForOpenAI(metadata?.tools, this.options.openAiToolStrictMode ?? false), tool_choice: metadata?.tool_choice, - parallel_tool_calls: metadata?.parallelToolCalls ?? true, + // Only send parallel_tool_calls when tools are present; some + // OpenAI-compatible providers (e.g. Upstage solar-open2) reject + // this field when no tools are supplied. + ...(metadata?.tools && metadata.tools.length > 0 + ? { parallel_tool_calls: metadata?.parallelToolCalls ?? true } + : {}), } // Add max_tokens if needed @@ -342,7 +352,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl messages: Anthropic.Messages.MessageParam[], metadata?: ApiHandlerCreateMessageMetadata, ): ApiStream { - const modelInfo = this.getModel().info + const { info: modelInfo, reasoning } = this.getModel() const methodIsAzureAiInference = this._isAzureAiInference(this.options.openAiBaseUrl) if (this.options.openAiStreamingEnabled ?? true) { @@ -359,10 +369,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl ], stream: true, ...(isGrokXAI ? {} : { stream_options: { include_usage: true } }), - reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined, + ...(reasoning && reasoning), temperature: undefined, // Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS) - tools: this.convertToolsForOpenAI(metadata?.tools), + tools: this.convertToolsForOpenAI(metadata?.tools, this.options.openAiToolStrictMode ?? false), tool_choice: metadata?.tool_choice, parallel_tool_calls: metadata?.parallelToolCalls ?? true, } @@ -393,10 +403,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl }, ...convertToOpenAiMessages(messages), ], - reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined, + ...(reasoning && reasoning), temperature: undefined, // Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS) - tools: this.convertToolsForOpenAI(metadata?.tools), + tools: this.convertToolsForOpenAI(metadata?.tools, this.options.openAiToolStrictMode ?? false), tool_choice: metadata?.tool_choice, parallel_tool_calls: metadata?.parallelToolCalls ?? true, } diff --git a/src/api/providers/opencode-go.ts b/src/api/providers/opencode-go.ts index be53dc1c02..3e490ee5ce 100644 --- a/src/api/providers/opencode-go.ts +++ b/src/api/providers/opencode-go.ts @@ -189,7 +189,7 @@ export class OpencodeGoHandler extends RouterProvider implements SingleCompletio this.options.includeMaxTokens === true ? this.options.modelMaxTokens || maxTokens : maxTokens, stream: true, stream_options: { include_usage: true }, - tools: this.convertToolsForOpenAI(metadata?.tools), + tools: this.convertToolsForOpenAI(metadata?.tools, this.options.openAiToolStrictMode ?? false), tool_choice: metadata?.tool_choice, parallel_tool_calls: metadata?.parallelToolCalls ?? true, ...(reasoningEffort && { diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 3e59b4360b..d74920adff 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -327,7 +327,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH }, }), ...(reasoning && { reasoning }), - tools: this.convertToolsForOpenAI(metadata?.tools), + tools: this.convertToolsForOpenAI(metadata?.tools, this.options.openAiToolStrictMode ?? false), tool_choice: metadata?.tool_choice, } diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts index 7838553835..1ef25e852b 100644 --- a/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts +++ b/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts @@ -23,6 +23,17 @@ vi.mock("@roo-code/core", () => ({ }, })) +// presentAssistantMessage records tool usage through TelemetryService.instance. +vi.mock("@roo-code/telemetry", () => ({ + TelemetryService: { + instance: { + captureToolUsage: vi.fn(), + captureConsecutiveMistakeError: vi.fn(), + captureEvent: vi.fn(), + }, + }, +})) + import { customToolRegistry } from "@roo-code/core" describe("presentAssistantMessage - Custom Tool Recording", () => { diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-tool-usage-attribution.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-tool-usage-attribution.spec.ts new file mode 100644 index 0000000000..c75eb6ee18 --- /dev/null +++ b/src/core/assistant-message/__tests__/presentAssistantMessage-tool-usage-attribution.spec.ts @@ -0,0 +1,319 @@ +// npx vitest src/core/assistant-message/__tests__/presentAssistantMessage-tool-usage-attribution.spec.ts + +import type { Anthropic } from "@anthropic-ai/sdk" +import { describe, it, expect, beforeEach, vi } from "vitest" +import { presentAssistantMessage } from "../presentAssistantMessage" +import { validateToolUse } from "../../tools/validateToolUse" +import { getModeBySlug } from "../../../shared/modes" +import type { Task } from "../../task/Task" + +vi.mock("../../task/Task") +vi.mock("../../../shared/modes", async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + getModeBySlug: vi.fn(actual.getModeBySlug), + } +}) +// isValidToolName is left as the real implementation (only validateToolUse is +// mocked): it has its own independent mcp_ prefix carve-out, and a hand-rolled +// mock allowlist here would mask a regression in toTelemetryToolName's +// ordering relative to isValidToolName. +vi.mock("../../tools/validateToolUse", async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + validateToolUse: vi.fn(), + } +}) + +vi.mock("@roo-code/core", () => ({ + customToolRegistry: { + has: vi.fn(() => false), + get: vi.fn(), + }, +})) + +vi.mock("@roo-code/telemetry", () => ({ + TelemetryService: { + instance: { + captureToolUsage: vi.fn(), + captureConsecutiveMistakeError: vi.fn(), + captureEvent: vi.fn(), + }, + }, +})) + +import { TelemetryService } from "@roo-code/telemetry" + +interface MockTask { + taskId: string + instanceId: string + abort: boolean + presentAssistantMessageLocked: boolean + presentAssistantMessageHasPendingUpdates: boolean + currentStreamingContentIndex: number + assistantMessageContent: unknown[] + userMessageContent: Anthropic.ToolResultBlockParam[] + didCompleteReadingStream: boolean + didRejectTool: boolean + didAlreadyUseTool: boolean + consecutiveMistakeCount: number + clineMessages: unknown[] + api: { getModel: () => { id: string; info: Record } } + recordToolUsage: ReturnType + recordToolError: ReturnType + toolRepetitionDetector: { check: ReturnType } + providerRef: { + deref: () => { + getState: ReturnType + getMcpHub?: () => { findServerNameBySanitizedName: (name: string) => string | undefined } + } + } + say: ReturnType + ask: ReturnType + pushToolResultToUserContent: ReturnType +} + +describe("presentAssistantMessage - tool usage attribution", () => { + let mockTask: MockTask + + beforeEach(() => { + vi.clearAllMocks() + vi.mocked(validateToolUse).mockImplementation(() => undefined) + + mockTask = { + taskId: "test-task-id", + instanceId: "test-instance", + abort: false, + presentAssistantMessageLocked: false, + presentAssistantMessageHasPendingUpdates: false, + currentStreamingContentIndex: 0, + assistantMessageContent: [], + userMessageContent: [], + didCompleteReadingStream: false, + didRejectTool: false, + didAlreadyUseTool: false, + consecutiveMistakeCount: 0, + clineMessages: [], + api: { + getModel: () => ({ id: "test-model", info: {} }), + }, + recordToolUsage: vi.fn(), + recordToolError: vi.fn(), + toolRepetitionDetector: { + check: vi.fn().mockReturnValue({ allowExecution: true }), + }, + providerRef: { + deref: () => ({ + getState: vi.fn().mockResolvedValue({ + mode: "code", + customModes: [], + }), + }), + }, + say: vi.fn().mockResolvedValue(undefined), + ask: vi.fn().mockResolvedValue({ response: "yesButtonClicked" }), + pushToolResultToUserContent: vi.fn(), + } + + mockTask.pushToolResultToUserContent = vi + .fn() + .mockImplementation((toolResult: Anthropic.ToolResultBlockParam) => { + const existingResult = mockTask.userMessageContent.find( + (block) => block.type === "tool_result" && block.tool_use_id === toolResult.tool_use_id, + ) + if (existingResult) { + return false + } + mockTask.userMessageContent.push(toolResult) + return true + }) + }) + + it("records exactly one attempt for a normal static tool", async () => { + mockTask.assistantMessageContent = [ + { + type: "tool_use", + id: "call_1", + name: "read_file", + params: { path: "test.txt" }, + nativeArgs: { path: "test.txt" }, + partial: false, + }, + ] + + await presentAssistantMessage(mockTask as unknown as Task) + + expect(mockTask.recordToolUsage).toHaveBeenCalledTimes(1) + expect(mockTask.recordToolUsage).toHaveBeenCalledWith("read_file") + expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledTimes(1) + expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledWith(mockTask.taskId, "read_file") + }) + + it("records a valid dynamic mcp_ tool name as use_mcp_tool", async () => { + mockTask.assistantMessageContent = [ + { + type: "tool_use", + id: "call_mcp", + name: "mcp_my_server_do_thing", + params: {}, + nativeArgs: {}, + partial: false, + }, + ] + + await presentAssistantMessage(mockTask as unknown as Task) + + expect(mockTask.recordToolUsage).toHaveBeenCalledWith("use_mcp_tool") + expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledWith(mockTask.taskId, "use_mcp_tool") + }) + + it("records a malformed mcp_ tool name as use_mcp_tool, not the raw name", async () => { + mockTask.assistantMessageContent = [ + { + type: "tool_use", + id: "call_mcp_bad", + name: "mcp_", + params: {}, + nativeArgs: {}, + partial: false, + }, + ] + + await presentAssistantMessage(mockTask as unknown as Task) + + expect(mockTask.recordToolUsage).toHaveBeenCalledWith("use_mcp_tool") + expect(mockTask.recordToolUsage).not.toHaveBeenCalledWith("mcp_") + }) + + it("records a safe failure key without leaking the raw tool name when validation fails", async () => { + vi.mocked(validateToolUse).mockImplementation(() => { + throw new Error('Tool "read_file" is not allowed in this mode.') + }) + + mockTask.assistantMessageContent = [ + { + type: "tool_use", + id: "call_bad_mode", + name: "read_file", + params: { path: "test.txt" }, + nativeArgs: { path: "test.txt" }, + partial: false, + }, + ] + + await presentAssistantMessage(mockTask as unknown as Task) + + // A known static tool that fails validation still maps to its own name + // (it's a real, recognized tool - just disallowed here), never left raw/unmapped. + expect(mockTask.recordToolError).toHaveBeenCalledWith("read_file", expect.any(String)) + // No success attempt should be recorded for a validation failure. + expect(mockTask.recordToolUsage).not.toHaveBeenCalled() + }) + + it("records invalid_tool_call, not the raw name, when an arbitrary unknown tool fails validation", async () => { + vi.mocked(validateToolUse).mockImplementation(() => { + throw new Error('Unknown tool "totally_made_up_tool". This tool does not exist.') + }) + + mockTask.assistantMessageContent = [ + { + type: "tool_use", + id: "call_unknown", + name: "totally_made_up_tool", + params: {}, + nativeArgs: {}, + partial: false, + }, + ] + + await presentAssistantMessage(mockTask as unknown as Task) + + expect(mockTask.recordToolError).toHaveBeenCalledWith("invalid_tool_call", expect.any(String)) + expect(mockTask.recordToolError).not.toHaveBeenCalledWith("totally_made_up_tool", expect.anything()) + expect(mockTask.recordToolUsage).not.toHaveBeenCalled() + }) + + describe("native mcp_tool_use block", () => { + it("records exactly one attempt once the MCP tool's own validation passes", async () => { + mockTask.providerRef = { + deref: () => ({ + getState: vi.fn().mockResolvedValue({ + mode: "code", + customModes: [], + }), + getMcpHub: () => ({ + findServerNameBySanitizedName: () => "my_server", + getAllServers: () => [ + { + name: "my_server", + tools: [{ name: "do_thing", enabledForPrompt: true }], + }, + ], + }), + }), + } + + mockTask.assistantMessageContent = [ + { + type: "mcp_tool_use", + id: "call_native_mcp", + name: "mcp_my_server_do_thing", + serverName: "my_server", + toolName: "do_thing", + arguments: {}, + partial: false, + }, + ] + + await presentAssistantMessage(mockTask as unknown as Task) + + expect(mockTask.recordToolUsage).toHaveBeenCalledTimes(1) + expect(mockTask.recordToolUsage).toHaveBeenCalledWith("use_mcp_tool") + expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledTimes(1) + expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledWith(mockTask.taskId, "use_mcp_tool") + }) + + it("records no attempt when the MCP server is not on the mode's allow-list", async () => { + vi.mocked(getModeBySlug).mockReturnValueOnce({ + slug: "code", + name: "Code", + roleDefinition: "", + groups: [], + allowedMcpServers: ["some-other-server"], + }) + + mockTask.providerRef = { + deref: () => ({ + getState: vi.fn().mockResolvedValue({ + mode: "code", + customModes: [], + }), + getMcpHub: () => ({ + findServerNameBySanitizedName: () => "my_server", + }), + }), + } + + mockTask.assistantMessageContent = [ + { + type: "mcp_tool_use", + id: "call_native_mcp_disallowed", + name: "mcp_my_server_do_thing", + serverName: "my_server", + toolName: "do_thing", + arguments: {}, + partial: false, + }, + ] + + await presentAssistantMessage(mockTask as unknown as Task) + + // The server is disallowed, so the call never reaches onValidated: + // no success attempt is recorded for a call that was never permitted to execute. + expect(mockTask.recordToolUsage).not.toHaveBeenCalled() + expect(TelemetryService.instance.captureToolUsage).not.toHaveBeenCalled() + }) + }) +}) diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts index 8e6c8d9d9e..819cfffcd9 100644 --- a/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts +++ b/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts @@ -101,9 +101,10 @@ describe("presentAssistantMessage - Unknown Tool Handling", () => { // Verify consecutiveMistakeCount was incremented expect(mockTask.consecutiveMistakeCount).toBe(1) - // Verify recordToolError was called + // Verify recordToolError was called with a safe static key, never the + // raw model-controlled tool name. expect(mockTask.recordToolError).toHaveBeenCalledWith( - "nonexistent_tool", + "invalid_tool_call", expect.stringContaining("Unknown tool"), ) @@ -135,8 +136,9 @@ describe("presentAssistantMessage - Unknown Tool Handling", () => { // Verify consecutiveMistakeCount was incremented expect(mockTask.consecutiveMistakeCount).toBe(1) - // Verify recordToolError was called - expect(mockTask.recordToolError).toHaveBeenCalled() + // Verify recordToolError was called with a safe static key, never the + // raw model-reported tool name ("fake_tool_that_does_not_exist"). + expect(mockTask.recordToolError).toHaveBeenCalledWith("invalid_tool_call", expect.anything()) // Verify error message was shown to user expect(mockTask.say).toHaveBeenCalledWith("error", expect.anything()) diff --git a/src/core/assistant-message/__tests__/toTelemetryToolName.spec.ts b/src/core/assistant-message/__tests__/toTelemetryToolName.spec.ts new file mode 100644 index 0000000000..699cececdd --- /dev/null +++ b/src/core/assistant-message/__tests__/toTelemetryToolName.spec.ts @@ -0,0 +1,54 @@ +// npx vitest src/core/assistant-message/__tests__/toTelemetryToolName.spec.ts + +import { describe, it, expect, vi } from "vitest" + +// Only validateToolUse itself needs mocking (unused by toTelemetryToolName, +// but imported by the same module). isValidToolName is left as the real +// implementation: it has its own independent mcp_ prefix carve-out, and a +// hand-rolled mock allowlist here would mask a regression where +// toTelemetryToolName's ordering relative to isValidToolName changes. +vi.mock("../../tools/validateToolUse", async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + validateToolUse: vi.fn(), + } +}) + +import { toTelemetryToolName } from "../presentAssistantMessage" + +describe("toTelemetryToolName", () => { + it("maps a known static tool to its own name", () => { + expect(toTelemetryToolName("read_file", false, undefined)).toBe("read_file") + }) + + it("maps a registered custom tool to custom_tool", () => { + expect(toTelemetryToolName("my_custom_tool", true, undefined)).toBe("custom_tool") + }) + + it("maps a valid dynamic mcp_ tool name to use_mcp_tool", () => { + expect(toTelemetryToolName("mcp_my_server_do_thing", false, undefined)).toBe("use_mcp_tool") + }) + + it("maps a malformed mcp_ tool name to use_mcp_tool", () => { + expect(toTelemetryToolName("mcp_", false, undefined)).toBe("use_mcp_tool") + }) + + it("maps an arbitrary unknown tool name to invalid_tool_call", () => { + expect(toTelemetryToolName("drop_table_users", false, undefined)).toBe("invalid_tool_call") + }) + + it("never returns the raw name for an unrecognized tool", () => { + const raw = "'; DROP TABLE users; --" + const result = toTelemetryToolName(raw, false, undefined) + expect(result).not.toBe(raw) + expect(result).toBe("invalid_tool_call") + }) + + it("maps mcp_ names to use_mcp_tool against the real isValidToolName, not a mock allowlist", () => { + // isValidToolName is unmocked in this file (see the vi.mock factory above), + // so this exercises the real mcp_ prefix carve-out ordering rather than one + // a hand-rolled mock could silently keep agreeing with after a regression. + expect(toTelemetryToolName("mcp_my_server_do_thing", false, undefined)).toBe("use_mcp_tool") + }) +}) diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 12a5bfb4a2..7383a7a35a 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -41,6 +41,31 @@ import { codebaseSearchTool } from "../tools/CodebaseSearchTool" import { formatResponse } from "../prompts/responses" import { sanitizeToolUseId } from "../../utils/tool-id" +/** + * Maps a raw, potentially model-controlled tool name to a safe analytics key. + * Never returns the raw name unless it is a known static tool, so an + * arbitrary model-supplied string can never become a `toolsUsed` property key. + */ +export function toTelemetryToolName( + toolName: string, + isCustomTool: boolean, + experiments?: Record, +): ToolName { + if (isCustomTool) { + return "custom_tool" + } + + if (toolName.startsWith("mcp_")) { + return "use_mcp_tool" + } + + if (isValidToolName(toolName, experiments)) { + return toolName + } + + return "invalid_tool_call" +} + /** * Processes and presents assistant message content to the user interface. * @@ -233,10 +258,6 @@ export async function presentAssistantMessage(cline: Task) { pushToolResult(formatResponse.toolError(errorString)) } - if (!mcpBlock.partial) { - cline.recordToolUsage("use_mcp_tool") // Record as use_mcp_tool for analytics - } - // Resolve sanitized server name back to original server name // The serverName from parsing is sanitized (e.g., "my_server" from "my server") // We need the original name to find the actual MCP connection @@ -272,6 +293,12 @@ export async function presentAssistantMessage(cline: Task) { askApproval, handleError, pushToolResult, + onValidated: mcpBlock.partial + ? undefined + : () => { + cline.recordToolUsage("use_mcp_tool") + TelemetryService.instance.captureToolUsage(cline.taskId, "use_mcp_tool") + }, }) break } @@ -301,14 +328,10 @@ export async function presentAssistantMessage(cline: Task) { if (!toolCallId) { const errorMessage = "Invalid tool call: missing tool_use.id. XML tool calls are no longer supported. Remove any XML tool markup (e.g. ...) and use native tool calling instead." - // Record a tool error for visibility/telemetry. Use the reported tool name if present. + // Record a safe, static analytics key. Never key telemetry on the + // model-reported tool name, which is untrusted here. try { - if ( - typeof (cline as any).recordToolError === "function" && - typeof (block as any).name === "string" - ) { - ;(cline as any).recordToolError((block as any).name as ToolName, errorMessage) - } + cline.recordToolError("invalid_tool_call", errorMessage) } catch { // Best-effort only } @@ -424,7 +447,7 @@ export async function presentAssistantMessage(cline: Task) { cline.consecutiveMistakeCount++ try { - cline.recordToolError(block.name as ToolName, errorMessage) + cline.recordToolError(toTelemetryToolName(block.name, false, stateExperiments), errorMessage) } catch { // Best-effort only } @@ -552,22 +575,6 @@ export async function presentAssistantMessage(cline: Task) { pushToolResult(formatResponse.toolError(errorString)) } - if (!block.partial) { - // Check if this is a custom tool - if so, record as "custom_tool" (like MCP tools) - const isCustomTool = stateExperiments?.customTools && customToolRegistry.has(block.name) - const recordName = isCustomTool ? "custom_tool" : block.name - cline.recordToolUsage(recordName) - - // Track legacy format usage for read_file tool (for migration monitoring) - if (block.name === "read_file" && block.usedLegacyFormat) { - const modelInfo = cline.api.getModel() - TelemetryService.instance.captureEvent(TelemetryEventName.READ_FILE_LEGACY_FORMAT_USED, { - taskId: cline.taskId, - model: modelInfo?.id, - }) - } - } - // Validate tool use before execution - ONLY for complete (non-partial) blocks. // Validating partial blocks would cause validation errors to be thrown repeatedly // during streaming, pushing multiple tool_results for the same tool_use_id and @@ -580,6 +587,8 @@ export async function presentAssistantMessage(cline: Task) { const { resolveToolAlias } = await import("../prompts/tools/filter-tools-for-mode") const includedTools = rawIncludedTools?.map((tool) => resolveToolAlias(tool)) + const isCustomTool = Boolean(stateExperiments?.customTools && customToolRegistry.has(block.name)) + try { const toolRequirements = disabledTools?.reduce( @@ -617,8 +626,30 @@ export async function presentAssistantMessage(cline: Task) { is_error: true, }) + // Record a safe failure key. Never key telemetry on the raw, + // model-controlled tool name. + cline.recordToolError( + toTelemetryToolName(block.name, isCustomTool, stateExperiments), + error.message, + ) + break } + + // Validation passed: record exactly one attempt at this single + // central point. Individual tool handlers must not also record + // usage, or the attempt would be double-counted. + const recordName = toTelemetryToolName(block.name, isCustomTool, stateExperiments) + cline.recordToolUsage(recordName) + TelemetryService.instance.captureToolUsage(cline.taskId, recordName) + + // Track legacy format usage for read_file tool (for migration monitoring) + if (block.name === "read_file" && block.usedLegacyFormat) { + TelemetryService.instance.captureEvent(TelemetryEventName.READ_FILE_LEGACY_FORMAT_USED, { + taskId: cline.taskId, + model: modelInfo?.id, + }) + } } // Check for identical consecutive tool calls. @@ -901,7 +932,7 @@ export async function presentAssistantMessage(cline: Task) { // Not a custom tool - handle as unknown tool error const errorMessage = `Unknown tool "${block.name}". This tool does not exist. Please use one of the available tools.` cline.consecutiveMistakeCount++ - cline.recordToolError(block.name as ToolName, errorMessage) + cline.recordToolError("invalid_tool_call", errorMessage) await cline.say("error", t("tools:unknownToolError", { toolName: block.name })) // Push tool_result directly WITHOUT setting didAlreadyUseTool // This prevents the stream from being interrupted with "Response interrupted by tool use result" diff --git a/src/core/config/__tests__/ContextProxy.spec.ts b/src/core/config/__tests__/ContextProxy.spec.ts index 0a24141155..551591b0ed 100644 --- a/src/core/config/__tests__/ContextProxy.spec.ts +++ b/src/core/config/__tests__/ContextProxy.spec.ts @@ -4,6 +4,9 @@ import * as vscode from "vscode" import { GLOBAL_STATE_KEYS, SECRET_STATE_KEYS, GLOBAL_SECRET_KEYS } from "@roo-code/types" +import { clearAllMocks } from "../../../test-utils/reset" +import { makeExtensionContext, makeUri } from "../../../test-utils/vscode" + import { ContextProxy } from "../ContextProxy" vi.mock("vscode", () => ({ @@ -25,7 +28,7 @@ describe("ContextProxy", () => { beforeEach(async () => { // Reset mocks - vi.clearAllMocks() + clearAllMocks() // Mock globalState mockGlobalState = { @@ -42,14 +45,16 @@ describe("ContextProxy", () => { // Mock the extension context mockContext = { - globalState: mockGlobalState, - secrets: mockSecrets, - extensionUri: { path: "/test/extension" }, - extensionPath: "/test/extension", - globalStorageUri: { path: "/test/storage" }, - logUri: { path: "/test/logs" }, + ...makeExtensionContext({ + globalState: mockGlobalState, + secrets: mockSecrets, + extensionUri: makeUri("/test/extension"), + extensionPath: "/test/extension", + extensionMode: vscode.ExtensionMode.Development, + }), + globalStorageUri: makeUri("/test/storage"), + logUri: makeUri("/test/logs"), extension: { packageJSON: { version: "1.0.0" } }, - extensionMode: vscode.ExtensionMode.Development, } // Create proxy instance @@ -436,7 +441,7 @@ describe("ContextProxy", () => { describe("invalid apiProvider migration", () => { it("should clear Roo provider state during initialization", async () => { - vi.clearAllMocks() + clearAllMocks() mockGlobalState.get.mockImplementation((key: string) => { if (key === "apiProvider") { return "roo" @@ -460,7 +465,7 @@ describe("ContextProxy", () => { it("should clear invalid apiProvider from storage during initialization", async () => { // Reset and create a new proxy with invalid provider in state - vi.clearAllMocks() + clearAllMocks() mockGlobalState.get.mockImplementation((key: string) => { if (key === "apiProvider") { return "invalid-removed-provider" // Invalid/removed provider @@ -477,7 +482,7 @@ describe("ContextProxy", () => { it("should not clear retired apiProvider from storage during initialization", async () => { // Reset and create a new proxy with retired provider in state - vi.clearAllMocks() + clearAllMocks() mockGlobalState.get.mockImplementation((key: string) => { if (key === "apiProvider") { return "groq" // Retired provider @@ -496,7 +501,7 @@ describe("ContextProxy", () => { it("should not modify valid apiProvider during initialization", async () => { // Reset and create a new proxy with valid provider in state - vi.clearAllMocks() + clearAllMocks() mockGlobalState.get.mockImplementation((key: string) => { if (key === "apiProvider") { return "anthropic" // Valid provider @@ -517,7 +522,7 @@ describe("ContextProxy", () => { describe("getProviderSettings", () => { it("should sanitize invalid apiProvider before parsing", async () => { // Reset and create a new proxy with an unknown provider in state - vi.clearAllMocks() + clearAllMocks() mockGlobalState.get.mockImplementation((key: string) => { if (key === "apiProvider") { return "invalid-removed-provider" @@ -620,7 +625,7 @@ Output only the summary of the conversation so far, without any additional comme it("should clear old v1 default condensing prompt from customSupportPrompts during initialization", async () => { // Reset and create a new proxy with old v1 default prompt in customSupportPrompts - vi.clearAllMocks() + clearAllMocks() mockGlobalState.get.mockImplementation((key: string) => { if (key === "customSupportPrompts") { return { CONDENSE: OLD_V1_DEFAULT_CONDENSE_PROMPT } @@ -638,7 +643,7 @@ Output only the summary of the conversation so far, without any additional comme it("should preserve other custom prompts when clearing old v1 default", async () => { // Reset and create a new proxy with old v1 default plus other custom prompts - vi.clearAllMocks() + clearAllMocks() mockGlobalState.get.mockImplementation((key: string) => { if (key === "customSupportPrompts") { return { @@ -660,7 +665,7 @@ Output only the summary of the conversation so far, without any additional comme it("should not clear truly customized condensing prompts", async () => { // Reset and create a new proxy with a truly customized condensing prompt - vi.clearAllMocks() + clearAllMocks() const customPrompt = "My custom condensing instructions" mockGlobalState.get.mockImplementation((key: string) => { if (key === "customSupportPrompts") { @@ -682,7 +687,7 @@ Output only the summary of the conversation so far, without any additional comme it("should not fail when customSupportPrompts is undefined", async () => { // Reset and create a new proxy with no customSupportPrompts - vi.clearAllMocks() + clearAllMocks() mockGlobalState.get.mockReturnValue(undefined) const proxyWithNoPrompts = new ContextProxy(mockContext) diff --git a/src/core/config/__tests__/CustomModesManager.exportImportSlugChange.spec.ts b/src/core/config/__tests__/CustomModesManager.exportImportSlugChange.spec.ts index 3c9655ed08..b141006c85 100644 --- a/src/core/config/__tests__/CustomModesManager.exportImportSlugChange.spec.ts +++ b/src/core/config/__tests__/CustomModesManager.exportImportSlugChange.spec.ts @@ -13,6 +13,8 @@ import type { ModeConfig } from "@roo-code/types" import { fileExistsAtPath } from "../../../utils/fs" import { getWorkspacePath } from "../../../utils/path" import { GlobalFileNames } from "../../../shared/globalFileNames" +import { clearAllMocks } from "../../../test-utils/reset" +import { makeExtensionContext, makeUri } from "../../../test-utils/vscode" import { CustomModesManager } from "../CustomModesManager" @@ -53,20 +55,11 @@ describe("CustomModesManager - Export/Import with Slug Changes", () => { beforeEach(() => { mockOnUpdate = vi.fn() - mockContext = { - globalState: { - get: vi.fn(), - update: vi.fn(), - keys: vi.fn(() => []), - setKeysForSync: vi.fn(), - }, - globalStorageUri: { - fsPath: mockStoragePath, - }, - } as unknown as vscode.ExtensionContext + mockContext = makeExtensionContext({ globalStorageUri: makeUri(mockStoragePath) }) + mockContext.globalState.setKeysForSync = vi.fn() // mockWorkspacePath is now defined at the top level - mockWorkspaceFolders = [{ uri: { fsPath: mockWorkspacePath } }] + mockWorkspaceFolders = [{ uri: makeUri(mockWorkspacePath) }] ;(vscode.workspace as any).workspaceFolders = mockWorkspaceFolders ;(vscode.workspace.onDidSaveTextDocument as Mock).mockReturnValue({ dispose: vi.fn() }) ;(getWorkspacePath as Mock).mockReturnValue(mockWorkspacePath) @@ -90,7 +83,7 @@ describe("CustomModesManager - Export/Import with Slug Changes", () => { }) afterEach(() => { - vi.clearAllMocks() + clearAllMocks() }) describe("Export Path Calculation", () => { diff --git a/src/core/config/__tests__/CustomModesManager.spec.ts b/src/core/config/__tests__/CustomModesManager.spec.ts index 775ae64489..71ed60ee40 100644 --- a/src/core/config/__tests__/CustomModesManager.spec.ts +++ b/src/core/config/__tests__/CustomModesManager.spec.ts @@ -13,6 +13,8 @@ import type { ModeConfig } from "@roo-code/types" import { fileExistsAtPath } from "../../../utils/fs" import { getWorkspacePath, arePathsEqual } from "../../../utils/path" import { GlobalFileNames } from "../../../shared/globalFileNames" +import { clearAllMocks } from "../../../test-utils/reset" +import { makeExtensionContext, makeUri } from "../../../test-utils/vscode" import { CustomModesManager } from "../CustomModesManager" @@ -53,20 +55,11 @@ describe("CustomModesManager", () => { beforeEach(() => { mockOnUpdate = vi.fn() - mockContext = { - globalState: { - get: vi.fn(), - update: vi.fn(), - keys: vi.fn(() => []), - setKeysForSync: vi.fn(), - }, - globalStorageUri: { - fsPath: mockStoragePath, - }, - } as unknown as vscode.ExtensionContext + mockContext = makeExtensionContext({ globalStorageUri: makeUri(mockStoragePath) }) + mockContext.globalState.setKeysForSync = vi.fn() // mockWorkspacePath is now defined at the top level - mockWorkspaceFolders = [{ uri: { fsPath: mockWorkspacePath } }] + mockWorkspaceFolders = [{ uri: makeUri(mockWorkspacePath) }] ;(vscode.workspace as any).workspaceFolders = mockWorkspaceFolders ;(vscode.workspace.onDidSaveTextDocument as Mock).mockReturnValue({ dispose: vi.fn() }) ;(getWorkspacePath as Mock).mockReturnValue(mockWorkspacePath) @@ -90,7 +83,7 @@ describe("CustomModesManager", () => { }) afterEach(() => { - vi.clearAllMocks() + clearAllMocks() }) describe("getCustomModes", () => { diff --git a/src/core/config/__tests__/CustomModesManager.yamlEdgeCases.spec.ts b/src/core/config/__tests__/CustomModesManager.yamlEdgeCases.spec.ts index cad28ef94c..eab01eee41 100644 --- a/src/core/config/__tests__/CustomModesManager.yamlEdgeCases.spec.ts +++ b/src/core/config/__tests__/CustomModesManager.yamlEdgeCases.spec.ts @@ -13,6 +13,8 @@ import type { ModeConfig } from "@roo-code/types" import { fileExistsAtPath } from "../../../utils/fs" import { getWorkspacePath } from "../../../utils/path" import { GlobalFileNames } from "../../../shared/globalFileNames" +import { clearAllMocks } from "../../../test-utils/reset" +import { makeExtensionContext, makeUri } from "../../../test-utils/vscode" import { CustomModesManager } from "../CustomModesManager" @@ -52,19 +54,10 @@ describe("CustomModesManager - YAML Edge Cases", () => { beforeEach(() => { mockOnUpdate = vi.fn() - mockContext = { - globalState: { - get: vi.fn(), - update: vi.fn(), - keys: vi.fn(() => []), - setKeysForSync: vi.fn(), - }, - globalStorageUri: { - fsPath: mockStoragePath, - }, - } as unknown as vscode.ExtensionContext - - mockWorkspaceFolders = [{ uri: { fsPath: "/mock/workspace" } }] + mockContext = makeExtensionContext({ globalStorageUri: makeUri(mockStoragePath) }) + mockContext.globalState.setKeysForSync = vi.fn() + + mockWorkspaceFolders = [{ uri: makeUri("/mock/workspace") }] ;(vscode.workspace as any).workspaceFolders = mockWorkspaceFolders ;(vscode.workspace.onDidSaveTextDocument as Mock).mockReturnValue({ dispose: vi.fn() }) ;(getWorkspacePath as Mock).mockReturnValue("/mock/workspace") @@ -92,7 +85,7 @@ describe("CustomModesManager - YAML Edge Cases", () => { }) afterEach(() => { - vi.clearAllMocks() + clearAllMocks() }) describe("BOM (Byte Order Mark) handling", () => { diff --git a/src/core/config/__tests__/CustomModesSettings.spec.ts b/src/core/config/__tests__/CustomModesSettings.spec.ts index 186ef5aeba..b8e52963a1 100644 --- a/src/core/config/__tests__/CustomModesSettings.spec.ts +++ b/src/core/config/__tests__/CustomModesSettings.spec.ts @@ -51,7 +51,7 @@ describe("CustomModesSettings", () => { }) it("rejects missing customModes field", () => { - const invalidSettings = {} as any + const invalidSettings = {} expect(() => { customModesSettingsSchema.parse(invalidSettings) @@ -115,7 +115,7 @@ describe("CustomModesSettings", () => { customModes: [ { ...validMode, - groups: ["invalid_group"] as any, + groups: ["invalid_group"], }, ], } diff --git a/src/core/config/__tests__/ModeConfig.spec.ts b/src/core/config/__tests__/ModeConfig.spec.ts index 74cbc0c437..20379d751d 100644 --- a/src/core/config/__tests__/ModeConfig.spec.ts +++ b/src/core/config/__tests__/ModeConfig.spec.ts @@ -95,7 +95,7 @@ describe("CustomModeSchema", () => { slug: "123e4567-e89b-12d3-a456-426614174000", name: "Test Mode", roleDefinition: "Test role definition", - groups: ["not-a-valid-group"] as any, + groups: ["not-a-valid-group"], } expect(() => validateCustomMode(invalidGroupMode)).toThrow(ZodError) @@ -209,7 +209,7 @@ describe("CustomModeSchema", () => { test("rejects non-array group format", () => { const mode = { ...validBaseMode, - groups: "not-an-array" as any, + groups: "not-an-array", } expect(() => modeConfigSchema.parse(mode)).toThrow() @@ -218,7 +218,7 @@ describe("CustomModeSchema", () => { test("rejects invalid group names", () => { const mode = { ...validBaseMode, - groups: ["invalid_group"] as any, + groups: ["invalid_group"], } expect(() => modeConfigSchema.parse(mode)).toThrow() @@ -227,7 +227,7 @@ describe("CustomModeSchema", () => { test("rejects duplicate groups", () => { const mode = { ...validBaseMode, - groups: ["read", "read"] as any, + groups: ["read", "read"], } expect(() => modeConfigSchema.parse(mode)).toThrow("Duplicate groups are not allowed") @@ -236,12 +236,12 @@ describe("CustomModeSchema", () => { test("rejects null or undefined groups", () => { const modeWithNull = { ...validBaseMode, - groups: null as any, + groups: null, } const modeWithUndefined = { ...validBaseMode, - groups: undefined as any, + groups: undefined, } expect(() => modeConfigSchema.parse(modeWithNull)).toThrow() diff --git a/src/core/config/__tests__/ProviderSettingsManager.spec.ts b/src/core/config/__tests__/ProviderSettingsManager.spec.ts index 491ef3b18a..56d4a6951b 100644 --- a/src/core/config/__tests__/ProviderSettingsManager.spec.ts +++ b/src/core/config/__tests__/ProviderSettingsManager.spec.ts @@ -1,7 +1,5 @@ // npx vitest src/core/config/__tests__/ProviderSettingsManager.spec.ts -import { ExtensionContext } from "vscode" - import { OPEN_AI_CODEX_SERVICE_TIER_KEY, OpenAiCodexServiceTier, @@ -9,6 +7,9 @@ import { type ProviderSettings, } from "@roo-code/types" +import { clearAllMocks } from "../../../test-utils/reset" +import { makeExtensionContext } from "../../../test-utils/vscode" + import { ProviderSettingsManager, ProviderProfiles, SyncCloudProfilesResult } from "../ProviderSettingsManager" // `export()` builds an API handler per profile to read model capabilities. Mock @@ -38,7 +39,6 @@ vi.mock("../../../api", async () => { } }) -// Mock VSCode ExtensionContext const mockSecrets = { get: vi.fn(), store: vi.fn(), @@ -50,16 +50,17 @@ const mockGlobalState = { update: vi.fn(), } -const mockContext = { - secrets: mockSecrets, - globalState: mockGlobalState, -} as unknown as ExtensionContext +const baseContext = makeExtensionContext() +const mockContext = makeExtensionContext({ + secrets: { ...baseContext.secrets, ...mockSecrets }, + globalState: { ...baseContext.globalState, ...mockGlobalState }, +}) describe("ProviderSettingsManager", () => { let providerSettingsManager: ProviderSettingsManager beforeEach(() => { - vi.clearAllMocks() + clearAllMocks() // Reset all mock implementations to default successful behavior mockSecrets.get.mockResolvedValue(null) mockSecrets.store.mockResolvedValue(undefined) diff --git a/src/core/tools/ApplyPatchTool.ts b/src/core/tools/ApplyPatchTool.ts index 3f3295404b..56b2bf8909 100644 --- a/src/core/tools/ApplyPatchTool.ts +++ b/src/core/tools/ApplyPatchTool.ts @@ -131,7 +131,6 @@ export class ApplyPatchTool extends BaseTool<"apply_patch"> { } task.consecutiveMistakeCount = 0 - task.recordToolUsage("apply_patch") } catch (error) { await handleError("apply patch", error as Error) await task.diffViewProvider.reset() diff --git a/src/core/tools/BaseTool.ts b/src/core/tools/BaseTool.ts index 7d574068a9..83a733c7b0 100644 --- a/src/core/tools/BaseTool.ts +++ b/src/core/tools/BaseTool.ts @@ -108,9 +108,16 @@ export abstract class BaseTool { * * @param task - Task instance * @param block - ToolUse block from assistant message - * @param callbacks - Tool execution callbacks + * @param callbacks - Tool execution callbacks. Accepts any subclass-specific + * extension of ToolCallbacks (e.g. UseMcpToolCallbacks) so callers can pass + * extra fields through to a matching execute() override without widening + * the shared ToolCallbacks interface for every tool. */ - async handle(task: Task, block: ToolUse, callbacks: ToolCallbacks): Promise { + async handle( + task: Task, + block: ToolUse, + callbacks: TCallbacks, + ): Promise { // Handle partial messages if (block.partial) { try { diff --git a/src/core/tools/EditFileTool.ts b/src/core/tools/EditFileTool.ts index 2495a372bc..a7301e2ac9 100644 --- a/src/core/tools/EditFileTool.ts +++ b/src/core/tools/EditFileTool.ts @@ -463,8 +463,6 @@ export class EditFileTool extends BaseTool<"edit_file"> { pushToolResult(message + replacementInfo) - // Record successful tool usage and cleanup - task.recordToolUsage("edit_file") await task.diffViewProvider.reset() this.resetPartialState() diff --git a/src/core/tools/EditTool.ts b/src/core/tools/EditTool.ts index 79338c17a6..2ae8bf4ed0 100644 --- a/src/core/tools/EditTool.ts +++ b/src/core/tools/EditTool.ts @@ -229,8 +229,6 @@ export class EditTool extends BaseTool<"edit"> { const message = await task.diffViewProvider.pushToolWriteResult(task, task.cwd, false) pushToolResult(message) - // Record successful tool usage and cleanup - task.recordToolUsage("edit") await task.diffViewProvider.reset() this.resetPartialState() diff --git a/src/core/tools/GenerateImageTool.ts b/src/core/tools/GenerateImageTool.ts index c32fc85bf1..b036a71977 100644 --- a/src/core/tools/GenerateImageTool.ts +++ b/src/core/tools/GenerateImageTool.ts @@ -242,8 +242,6 @@ export class GenerateImageTool extends BaseTool<"generate_image"> { task.didEditFile = true - task.recordToolUsage("generate_image") - const fullImagePath = path.join(task.cwd, finalPath) let imageUri = provider?.convertToWebviewUri?.(fullImagePath) ?? vscode.Uri.file(fullImagePath).toString() diff --git a/src/core/tools/SearchReplaceTool.ts b/src/core/tools/SearchReplaceTool.ts index 2d8817364f..e29b124010 100644 --- a/src/core/tools/SearchReplaceTool.ts +++ b/src/core/tools/SearchReplaceTool.ts @@ -225,8 +225,6 @@ export class SearchReplaceTool extends BaseTool<"search_replace"> { const message = await task.diffViewProvider.pushToolWriteResult(task, task.cwd, false) pushToolResult(message) - // Record successful tool usage and cleanup - task.recordToolUsage("search_replace") await task.diffViewProvider.reset() this.resetPartialState() diff --git a/src/core/tools/UseMcpToolTool.ts b/src/core/tools/UseMcpToolTool.ts index da5ceb9403..9b2870060c 100644 --- a/src/core/tools/UseMcpToolTool.ts +++ b/src/core/tools/UseMcpToolTool.ts @@ -15,6 +15,18 @@ interface UseMcpToolParams { arguments?: Record } +/** + * Extends the shared callbacks with a hook invoked once this tool's own + * internal validation (params, tool existence, server allow-list) has + * passed, before side-effecting execution begins. Native MCP tool calls + * (presentAssistantMessage's mcp_tool_use branch) use this to defer telemetry + * attribution past validation that lives inside this class rather than the + * shared validateToolUse path. + */ +export interface UseMcpToolCallbacks extends ToolCallbacks { + onValidated?: () => void +} + type ValidationResult = | { isValid: false } | { @@ -27,8 +39,8 @@ type ValidationResult = export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> { readonly name = "use_mcp_tool" as const - async execute(params: UseMcpToolParams, task: Task, callbacks: ToolCallbacks): Promise { - const { askApproval, handleError, pushToolResult } = callbacks + async execute(params: UseMcpToolParams, task: Task, callbacks: UseMcpToolCallbacks): Promise { + const { askApproval, handleError, pushToolResult, onValidated } = callbacks try { // Validate parameters @@ -66,6 +78,10 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> { // Reset mistake count on successful validation task.consecutiveMistakeCount = 0 + // All internal validation (params, tool existence, server allow-list) has + // passed. Only now is it safe to attribute this as an attempted tool use. + onValidated?.() + // Get user approval const completeMessage = JSON.stringify({ type: "use_mcp_tool", diff --git a/src/core/tools/__tests__/applyPatchTool.execute.spec.ts b/src/core/tools/__tests__/applyPatchTool.execute.spec.ts new file mode 100644 index 0000000000..72ffb112bc --- /dev/null +++ b/src/core/tools/__tests__/applyPatchTool.execute.spec.ts @@ -0,0 +1,96 @@ +// npx vitest run core/tools/__tests__/applyPatchTool.execute.spec.ts + +import type { MockedFunction } from "vitest" + +import { fileExistsAtPath } from "../../../utils/fs" +import { isPathOutsideWorkspace } from "../../../utils/pathUtils" +import type { Task } from "../../task/Task" +import { ApplyPatchTool } from "../ApplyPatchTool" + +vi.mock("fs/promises", () => ({ + default: { + readFile: vi.fn().mockResolvedValue("original file content\n"), + unlink: vi.fn().mockResolvedValue(undefined), + }, +})) + +vi.mock("../../../utils/fs", () => ({ + fileExistsAtPath: vi.fn().mockResolvedValue(true), +})) + +vi.mock("../../../utils/pathUtils", () => ({ + isPathOutsideWorkspace: vi.fn().mockReturnValue(false), +})) + +describe("ApplyPatchTool.execute - delete file success path", () => { + const mockedFileExistsAtPath = fileExistsAtPath as MockedFunction + const mockedIsPathOutsideWorkspace = isPathOutsideWorkspace as MockedFunction + + let tool: ApplyPatchTool + let mockTask: Pick< + Task, + | "cwd" + | "consecutiveMistakeCount" + | "recordToolUsage" + | "recordToolError" + | "rooIgnoreController" + | "rooProtectedController" + | "say" + | "processQueuedMessages" + | "didEditFile" + > + let mockAskApproval: MockedFunction<(...args: unknown[]) => Promise> + let mockHandleError: MockedFunction<(...args: unknown[]) => Promise> + let mockPushToolResult: MockedFunction<(...args: unknown[]) => void> + + beforeEach(() => { + vi.clearAllMocks() + + mockedFileExistsAtPath.mockResolvedValue(true) + mockedIsPathOutsideWorkspace.mockReturnValue(false) + + mockTask = { + cwd: "/workspace/project", + consecutiveMistakeCount: 0, + recordToolUsage: vi.fn(), + recordToolError: vi.fn(), + rooIgnoreController: { + validateAccess: vi.fn().mockReturnValue(true), + } as unknown as Task["rooIgnoreController"], + rooProtectedController: { + isWriteProtected: vi.fn().mockReturnValue(false), + } as unknown as Task["rooProtectedController"], + say: vi.fn().mockResolvedValue(undefined), + processQueuedMessages: vi.fn(), + didEditFile: false, + } + + mockAskApproval = vi.fn().mockResolvedValue(true) + mockHandleError = vi.fn().mockResolvedValue(undefined) + mockPushToolResult = vi.fn() + + tool = new ApplyPatchTool() + }) + + it("deletes the file and records no local tool usage on success", async () => { + const patch = `*** Begin Patch +*** Delete File: src/obsolete.ts +*** End Patch` + + await tool.execute({ patch }, mockTask as Task, { + askApproval: mockAskApproval, + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockAskApproval).toHaveBeenCalled() + expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Successfully deleted")) + expect(mockTask.didEditFile).toBe(true) + expect(mockHandleError).not.toHaveBeenCalled() + + // Usage is recorded once at the central presentAssistantMessage + // attribution point, not locally by the handler. + expect(mockTask.recordToolUsage).not.toHaveBeenCalled() + expect(mockTask.recordToolError).not.toHaveBeenCalled() + }) +}) diff --git a/src/core/tools/__tests__/editFileTool.spec.ts b/src/core/tools/__tests__/editFileTool.spec.ts index 0e7343905e..1ff8d52a8d 100644 --- a/src/core/tools/__tests__/editFileTool.spec.ts +++ b/src/core/tools/__tests__/editFileTool.spec.ts @@ -560,7 +560,9 @@ describe("editFileTool", () => { expect(mockTask.diffViewProvider.saveChanges).toHaveBeenCalled() expect(mockTask.didEditFile).toBe(true) - expect(mockTask.recordToolUsage).toHaveBeenCalledWith("edit_file") + // Usage is recorded once at the central presentAssistantMessage + // attribution point, not locally by the handler. + expect(mockTask.recordToolUsage).not.toHaveBeenCalled() }) it("reverts changes when user rejects", async () => { diff --git a/src/core/tools/__tests__/editTool.spec.ts b/src/core/tools/__tests__/editTool.spec.ts index cbf635554c..a5f665b9e5 100644 --- a/src/core/tools/__tests__/editTool.spec.ts +++ b/src/core/tools/__tests__/editTool.spec.ts @@ -345,7 +345,9 @@ describe("editTool", () => { expect(mockTask.diffViewProvider.saveChanges).toHaveBeenCalled() expect(mockTask.didEditFile).toBe(true) - expect(mockTask.recordToolUsage).toHaveBeenCalledWith("edit") + // Usage is recorded once at the central presentAssistantMessage + // attribution point, not locally by the handler. + expect(mockTask.recordToolUsage).not.toHaveBeenCalled() }) it("reverts changes when user rejects", async () => { diff --git a/src/core/tools/__tests__/generateImageTool.test.ts b/src/core/tools/__tests__/generateImageTool.test.ts index ca12cc6d24..4436c55e87 100644 --- a/src/core/tools/__tests__/generateImageTool.test.ts +++ b/src/core/tools/__tests__/generateImageTool.test.ts @@ -163,6 +163,9 @@ describe("generateImageTool", () => { expect(mockAskApproval).toHaveBeenCalled() expect(mockGenerateImage).toHaveBeenCalled() expect(mockPushToolResult).toHaveBeenCalled() + // Usage is recorded once at the central presentAssistantMessage + // attribution point, not locally by the handler. + expect(mockCline.recordToolUsage).not.toHaveBeenCalled() }) it("should add cache-busting parameter to image URI", async () => { diff --git a/src/core/tools/__tests__/searchReplaceTool.spec.ts b/src/core/tools/__tests__/searchReplaceTool.spec.ts index ed08f9acbd..5cf10790d4 100644 --- a/src/core/tools/__tests__/searchReplaceTool.spec.ts +++ b/src/core/tools/__tests__/searchReplaceTool.spec.ts @@ -314,7 +314,9 @@ describe("searchReplaceTool", () => { expect(mockCline.diffViewProvider.saveChanges).toHaveBeenCalled() expect(mockCline.didEditFile).toBe(true) - expect(mockCline.recordToolUsage).toHaveBeenCalledWith("search_replace") + // Usage is recorded once at the central presentAssistantMessage + // attribution point, not locally by the handler. + expect(mockCline.recordToolUsage).not.toHaveBeenCalled() }) it("reverts changes when user rejects", async () => { diff --git a/src/eslint-suppressions.json b/src/eslint-suppressions.json index 53d5ba4441..0f51104b36 100644 --- a/src/eslint-suppressions.json +++ b/src/eslint-suppressions.json @@ -1,1762 +1,1752 @@ { - "__mocks__/fs/promises.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "__tests__/abandonSubtask.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 20 - } - }, - "__tests__/api-subtask.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "__tests__/delegation-concurrent.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 6 - } - }, - "__tests__/delegation-events.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 6 - } - }, - "__tests__/extension.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "__tests__/history-resume-delegation.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 72 - } - }, - "__tests__/migrateSettings.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "__tests__/nested-delegation-resume.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 20 - } - }, - "__tests__/new-task-delegation.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 9 - } - }, - "__tests__/provider-delegation.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 9 - } - }, - "activate/CodeActionProvider.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "activate/__tests__/CodeActionProvider.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 10 - } - }, - "activate/__tests__/handleUri.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 14 - } - }, - "activate/__tests__/registerCommands.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "activate/registerCodeActions.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "activate/registerCommands.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "activate/registerTerminalActions.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/__tests__/anthropic-vertex.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 31 - } - }, - "api/providers/__tests__/anthropic.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 9 - } - }, - "api/providers/__tests__/base-openai-compatible-provider-timeout.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "api/providers/__tests__/base-openai-compatible-provider.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/__tests__/base-provider.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "api/providers/__tests__/bedrock-custom-arn.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 12 - } - }, - "api/providers/__tests__/bedrock-error-handling.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 7 - } - }, - "api/providers/__tests__/bedrock-inference-profiles.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 27 - } - }, - "api/providers/__tests__/bedrock-native-tools.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 19 - } - }, - "api/providers/__tests__/bedrock-reasoning.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/__tests__/bedrock.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 38 - } - }, - "api/providers/__tests__/deepseek.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 12 - } - }, - "api/providers/__tests__/gemini-handler.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 18 - } - }, - "api/providers/__tests__/gemini.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 33 - } - }, - "api/providers/__tests__/kenari.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/__tests__/kimi-code.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/__tests__/lite-llm.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 36 - } - }, - "api/providers/__tests__/lm-studio-timeout.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "api/providers/__tests__/mimo.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 18 - } - }, - "api/providers/__tests__/minimax.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "api/providers/__tests__/moonshot.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 22 - } - }, - "api/providers/__tests__/native-ollama.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 21 - } - }, - "api/providers/__tests__/openai-codex-native-tool-calls.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 23 - } - }, - "api/providers/__tests__/openai-codex.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 16 - } - }, - "api/providers/__tests__/openai-native-tools.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 14 - } - }, - "api/providers/__tests__/openai-native-usage.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 25 - } - }, - "api/providers/__tests__/openai-native.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 74 - } - }, - "api/providers/__tests__/openai-timeout.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "api/providers/__tests__/openai.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "api/providers/__tests__/opencode-go.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "api/providers/__tests__/openrouter.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 34 - } - }, - "api/providers/__tests__/poe.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/__tests__/qwen-code-native-tools.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "api/providers/__tests__/sambanova.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "api/providers/__tests__/unbound.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "api/providers/__tests__/vercel-ai-gateway.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "api/providers/__tests__/vertex.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "api/providers/__tests__/zai.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "api/providers/anthropic-vertex.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "api/providers/anthropic.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/base-openai-compatible-provider.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 6 - } - }, - "api/providers/base-provider.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "api/providers/bedrock.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 34 - } - }, - "api/providers/deepseek.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "api/providers/fetchers/__tests__/kenari.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/fetchers/__tests__/kimi-code.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/fetchers/__tests__/lmstudio.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "api/providers/fetchers/__tests__/modelEndpointCache.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 9 - } - }, - "api/providers/fetchers/__tests__/moonshot.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/fetchers/__tests__/ollama.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 9 - } - }, - "api/providers/fetchers/__tests__/opencode-go.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/fetchers/__tests__/openrouter.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/fetchers/__tests__/zoo-gateway.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 8 - } - }, - "api/providers/fetchers/litellm.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/gemini.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "api/providers/lite-llm.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 8 - } - }, - "api/providers/lm-studio.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "api/providers/mimo.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "api/providers/moonshot.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/native-ollama.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "api/providers/openai-codex.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 35 - } - }, - "api/providers/openai-native.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 31 - } - }, - "api/providers/openai.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "api/providers/openrouter.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 6 - } - }, - "api/providers/poe.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "api/providers/qwen-code.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/providers/requesty.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "api/providers/unbound.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "api/providers/utils/__tests__/error-handler.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 35 - } - }, - "api/providers/utils/__tests__/image-generation.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 17 - } - }, - "api/providers/utils/__tests__/timeout-config.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "api/providers/utils/error-handler.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 7 - } - }, - "api/providers/xai.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 7 - } - }, - "api/transform/__tests__/ai-sdk.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "api/transform/__tests__/anthropic-filter.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 6 - } - }, - "api/transform/__tests__/bedrock-converse-format.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "api/transform/__tests__/gemini-format.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 6 - } - }, - "api/transform/__tests__/mistral-format.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "api/transform/__tests__/model-params.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 8 - } - }, - "api/transform/__tests__/openai-format.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 49 - } - }, - "api/transform/__tests__/r1-format.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 8 - } - }, - "api/transform/__tests__/reasoning.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/transform/__tests__/responses-api-input.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "api/transform/__tests__/responses-api-stream.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "api/transform/__tests__/zai-format.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "api/transform/ai-sdk.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "api/transform/bedrock-converse-format.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "api/transform/cache-strategy/__tests__/cache-strategy.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 17 - } - }, - "api/transform/caching/__tests__/gemini.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/transform/gemini-format.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "api/transform/openai-format.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 8 - } - }, - "api/transform/r1-format.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "api/transform/responses-api-input.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 6 - } - }, - "api/transform/responses-api-stream.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "api/transform/zai-format.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "core/assistant-message/NativeToolCallParser.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 16 - } - }, - "core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 8 - } - }, - "core/assistant-message/presentAssistantMessage.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 7 - } - }, - "core/auto-approval/__tests__/AutoApprovalHandler.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/checkpoints/__tests__/checkpoint.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "core/checkpoints/index.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/condense/__tests__/condense.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "core/condense/__tests__/foldedFileContext.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 10 - } - }, - "core/condense/__tests__/index.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 24 - } - }, - "core/config/ContextProxy.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/config/CustomModesManager.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "core/config/ProviderSettingsManager.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/config/__tests__/ContextProxy.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "core/config/__tests__/CustomModesManager.exportImportSlugChange.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "core/config/__tests__/CustomModesManager.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 12 - } - }, - "core/config/__tests__/CustomModesManager.yamlEdgeCases.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/config/__tests__/CustomModesSettings.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/config/__tests__/ModeConfig.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 6 - } - }, - "core/config/__tests__/ProviderSettingsManager.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/context-management/__tests__/context-management.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/context-tracking/__tests__/FileContextTracker.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "core/context/context-management/__tests__/context-error-handling.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/context/context-management/context-error-handling.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/diff/stats.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/environment/__tests__/getEnvironmentDetails.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/ignore/__tests__/RooIgnoreController.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/mentions/__tests__/processUserContentMentions.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/mentions/index.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/mentions/processUserContentMentions.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/message-manager/index.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 21 - } - }, - "core/message-manager/index.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/prompts/__tests__/add-custom-instructions.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/prompts/__tests__/get-prompt-component.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/prompts/__tests__/responses-rooignore.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 6 - } - }, - "core/prompts/__tests__/system-prompt.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "core/prompts/sections/__tests__/custom-instructions-global.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 18 - } - }, - "core/prompts/sections/__tests__/custom-instructions.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 55 - } - }, - "core/prompts/sections/__tests__/system-info.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 14 - } - }, - "core/prompts/tools/filter-tools-for-mode.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/prompts/tools/native-tools/__tests__/converters.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/prompts/tools/native-tools/__tests__/read_file.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "core/task-persistence/__tests__/TaskHistoryStore.crossInstance.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/task-persistence/__tests__/TaskHistoryStore.reconciliation.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/task-persistence/__tests__/TaskHistoryStore.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "core/task-persistence/__tests__/importRooTaskHistory.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/task-persistence/__tests__/taskMessages.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/task-persistence/apiMessages.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/task/Task.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 19 - } - }, - "core/task/__tests__/Task.dispose.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/task/__tests__/Task.persistence.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 8 - } - }, - "core/task/__tests__/Task.sticky-profile-race.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/task/__tests__/Task.throttle.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 24 - } - }, - "core/task/__tests__/apiConversationHistory.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 23 - } - }, - "core/task/__tests__/ask-clear-approval-buttons.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 19 - } - }, - "core/task/__tests__/ask-queued-message-drain.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 32 - } - }, - "core/task/__tests__/flushPendingToolResultsToHistory.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 14 - } - }, - "core/task/__tests__/grace-retry-errors.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "core/task/__tests__/grounding-sources.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "core/task/__tests__/native-tools-filtering.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/task/__tests__/new-task-isolation.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/task/__tests__/reasoning-preservation.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 20 - } - }, - "core/task/__tests__/task-tool-history.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/task/apiConversationHistory.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 8 - } - }, - "core/tools/BaseTool.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/tools/CodebaseSearchTool.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/tools/GenerateImageTool.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/tools/NewTaskTool.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/tools/ReadFileTool.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "core/tools/ToolRepetitionDetector.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/tools/UpdateTodoListTool.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/tools/UseMcpToolTool.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/tools/__tests__/ReadCommandOutputTool.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 16 - } - }, - "core/tools/__tests__/ToolRepetitionDetector.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/tools/__tests__/askFollowupQuestionTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 20 - } - }, - "core/tools/__tests__/attemptCompletionTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 11 - } - }, - "core/tools/__tests__/editFileTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "core/tools/__tests__/editTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/tools/__tests__/executeCommand.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 12 - } - }, - "core/tools/__tests__/executeCommandTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 8 - } - }, - "core/tools/__tests__/generateImageTool.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 9 - } - }, - "core/tools/__tests__/listFilesTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 10 - } - }, - "core/tools/__tests__/mcpServerRestriction.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 7 - } - }, - "core/tools/__tests__/newTaskTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 31 - } - }, - "core/tools/__tests__/readFileTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 98 - } - }, - "core/tools/__tests__/runSlashCommandTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/tools/__tests__/searchReplaceTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/tools/__tests__/skillTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/tools/__tests__/updateTodoListTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/tools/__tests__/useMcpToolTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 21 - } - }, - "core/tools/__tests__/validateToolUse.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/tools/__tests__/writeToFileTool.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "core/tools/helpers/toolResultFormatting.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/tools/validateToolUse.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "core/webview/ClineProvider.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 12 - } - }, - "core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 34 - } - }, - "core/webview/__tests__/ClineProvider.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 198 - } - }, - "core/webview/__tests__/ClineProvider.sticky-mode.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 37 - } - }, - "core/webview/__tests__/ClineProvider.sticky-profile.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 16 - } - }, - "core/webview/__tests__/ClineProvider.taskHistory.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 12 - } - }, - "core/webview/__tests__/checkpointRestoreHandler.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/webview/__tests__/diagnosticsHandler.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 7 - } - }, - "core/webview/__tests__/messageEnhancer.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 8 - } - }, - "core/webview/__tests__/skillsMessageHandler.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/webview/__tests__/telemetrySettingsTracking.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "core/webview/__tests__/webviewMessageHandler.checkpoint.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "core/webview/__tests__/webviewMessageHandler.cloudAuth.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/webview/__tests__/webviewMessageHandler.delete.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 9 - } - }, - "core/webview/__tests__/webviewMessageHandler.edit.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/webview/__tests__/webviewMessageHandler.importRooHistory.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 13 - } - }, - "core/webview/__tests__/webviewMessageHandler.readFileContent.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/webview/__tests__/webviewMessageHandler.routerModels.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 56 - } - }, - "core/webview/__tests__/webviewMessageHandler.searchFiles.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "core/webview/__tests__/webviewMessageHandler.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 35 - } - }, - "core/webview/messageEnhancer.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "core/webview/webviewMessageHandler.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "extension.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "extension/__tests__/api-delete-queued-message.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "extension/__tests__/api-send-message.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 7 - } - }, - "extension/api.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 9 - } - }, - "i18n/index.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "i18n/setup.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "integrations/editor/DiffViewProvider.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "integrations/editor/__tests__/DiffViewProvider.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 310 - } - }, - "integrations/editor/__tests__/EditorUtils.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "integrations/kimi-code/__tests__/oauth.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "integrations/misc/__tests__/export-markdown.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "integrations/misc/__tests__/extract-text.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "integrations/misc/__tests__/line-counter.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "integrations/misc/__tests__/open-file.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 11 - } - }, - "integrations/misc/__tests__/performance/processCarriageReturns.benchmark.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "integrations/terminal/__tests__/ExecaTerminalProcess.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 10 - } - }, - "integrations/terminal/__tests__/OutputInterceptor.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "integrations/terminal/__tests__/TerminalProcess.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 7 - } - }, - "integrations/terminal/__tests__/TerminalProcess.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "integrations/terminal/__tests__/TerminalProcessExec.bash.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "integrations/terminal/__tests__/TerminalProcessExec.cmd.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "integrations/terminal/__tests__/TerminalProcessExec.pwsh.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "integrations/terminal/__tests__/TerminalProcessInterpretExitCode.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "integrations/terminal/__tests__/TerminalProfile.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 35 - } - }, - "integrations/terminal/__tests__/TerminalRegistry.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 22 - } - }, - "integrations/terminal/__tests__/setupTerminalTests.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "integrations/terminal/__tests__/streamUtils/bashStream.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "integrations/terminal/__tests__/streamUtils/cmdStream.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "integrations/terminal/__tests__/streamUtils/index.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "integrations/terminal/__tests__/streamUtils/pwshStream.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "integrations/theme/getTheme.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 9 - } - }, - "services/__tests__/zoo-code-auth.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "services/checkpoints/__tests__/ShadowCheckpointService.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "services/code-index/__tests__/config-manager.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "services/code-index/__tests__/manager.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 89 - } - }, - "services/code-index/__tests__/orchestrator.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 25 - } - }, - "services/code-index/__tests__/service-factory.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 43 - } - }, - "services/code-index/embedders/__tests__/bedrock.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 7 - } - }, - "services/code-index/embedders/__tests__/gemini.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "services/code-index/embedders/__tests__/mistral.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "services/code-index/embedders/__tests__/ollama.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "services/code-index/embedders/__tests__/openai-compatible-rate-limit.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 15 - } - }, - "services/code-index/embedders/__tests__/openai-compatible.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 28 - } - }, - "services/code-index/embedders/__tests__/openai.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 10 - } - }, - "services/code-index/embedders/__tests__/openrouter.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "services/code-index/embedders/__tests__/vercel-ai-gateway.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "services/code-index/embedders/bedrock.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "services/code-index/embedders/ollama.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "services/code-index/embedders/openai-compatible.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "services/code-index/embedders/openai.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "services/code-index/embedders/openrouter.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "services/code-index/interfaces/vector-store.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "services/code-index/orchestrator.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "services/code-index/processors/__tests__/file-watcher.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 25 - } - }, - "services/code-index/processors/__tests__/parser.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 22 - } - }, - "services/code-index/processors/__tests__/scanner.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 26 - } - }, - "services/code-index/processors/file-watcher.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "services/code-index/processors/scanner.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "services/code-index/semble/__tests__/provider.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 12 - } - }, - "services/code-index/semble/__tests__/semble-cli.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "services/code-index/semble/__tests__/semble-downloader.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 61 - } - }, - "services/code-index/semble/provider.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "services/code-index/semble/semble-cli.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "services/code-index/shared/__tests__/validation-helpers.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "services/code-index/shared/validation-helpers.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "services/code-index/vector-store/__tests__/qdrant-client.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 87 - } - }, - "services/code-index/vector-store/qdrant-client.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "services/glob/__tests__/gitignore-integration.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "services/glob/__tests__/gitignore-test.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "services/glob/__tests__/list-files-limit.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 6 - } - }, - "services/glob/__tests__/list-files.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 22 - } - }, - "services/marketplace/MarketplaceManager.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "services/marketplace/SimpleInstaller.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 7 - } - }, - "services/marketplace/__tests__/MarketplaceManager.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "services/marketplace/__tests__/SimpleInstaller.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 17 - } - }, - "services/marketplace/__tests__/marketplace-setting-check.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "services/mcp/McpHub.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 10 - } - }, - "services/mcp/McpOAuthClientProvider.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "services/mcp/McpServerManager.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "services/mcp/__tests__/McpHub.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 150 - } - }, - "services/mcp/__tests__/McpOAuthClientProvider.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 20 - } - }, - "services/mcp/__tests__/SecretStorageService.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "services/mcp/utils/__tests__/callbackServer.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "services/mcp/utils/__tests__/oauth.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 9 - } - }, - "services/mcp/utils/callbackServer.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "services/mcp/utils/oauth.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "services/mdm/__tests__/MdmService.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "services/ripgrep/__tests__/diagnostic.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "services/roo-config/__tests__/index.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 20 - } - }, - "services/roo-config/index.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "services/rules/__tests__/rules.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "services/search/__tests__/file-search.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "services/skills/__tests__/SkillsManager.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "services/tree-sitter/__tests__/helpers.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "services/tree-sitter/__tests__/markdownParser.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "shared/__tests__/api.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 8 - } - }, - "shared/__tests__/embeddingModels.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "shared/__tests__/modes-empty-prompt-component.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "shared/api.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "shared/checkExistApiConfig.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "shared/cost.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "shared/parse-command.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "shared/support-prompt.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 5 - } - }, - "shared/tools.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "shared/utils/__tests__/requesty.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "utils/__tests__/autoImportSettings.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 16 - } - }, - "utils/__tests__/enhance-prompt.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 6 - } - }, - "utils/__tests__/git.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 95 - } - }, - "utils/__tests__/json-schema.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 3 - } - }, - "utils/__tests__/migrateSettings.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "utils/__tests__/outputChannelLogger.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "utils/__tests__/safeWriteJson.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 27 - } - }, - "utils/__tests__/shell.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 46 - } - }, - "utils/__tests__/storage.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 25 - } - }, - "utils/__tests__/tiktoken.spec.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "utils/config.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "utils/export.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 2 - } - }, - "utils/safeWriteJson.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 4 - } - }, - "utils/tts.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, - "vitest.setup.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - } -} + "__mocks__/fs/promises.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "__tests__/abandonSubtask.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 20 + } + }, + "__tests__/api-subtask.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "__tests__/delegation-concurrent.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 6 + } + }, + "__tests__/delegation-events.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 6 + } + }, + "__tests__/extension.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "__tests__/history-resume-delegation.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 72 + } + }, + "__tests__/migrateSettings.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "__tests__/nested-delegation-resume.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 20 + } + }, + "__tests__/new-task-delegation.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 9 + } + }, + "__tests__/provider-delegation.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 9 + } + }, + "activate/CodeActionProvider.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "activate/__tests__/CodeActionProvider.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 10 + } + }, + "activate/__tests__/handleUri.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 14 + } + }, + "activate/__tests__/registerCommands.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "activate/registerCodeActions.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "activate/registerCommands.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "activate/registerTerminalActions.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/__tests__/anthropic-vertex.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 31 + } + }, + "api/providers/__tests__/anthropic.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 9 + } + }, + "api/providers/__tests__/base-openai-compatible-provider-timeout.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "api/providers/__tests__/base-openai-compatible-provider.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/__tests__/base-provider.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "api/providers/__tests__/bedrock-custom-arn.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 12 + } + }, + "api/providers/__tests__/bedrock-error-handling.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 7 + } + }, + "api/providers/__tests__/bedrock-inference-profiles.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 27 + } + }, + "api/providers/__tests__/bedrock-native-tools.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 19 + } + }, + "api/providers/__tests__/bedrock-reasoning.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/__tests__/bedrock.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 38 + } + }, + "api/providers/__tests__/deepseek.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 12 + } + }, + "api/providers/__tests__/gemini-handler.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 18 + } + }, + "api/providers/__tests__/gemini.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 33 + } + }, + "api/providers/__tests__/kenari.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/__tests__/kimi-code.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/__tests__/lite-llm.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 36 + } + }, + "api/providers/__tests__/lm-studio-timeout.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "api/providers/__tests__/mimo.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 18 + } + }, + "api/providers/__tests__/minimax.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "api/providers/__tests__/moonshot.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 22 + } + }, + "api/providers/__tests__/native-ollama.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 21 + } + }, + "api/providers/__tests__/openai-codex-native-tool-calls.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 23 + } + }, + "api/providers/__tests__/openai-codex.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 16 + } + }, + "api/providers/__tests__/openai-native-tools.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 14 + } + }, + "api/providers/__tests__/openai-native-usage.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 25 + } + }, + "api/providers/__tests__/openai-native.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 74 + } + }, + "api/providers/__tests__/openai-timeout.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "api/providers/__tests__/openai.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "api/providers/__tests__/opencode-go.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "api/providers/__tests__/openrouter.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 34 + } + }, + "api/providers/__tests__/poe.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/__tests__/qwen-code-native-tools.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "api/providers/__tests__/sambanova.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "api/providers/__tests__/unbound.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "api/providers/__tests__/vercel-ai-gateway.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "api/providers/__tests__/vertex.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "api/providers/__tests__/zai.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "api/providers/anthropic-vertex.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "api/providers/anthropic.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/base-openai-compatible-provider.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 6 + } + }, + "api/providers/base-provider.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "api/providers/bedrock.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 34 + } + }, + "api/providers/deepseek.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "api/providers/fetchers/__tests__/kenari.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/fetchers/__tests__/kimi-code.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/fetchers/__tests__/lmstudio.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "api/providers/fetchers/__tests__/modelEndpointCache.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 9 + } + }, + "api/providers/fetchers/__tests__/moonshot.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/fetchers/__tests__/ollama.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 9 + } + }, + "api/providers/fetchers/__tests__/opencode-go.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/fetchers/__tests__/openrouter.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "api/providers/fetchers/__tests__/vercel-ai-gateway.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/fetchers/__tests__/zoo-gateway.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 8 + } + }, + "api/providers/fetchers/litellm.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/gemini.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "api/providers/lite-llm.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 8 + } + }, + "api/providers/lm-studio.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "api/providers/mimo.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "api/providers/moonshot.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/native-ollama.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "api/providers/openai-codex.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 35 + } + }, + "api/providers/openai-native.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 31 + } + }, + "api/providers/openai.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "api/providers/openrouter.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 6 + } + }, + "api/providers/poe.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "api/providers/qwen-code.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/providers/requesty.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "api/providers/unbound.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "api/providers/utils/__tests__/error-handler.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 35 + } + }, + "api/providers/utils/__tests__/image-generation.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 17 + } + }, + "api/providers/utils/__tests__/timeout-config.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "api/providers/utils/error-handler.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 7 + } + }, + "api/providers/xai.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 7 + } + }, + "api/transform/__tests__/ai-sdk.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "api/transform/__tests__/anthropic-filter.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 6 + } + }, + "api/transform/__tests__/bedrock-converse-format.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "api/transform/__tests__/gemini-format.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 6 + } + }, + "api/transform/__tests__/mistral-format.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "api/transform/__tests__/model-params.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 8 + } + }, + "api/transform/__tests__/openai-format.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 49 + } + }, + "api/transform/__tests__/r1-format.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 8 + } + }, + "api/transform/__tests__/reasoning.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/transform/__tests__/responses-api-input.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "api/transform/__tests__/responses-api-stream.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "api/transform/__tests__/zai-format.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "api/transform/ai-sdk.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "api/transform/bedrock-converse-format.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "api/transform/cache-strategy/__tests__/cache-strategy.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 17 + } + }, + "api/transform/caching/__tests__/gemini.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/transform/gemini-format.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "api/transform/openai-format.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 8 + } + }, + "api/transform/r1-format.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "api/transform/responses-api-input.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 6 + } + }, + "api/transform/responses-api-stream.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "api/transform/zai-format.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "core/assistant-message/NativeToolCallParser.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 16 + } + }, + "core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 8 + } + }, + "core/assistant-message/presentAssistantMessage.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/auto-approval/__tests__/AutoApprovalHandler.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/checkpoints/__tests__/checkpoint.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "core/checkpoints/index.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/condense/__tests__/condense.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "core/condense/__tests__/foldedFileContext.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 10 + } + }, + "core/condense/__tests__/index.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 24 + } + }, + "core/config/ContextProxy.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/config/CustomModesManager.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "core/config/ProviderSettingsManager.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/config/__tests__/ContextProxy.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "core/config/__tests__/CustomModesManager.exportImportSlugChange.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "core/config/__tests__/CustomModesManager.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 12 + } + }, + "core/config/__tests__/CustomModesManager.yamlEdgeCases.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/config/__tests__/ProviderSettingsManager.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/context-management/__tests__/context-management.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/context-tracking/__tests__/FileContextTracker.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "core/context/context-management/__tests__/context-error-handling.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/context/context-management/context-error-handling.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/diff/stats.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/environment/__tests__/getEnvironmentDetails.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/ignore/__tests__/RooIgnoreController.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/mentions/__tests__/processUserContentMentions.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/mentions/index.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/mentions/processUserContentMentions.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/message-manager/index.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 21 + } + }, + "core/message-manager/index.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/prompts/__tests__/add-custom-instructions.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/prompts/__tests__/get-prompt-component.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/prompts/__tests__/responses-rooignore.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 6 + } + }, + "core/prompts/__tests__/system-prompt.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "core/prompts/sections/__tests__/custom-instructions-global.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 18 + } + }, + "core/prompts/sections/__tests__/custom-instructions.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 55 + } + }, + "core/prompts/sections/__tests__/system-info.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 14 + } + }, + "core/prompts/tools/filter-tools-for-mode.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/prompts/tools/native-tools/__tests__/converters.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/prompts/tools/native-tools/__tests__/read_file.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "core/task-persistence/__tests__/TaskHistoryStore.crossInstance.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/task-persistence/__tests__/TaskHistoryStore.reconciliation.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/task-persistence/__tests__/TaskHistoryStore.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "core/task-persistence/__tests__/importRooTaskHistory.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/task-persistence/__tests__/taskMessages.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/task-persistence/apiMessages.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/task/Task.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 19 + } + }, + "core/task/__tests__/Task.dispose.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/task/__tests__/Task.persistence.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 8 + } + }, + "core/task/__tests__/Task.sticky-profile-race.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/task/__tests__/Task.throttle.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 24 + } + }, + "core/task/__tests__/apiConversationHistory.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 23 + } + }, + "core/task/__tests__/ask-clear-approval-buttons.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 19 + } + }, + "core/task/__tests__/ask-queued-message-drain.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 32 + } + }, + "core/task/__tests__/flushPendingToolResultsToHistory.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 14 + } + }, + "core/task/__tests__/grace-retry-errors.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "core/task/__tests__/grounding-sources.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "core/task/__tests__/native-tools-filtering.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/task/__tests__/new-task-isolation.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/task/__tests__/reasoning-preservation.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 20 + } + }, + "core/task/__tests__/task-tool-history.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/task/apiConversationHistory.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 8 + } + }, + "core/tools/BaseTool.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/tools/CodebaseSearchTool.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/tools/GenerateImageTool.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/tools/NewTaskTool.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/tools/ReadFileTool.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "core/tools/ToolRepetitionDetector.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/tools/UpdateTodoListTool.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/tools/UseMcpToolTool.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/tools/__tests__/ReadCommandOutputTool.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 16 + } + }, + "core/tools/__tests__/ToolRepetitionDetector.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/tools/__tests__/askFollowupQuestionTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 20 + } + }, + "core/tools/__tests__/attemptCompletionTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 11 + } + }, + "core/tools/__tests__/editFileTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "core/tools/__tests__/editTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/tools/__tests__/executeCommand.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 12 + } + }, + "core/tools/__tests__/executeCommandTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 8 + } + }, + "core/tools/__tests__/generateImageTool.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 9 + } + }, + "core/tools/__tests__/listFilesTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 10 + } + }, + "core/tools/__tests__/mcpServerRestriction.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 7 + } + }, + "core/tools/__tests__/newTaskTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 31 + } + }, + "core/tools/__tests__/readFileTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 98 + } + }, + "core/tools/__tests__/runSlashCommandTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/tools/__tests__/searchReplaceTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/tools/__tests__/skillTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/tools/__tests__/updateTodoListTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/tools/__tests__/useMcpToolTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 21 + } + }, + "core/tools/__tests__/validateToolUse.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/tools/__tests__/writeToFileTool.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "core/tools/helpers/toolResultFormatting.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/tools/validateToolUse.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "core/webview/ClineProvider.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 12 + } + }, + "core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 34 + } + }, + "core/webview/__tests__/ClineProvider.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 198 + } + }, + "core/webview/__tests__/ClineProvider.sticky-mode.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 37 + } + }, + "core/webview/__tests__/ClineProvider.sticky-profile.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 16 + } + }, + "core/webview/__tests__/ClineProvider.taskHistory.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 12 + } + }, + "core/webview/__tests__/checkpointRestoreHandler.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/webview/__tests__/diagnosticsHandler.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 7 + } + }, + "core/webview/__tests__/messageEnhancer.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 8 + } + }, + "core/webview/__tests__/skillsMessageHandler.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/webview/__tests__/telemetrySettingsTracking.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "core/webview/__tests__/webviewMessageHandler.checkpoint.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "core/webview/__tests__/webviewMessageHandler.cloudAuth.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/webview/__tests__/webviewMessageHandler.delete.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 9 + } + }, + "core/webview/__tests__/webviewMessageHandler.edit.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/webview/__tests__/webviewMessageHandler.importRooHistory.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 13 + } + }, + "core/webview/__tests__/webviewMessageHandler.readFileContent.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/webview/__tests__/webviewMessageHandler.routerModels.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 56 + } + }, + "core/webview/__tests__/webviewMessageHandler.searchFiles.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "core/webview/__tests__/webviewMessageHandler.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 35 + } + }, + "core/webview/messageEnhancer.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "core/webview/webviewMessageHandler.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "extension.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "extension/__tests__/api-delete-queued-message.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "extension/__tests__/api-send-message.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 7 + } + }, + "extension/api.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 9 + } + }, + "i18n/index.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "i18n/setup.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "integrations/editor/DiffViewProvider.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "integrations/editor/__tests__/DiffViewProvider.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 310 + } + }, + "integrations/editor/__tests__/EditorUtils.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "integrations/kimi-code/__tests__/oauth.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "integrations/misc/__tests__/export-markdown.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "integrations/misc/__tests__/extract-text.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "integrations/misc/__tests__/line-counter.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "integrations/misc/__tests__/open-file.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 11 + } + }, + "integrations/misc/__tests__/performance/processCarriageReturns.benchmark.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "integrations/terminal/__tests__/ExecaTerminalProcess.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 10 + } + }, + "integrations/terminal/__tests__/OutputInterceptor.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "integrations/terminal/__tests__/TerminalProcess.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 7 + } + }, + "integrations/terminal/__tests__/TerminalProcess.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "integrations/terminal/__tests__/TerminalProcessExec.bash.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "integrations/terminal/__tests__/TerminalProcessExec.cmd.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "integrations/terminal/__tests__/TerminalProcessExec.pwsh.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "integrations/terminal/__tests__/TerminalProcessInterpretExitCode.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "integrations/terminal/__tests__/TerminalProfile.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 35 + } + }, + "integrations/terminal/__tests__/TerminalRegistry.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 22 + } + }, + "integrations/terminal/__tests__/setupTerminalTests.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "integrations/terminal/__tests__/streamUtils/bashStream.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "integrations/terminal/__tests__/streamUtils/cmdStream.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "integrations/terminal/__tests__/streamUtils/index.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "integrations/terminal/__tests__/streamUtils/pwshStream.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "integrations/theme/getTheme.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 9 + } + }, + "services/__tests__/zoo-code-auth.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "services/checkpoints/__tests__/ShadowCheckpointService.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "services/code-index/__tests__/config-manager.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "services/code-index/__tests__/manager.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 89 + } + }, + "services/code-index/__tests__/orchestrator.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 25 + } + }, + "services/code-index/__tests__/service-factory.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 43 + } + }, + "services/code-index/embedders/__tests__/bedrock.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 7 + } + }, + "services/code-index/embedders/__tests__/gemini.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "services/code-index/embedders/__tests__/mistral.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "services/code-index/embedders/__tests__/ollama.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "services/code-index/embedders/__tests__/openai-compatible-rate-limit.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 15 + } + }, + "services/code-index/embedders/__tests__/openai-compatible.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 28 + } + }, + "services/code-index/embedders/__tests__/openai.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 10 + } + }, + "services/code-index/embedders/__tests__/openrouter.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "services/code-index/embedders/__tests__/vercel-ai-gateway.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "services/code-index/embedders/bedrock.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "services/code-index/embedders/ollama.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "services/code-index/embedders/openai-compatible.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "services/code-index/embedders/openai.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "services/code-index/embedders/openrouter.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "services/code-index/interfaces/vector-store.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "services/code-index/orchestrator.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "services/code-index/processors/__tests__/file-watcher.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 25 + } + }, + "services/code-index/processors/__tests__/parser.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 22 + } + }, + "services/code-index/processors/__tests__/scanner.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 26 + } + }, + "services/code-index/processors/file-watcher.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "services/code-index/processors/scanner.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "services/code-index/semble/__tests__/provider.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 12 + } + }, + "services/code-index/semble/__tests__/semble-cli.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "services/code-index/semble/__tests__/semble-downloader.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 61 + } + }, + "services/code-index/semble/provider.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "services/code-index/semble/semble-cli.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "services/code-index/shared/__tests__/validation-helpers.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "services/code-index/shared/validation-helpers.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "services/code-index/vector-store/__tests__/qdrant-client.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 87 + } + }, + "services/code-index/vector-store/qdrant-client.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "services/glob/__tests__/gitignore-integration.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "services/glob/__tests__/gitignore-test.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "services/glob/__tests__/list-files-limit.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 6 + } + }, + "services/glob/__tests__/list-files.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 22 + } + }, + "services/marketplace/MarketplaceManager.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "services/marketplace/SimpleInstaller.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 7 + } + }, + "services/marketplace/__tests__/MarketplaceManager.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "services/marketplace/__tests__/SimpleInstaller.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 17 + } + }, + "services/marketplace/__tests__/marketplace-setting-check.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "services/mcp/McpHub.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 10 + } + }, + "services/mcp/McpOAuthClientProvider.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "services/mcp/McpServerManager.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "services/mcp/__tests__/McpHub.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 150 + } + }, + "services/mcp/__tests__/McpOAuthClientProvider.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 20 + } + }, + "services/mcp/__tests__/SecretStorageService.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "services/mcp/utils/__tests__/callbackServer.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "services/mcp/utils/__tests__/oauth.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 9 + } + }, + "services/mcp/utils/callbackServer.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "services/mcp/utils/oauth.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "services/mdm/__tests__/MdmService.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "services/ripgrep/__tests__/diagnostic.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "services/roo-config/__tests__/index.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 20 + } + }, + "services/roo-config/index.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "services/rules/__tests__/rules.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "services/search/__tests__/file-search.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "services/skills/__tests__/SkillsManager.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "services/tree-sitter/__tests__/helpers.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "services/tree-sitter/__tests__/markdownParser.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "shared/__tests__/api.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 8 + } + }, + "shared/__tests__/embeddingModels.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "shared/__tests__/modes-empty-prompt-component.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "shared/api.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "shared/checkExistApiConfig.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "shared/cost.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "shared/parse-command.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "shared/support-prompt.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 5 + } + }, + "shared/tools.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "shared/utils/__tests__/requesty.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "utils/__tests__/autoImportSettings.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 16 + } + }, + "utils/__tests__/enhance-prompt.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 6 + } + }, + "utils/__tests__/git.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 95 + } + }, + "utils/__tests__/json-schema.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 3 + } + }, + "utils/__tests__/migrateSettings.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "utils/__tests__/outputChannelLogger.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "utils/__tests__/safeWriteJson.test.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 27 + } + }, + "utils/__tests__/shell.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 46 + } + }, + "utils/__tests__/storage.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 25 + } + }, + "utils/__tests__/tiktoken.spec.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "utils/config.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "utils/export.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 2 + } + }, + "utils/safeWriteJson.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 4 + } + }, + "utils/tts.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + }, + "vitest.setup.ts": { + "@typescript-eslint/no-explicit-any": { + "count": 1 + } + } +} \ No newline at end of file diff --git a/src/integrations/terminal/__tests__/OutputInterceptor.test.ts b/src/integrations/terminal/__tests__/OutputInterceptor.test.ts index ed308cff13..ace293e811 100644 --- a/src/integrations/terminal/__tests__/OutputInterceptor.test.ts +++ b/src/integrations/terminal/__tests__/OutputInterceptor.test.ts @@ -2,6 +2,7 @@ import * as fs from "fs" import * as path from "path" import { vi, describe, it, expect, beforeEach, afterEach } from "vitest" +import { clearAllMocks, restoreGlobals } from "../../../test-utils/reset" import { OutputInterceptor } from "../OutputInterceptor" import { TerminalOutputPreviewSize } from "@roo-code/types" @@ -30,7 +31,7 @@ describe("OutputInterceptor", () => { let storageDir: string beforeEach(() => { - vi.clearAllMocks() + clearAllMocks() storageDir = path.normalize("/tmp/test-storage") @@ -49,7 +50,7 @@ describe("OutputInterceptor", () => { }) afterEach(() => { - vi.restoreAllMocks() + restoreGlobals() }) describe("Buffering behavior", () => { diff --git a/src/integrations/terminal/__tests__/TerminalProcessExec.bash.spec.ts b/src/integrations/terminal/__tests__/TerminalProcessExec.bash.spec.ts index 8e9af919ea..487e6273d5 100644 --- a/src/integrations/terminal/__tests__/TerminalProcessExec.bash.spec.ts +++ b/src/integrations/terminal/__tests__/TerminalProcessExec.bash.spec.ts @@ -3,6 +3,7 @@ import * as vscode from "vscode" import { execSync } from "child_process" +import { clearAllMocks } from "../../../test-utils/reset" import { ExitCodeDetails } from "../types" import { TerminalProcess } from "../TerminalProcess" import { Terminal } from "../Terminal" @@ -298,7 +299,7 @@ describe("TerminalProcess with Bash Command Output", () => { beforeEach(() => { // Reset the terminals array before each test TerminalRegistry["terminals"] = [] - vi.clearAllMocks() + clearAllMocks() }) // Each test uses Bash-specific commands to test the same functionality diff --git a/src/integrations/terminal/__tests__/TerminalProcessExec.cmd.spec.ts b/src/integrations/terminal/__tests__/TerminalProcessExec.cmd.spec.ts index e129160731..2505cf2c4a 100644 --- a/src/integrations/terminal/__tests__/TerminalProcessExec.cmd.spec.ts +++ b/src/integrations/terminal/__tests__/TerminalProcessExec.cmd.spec.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode" +import { clearAllMocks } from "../../../test-utils/reset" import { ExitCodeDetails } from "../types" import { TerminalProcess } from "../TerminalProcess" import { Terminal } from "../Terminal" @@ -251,7 +252,7 @@ describePlatform("TerminalProcess with CMD Command Output", () => { beforeEach(() => { // Reset state between tests TerminalRegistry["terminals"] = [] - vi.clearAllMocks() + clearAllMocks() }) // Each test uses CMD-specific commands to test the same functionality diff --git a/src/integrations/terminal/__tests__/TerminalProcessExec.pwsh.spec.ts b/src/integrations/terminal/__tests__/TerminalProcessExec.pwsh.spec.ts index 6f82634110..fa9b0d0549 100644 --- a/src/integrations/terminal/__tests__/TerminalProcessExec.pwsh.spec.ts +++ b/src/integrations/terminal/__tests__/TerminalProcessExec.pwsh.spec.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode" +import { clearAllMocks } from "../../../test-utils/reset" import { ExitCodeDetails } from "../types" import { TerminalProcess } from "../TerminalProcess" import { Terminal } from "../Terminal" @@ -245,7 +246,7 @@ describePlatform("TerminalProcess with PowerShell Command Output", () => { beforeEach(() => { // Reset state between tests TerminalRegistry["terminals"] = [] - vi.clearAllMocks() + clearAllMocks() }) // Each test uses PowerShell-specific commands to test the same functionality diff --git a/src/integrations/terminal/__tests__/TerminalProfile.spec.ts b/src/integrations/terminal/__tests__/TerminalProfile.spec.ts index 00cfaa3bc9..2510b5b89d 100644 --- a/src/integrations/terminal/__tests__/TerminalProfile.spec.ts +++ b/src/integrations/terminal/__tests__/TerminalProfile.spec.ts @@ -4,6 +4,7 @@ import { existsSync } from "fs" import * as vscode from "vscode" +import { restoreGlobals } from "../../../test-utils/reset" import { Terminal } from "../Terminal" import { TerminalRegistry } from "../TerminalRegistry" import { ShellIntegrationManager } from "../ShellIntegrationManager" @@ -73,7 +74,7 @@ describe("Terminal VS Code terminal profile (#277)", () => { afterEach(() => { Terminal.setTerminalProfile(undefined) - vi.restoreAllMocks() + restoreGlobals() }) describe("getTerminalProfile / setTerminalProfile", () => { @@ -748,7 +749,7 @@ describe("Terminal VS Code terminal profile (#277)", () => { Terminal.setTerminalZdotdir(false) Terminal.setTerminalProfile(undefined) TerminalRegistry["terminals"] = [] - vi.restoreAllMocks() + restoreGlobals() }) it("sets ZDOTDIR when zdotdir is enabled and no profile is configured", () => { diff --git a/src/integrations/terminal/__tests__/TerminalRegistry.spec.ts b/src/integrations/terminal/__tests__/TerminalRegistry.spec.ts index dbfb362d52..f60c0d0722 100644 --- a/src/integrations/terminal/__tests__/TerminalRegistry.spec.ts +++ b/src/integrations/terminal/__tests__/TerminalRegistry.spec.ts @@ -1,6 +1,8 @@ // npx vitest run src/integrations/terminal/__tests__/TerminalRegistry.spec.ts import * as vscode from "vscode" + +import { restoreGlobals } from "../../../test-utils/reset" import { ExecaTerminal } from "../ExecaTerminal" import { ShellIntegrationManager } from "../ShellIntegrationManager" import { Terminal } from "../Terminal" @@ -44,7 +46,7 @@ describe("TerminalRegistry", () => { afterEach(() => { TerminalRegistry["terminals"] = [] Terminal.setTerminalProfile(undefined) - vi.restoreAllMocks() + restoreGlobals() }) describe("createTerminal", () => { diff --git a/src/services/code-index/embedders/__tests__/openai-compatible-rate-limit.spec.ts b/src/services/code-index/embedders/__tests__/openai-compatible-rate-limit.spec.ts index 7ec4324138..49086d675b 100644 --- a/src/services/code-index/embedders/__tests__/openai-compatible-rate-limit.spec.ts +++ b/src/services/code-index/embedders/__tests__/openai-compatible-rate-limit.spec.ts @@ -2,6 +2,7 @@ import type { MockedClass, MockedFunction } from "vitest" import { OpenAI } from "openai" import { OpenAICompatibleEmbedder } from "../openai-compatible" +import { clearAllMocks, restoreGlobals } from "../../../../test-utils/reset" // Mock the OpenAI SDK vi.mock("openai") @@ -39,7 +40,7 @@ describe("OpenAICompatibleEmbedder - Global Rate Limiting", () => { const testModelId = "text-embedding-3-small" beforeEach(() => { - vi.clearAllMocks() + clearAllMocks() vi.useFakeTimers() vi.spyOn(console, "warn").mockImplementation(function () {}) vi.spyOn(console, "error").mockImplementation(function () {}) @@ -69,7 +70,7 @@ describe("OpenAICompatibleEmbedder - Global Rate Limiting", () => { afterEach(() => { vi.useRealTimers() - vi.restoreAllMocks() + restoreGlobals() }) it("should apply global rate limiting across multiple batch requests", async () => { diff --git a/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts b/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts index c36ae48d0a..627c642432 100644 --- a/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts +++ b/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts @@ -2,6 +2,7 @@ import type { MockedClass, MockedFunction } from "vitest" import { OpenAI } from "openai" import { OpenAICompatibleEmbedder } from "../openai-compatible" import { MAX_ITEM_TOKENS, INITIAL_RETRY_DELAY_MS } from "../../constants" +import { clearAllMocks, restoreGlobals } from "../../../../test-utils/reset" // Mock the OpenAI SDK vitest.mock("openai") @@ -62,7 +63,7 @@ describe("OpenAICompatibleEmbedder", () => { const testModelId = "text-embedding-3-small" beforeEach(() => { - vitest.clearAllMocks() + clearAllMocks() vitest.spyOn(console, "warn").mockImplementation(function () {}) vitest.spyOn(console, "error").mockImplementation(function () {}) @@ -90,7 +91,7 @@ describe("OpenAICompatibleEmbedder", () => { }) afterEach(() => { - vitest.restoreAllMocks() + restoreGlobals() }) describe("constructor", () => { @@ -706,7 +707,7 @@ describe("OpenAICompatibleEmbedder", () => { } beforeEach(() => { - vitest.clearAllMocks() + clearAllMocks() ;(global.fetch as MockedFunction).mockReset() }) @@ -802,7 +803,7 @@ describe("OpenAICompatibleEmbedder", () => { expectEmbeddingValues(azureResult.embeddings[0], [0.1, 0.2, 0.3]) // Reset and test base URL (SDK) - vitest.clearAllMocks() + clearAllMocks() const baseEmbedder = new OpenAICompatibleEmbedder(baseUrl, testApiKey, testModelId) mockEmbeddingsCreate.mockResolvedValue({ data: [{ embedding: [0.4, 0.5, 0.6] }], @@ -961,7 +962,7 @@ describe("OpenAICompatibleEmbedder", () => { let mockFetch: MockedFunction beforeEach(() => { - vitest.clearAllMocks() + clearAllMocks() // Reset and re-assign the global fetch mock global.fetch = vitest.fn() mockFetch = global.fetch as MockedFunction diff --git a/src/services/code-index/embedders/__tests__/openai.spec.ts b/src/services/code-index/embedders/__tests__/openai.spec.ts index be37efb03b..af402f0766 100644 --- a/src/services/code-index/embedders/__tests__/openai.spec.ts +++ b/src/services/code-index/embedders/__tests__/openai.spec.ts @@ -3,6 +3,7 @@ import { OpenAI } from "openai" import { OpenAiEmbedder } from "../openai" import { MAX_ITEM_TOKENS, INITIAL_RETRY_DELAY_MS } from "../../constants" +import { clearAllMocks } from "../../../../test-utils/reset" // Mock the OpenAI SDK vitest.mock("openai") @@ -44,7 +45,7 @@ describe("OpenAiEmbedder", () => { let MockedOpenAI: MockedClass beforeEach(() => { - vitest.clearAllMocks() + clearAllMocks() consoleMocks.error.mockClear() consoleMocks.warn.mockClear() @@ -62,7 +63,7 @@ describe("OpenAiEmbedder", () => { }) afterEach(() => { - vitest.clearAllMocks() + clearAllMocks() }) describe("constructor", () => { diff --git a/src/services/code-index/embedders/__tests__/openrouter.spec.ts b/src/services/code-index/embedders/__tests__/openrouter.spec.ts index fc44002fbf..088e9c7185 100644 --- a/src/services/code-index/embedders/__tests__/openrouter.spec.ts +++ b/src/services/code-index/embedders/__tests__/openrouter.spec.ts @@ -3,6 +3,7 @@ import { describe, it, expect, beforeEach, vi } from "vitest" import { OpenAI } from "openai" import { OpenRouterEmbedder, OPENROUTER_DEFAULT_PROVIDER_NAME } from "../openrouter" import { getModelDimension, getDefaultModelId } from "../../../../shared/embeddingModels" +import { clearAllMocks, restoreGlobals } from "../../../../test-utils/reset" // Mock the OpenAI SDK vi.mock("openai") @@ -42,7 +43,7 @@ describe("OpenRouterEmbedder", () => { let mockOpenAIInstance: any beforeEach(() => { - vi.clearAllMocks() + clearAllMocks() vi.spyOn(console, "warn").mockImplementation(function () {}) vi.spyOn(console, "error").mockImplementation(function () {}) @@ -60,7 +61,7 @@ describe("OpenRouterEmbedder", () => { }) afterEach(() => { - vi.restoreAllMocks() + restoreGlobals() }) describe("constructor", () => { diff --git a/src/shared/tools.ts b/src/shared/tools.ts index d2dd9907b1..1a1fb03200 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -290,6 +290,7 @@ export const TOOL_DISPLAY_NAMES: Record = { skill: "load skill", generate_image: "generate images", custom_tool: "use custom tools", + invalid_tool_call: "invalid tool call", } as const // Define available tool groups. diff --git a/webview-ui/src/components/settings/__tests__/OpenAICompatible.visual.fixture.test.tsx b/webview-ui/src/components/settings/__tests__/OpenAICompatible.visual.fixture.test.tsx new file mode 100644 index 0000000000..c37d0fc244 --- /dev/null +++ b/webview-ui/src/components/settings/__tests__/OpenAICompatible.visual.fixture.test.tsx @@ -0,0 +1,39 @@ +import { render, screen } from "@/utils/test-utils" +import { OpenAICompatibleStrictModeFixture } from "./OpenAICompatible.visual.fixture" + +vi.mock("vscrui", () => ({ + Checkbox: ({ + children, + checked, + onChange, + }: { + children: React.ReactNode + checked?: boolean + onChange?: () => void + }) => ( + + ), +})) + +describe("OpenAICompatibleStrictModeFixture", () => { + it("renders the strict tool schemas toggle block", () => { + render() + expect(screen.getByTestId("strict-tool-schemas-block")).toBeInTheDocument() + }) + + it("renders the checkbox with checked state", () => { + render() + const checkbox = screen.getByRole("checkbox") as HTMLInputElement + expect(checkbox).toBeChecked() + }) + + it("renders the description text", () => { + render() + expect( + screen.getByText(/Enables strict mode for function tool schemas/), + ).toBeInTheDocument() + }) +}) diff --git a/webview-ui/src/components/settings/__tests__/OpenAICompatible.visual.fixture.tsx b/webview-ui/src/components/settings/__tests__/OpenAICompatible.visual.fixture.tsx new file mode 100644 index 0000000000..32b81b5b77 --- /dev/null +++ b/webview-ui/src/components/settings/__tests__/OpenAICompatible.visual.fixture.tsx @@ -0,0 +1,45 @@ +import React from "react" +import { Checkbox } from "vscrui" + +import { TranslationContext } from "@src/i18n/TranslationContext" +import { TooltipProvider } from "@src/components/ui" + +/** + * Visual fixture for the `openAiToolStrictMode` toggle added to the OpenAI + * Compatible provider in PR b05a-strict-reasoning-v2. + * + * The full `OpenAICompatible` component cannot be mounted in Playwright CT + * (its `@roo-code/types` barrel re-export chain hits a `z is not defined` + * bundling issue), so this fixture renders the exact strict-mode toggle + * block added by the PR: checkbox + description, in the checked state. + */ +export const OpenAICompatibleStrictModeFixture = () => ( + + ( + ({ + "settings:modelInfo.strictToolSchemas": "Strict tool schemas", + "settings:modelInfo.strictToolSchemasDescription": + "Enables strict mode for function tool schemas, ensuring tool outputs match the schema exactly. Some providers may not support strict mode. MCP tools are always kept non-strict regardless of this setting. This setting is saved per profile and also applies to other providers that use the OpenAI protocol within the same profile.", + }) as Record + )[key] ?? key, + i18n: null as unknown as typeof import("../../../i18n/setup").default, + }}> + +
+
+ {}}> + Strict tool schemas + +
+ Enables strict mode for function tool schemas, ensuring tool outputs match the schema exactly. + Some providers may not support strict mode. MCP tools are always kept non-strict regardless of + this setting. This setting is saved per profile and also applies to other providers that use + the OpenAI protocol within the same profile. +
+
+
+
+
+) diff --git a/webview-ui/src/components/settings/__tests__/OpenAICompatible.visual.tsx b/webview-ui/src/components/settings/__tests__/OpenAICompatible.visual.tsx new file mode 100644 index 0000000000..a9126a7afa --- /dev/null +++ b/webview-ui/src/components/settings/__tests__/OpenAICompatible.visual.tsx @@ -0,0 +1,19 @@ +import React from "react" + +import { expect, test } from "../../../../playwright/coverage-fixture" +import { OpenAICompatibleStrictModeFixture } from "./OpenAICompatible.visual.fixture" + +test("renders strict tool schemas toggle enabled in the VS Code dark theme", async ({ mount }) => { + const component = await mount() + + await component.evaluate(async () => { + await document.fonts.ready + await new Promise((resolve) => requestAnimationFrame(() => resolve())) + }) + + // Target the inner strict-mode block via data-testid (Playwright CT + // cannot locate a testid on the outermost mounted wrapper). + const strictBlock = component.getByTestId("strict-tool-schemas-block") + await expect(strictBlock).toBeVisible() + await expect(strictBlock).toHaveScreenshot("openai-compatible-strict-tool-schemas-dark.png") +}) diff --git a/webview-ui/src/components/settings/__tests__/__screenshots__/openai-compatible-strict-tool-schemas-dark.png b/webview-ui/src/components/settings/__tests__/__screenshots__/openai-compatible-strict-tool-schemas-dark.png new file mode 100644 index 0000000000..23f6fc148d Binary files /dev/null and b/webview-ui/src/components/settings/__tests__/__screenshots__/openai-compatible-strict-tool-schemas-dark.png differ diff --git a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx index 8b11c128c7..e095954255 100644 --- a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx +++ b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx @@ -162,6 +162,16 @@ export const OpenAICompatible = ({ onChange={handleInputChange("openAiStreamingEnabled", noTransform)}> {t("settings:modelInfo.enableStreaming")} +
+ + {t("settings:modelInfo.strictToolSchemas")} + +
+ {t("settings:modelInfo.strictToolSchemasDescription")} +
+
{{serviceName}}. Si no esteu segur de quin model triar, Zoo Code funciona millor amb {{defaultModelId}}. També podeu cercar \"free\" per a opcions gratuïtes actualment disponibles.", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 5a8c05551f..9ed1bda05e 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* Kostenlos bis zu {{count}} Anfragen pro Minute. Danach hängt die Abrechnung von der Prompt-Größe ab.", "pricingDetails": "Weitere Informationen finden Sie unter Preisdetails.", "billingEstimate": "* Die Abrechnung ist eine Schätzung - die genauen Kosten hängen von der Prompt-Größe ab." - } + }, + "strictToolSchemas": "Strikte Tool-Schemas", + "strictToolSchemasDescription": "Aktiviert den strikten Modus für Funktionstool-Schemas und stellt sicher, dass Tool-Ausgaben genau mit dem Schema übereinstimmen. Manche Provider unterstützen den strikten Modus möglicherweise nicht. MCP-Tools werden unabhängig von dieser Einstellung immer als nicht-strikt behandelt. Diese Einstellung wird pro Profil gespeichert und gilt auch für andere Provider, die das OpenAI-Protokoll im selben Profil verwenden." }, "modelPicker": { "automaticFetch": "Die Erweiterung ruft automatisch die neueste Liste der auf {{serviceName}} verfügbaren Modelle ab. Wenn du dir nicht sicher bist, welches Modell du wählen sollst, funktioniert Zoo Code am besten mit {{defaultModelId}}. Du kannst auch versuchen, nach \"kostenlos\" zu suchen, um die derzeit verfügbaren kostenlosen Optionen zu finden.", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 2aacc322f0..58c1c547d1 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -1044,6 +1044,8 @@ "enableR1FormatTips": "Must be enabled when using R1 models such as QWQ to prevent 400 errors", "useAzure": "Use Azure", "azureApiVersion": "Set Azure API version", + "strictToolSchemas": "Strict tool schemas", + "strictToolSchemasDescription": "Enables strict mode for function tool schemas, ensuring tool outputs match the schema exactly. Some providers may not support strict mode. MCP tools are always kept non-strict regardless of this setting. This setting is saved per profile and also applies to other providers that use the OpenAI protocol within the same profile.", "gemini": { "freeRequests": "* Free up to {{count}} requests per minute. After that, billing depends on prompt size.", "pricingDetails": "For more info, see pricing details.", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 3f99fc1b14..c630619e2f 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* Gratis hasta {{count}} solicitudes por minuto. Después de eso, la facturación depende del tamaño del prompt.", "pricingDetails": "Para más información, consulte los detalles de precios.", "billingEstimate": "* La facturación es una estimación - el costo exacto depende del tamaño del prompt." - } + }, + "strictToolSchemas": "Esquemas de herramientas estrictos", + "strictToolSchemasDescription": "Activa el modo estricto para los esquemas de funciones de herramientas, asegurando que las salidas de las herramientas coincidan exactamente con el esquema. Algunos proveedores pueden no soportar el modo estricto. Las herramientas MCP siempre se mantienen no estrictas independientemente de esta configuración. Esta configuración se guarda por perfil y también se aplica a otros proveedores que utilicen el protocolo OpenAI dentro del mismo perfil." }, "modelPicker": { "automaticFetch": "La extensión obtiene automáticamente la lista más reciente de modelos disponibles en {{serviceName}}. Si no está seguro de qué modelo elegir, Zoo Code funciona mejor con {{defaultModelId}}. También puede buscar \"free\" para opciones sin costo actualmente disponibles.", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index ac0e6afb22..4aaa69ae19 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* Gratuit jusqu'à {{count}} requêtes par minute. Après cela, la facturation dépend de la taille du prompt.", "pricingDetails": "Pour plus d'informations, voir les détails de tarification.", "billingEstimate": "* La facturation est une estimation - le coût exact dépend de la taille du prompt." - } + }, + "strictToolSchemas": "Schémas d'outils stricts", + "strictToolSchemasDescription": "Active le mode strict pour les schémas de fonctions d'outils, garantissant que les sorties des outils correspondent exactement au schéma. Certains fournisseurs peuvent ne pas prendre en charge le mode strict. Les outils MCP sont toujours maintenus non stricts, quelle que soit ce paramètre. Ce paramètre est sauvegardé par profil et s'applique également aux autres fournisseurs utilisant le protocole OpenAI dans le même profil." }, "modelPicker": { "automaticFetch": "L'extension récupère automatiquement la liste la plus récente des modèles disponibles sur {{serviceName}}. Si vous ne savez pas quel modèle choisir, Zoo Code fonctionne mieux avec {{defaultModelId}}. Vous pouvez également rechercher \"free\" pour les options gratuites actuellement disponibles.", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index b720a5db83..b848453fc3 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* प्रति मिनट {{count}} अनुरोधों तक मुफ्त। उसके बाद, बिलिंग प्रॉम्प्ट आकार पर निर्भर करती है।", "pricingDetails": "अधिक जानकारी के लिए, मूल्य निर्धारण विवरण देखें।", "billingEstimate": "* बिलिंग एक अनुमान है - सटीक लागत प्रॉम्प्ट आकार पर निर्भर करती है।" - } + }, + "strictToolSchemas": "सख्त टूल स्कीमा", + "strictToolSchemasDescription": "फ़ंक्शन टूल स्कीमा के लिए सख्त मोड सक्षम करता है, जिससे टूल आउटपुट स्कीमा से बिल्कुल मेल खाते हैं। कुछ प्रदाता सख्त मोड का समर्थन नहीं कर सकते। MCP टूल इस सेटिंग की परवाह किए बिना हमेशा गैर-सख्त रखे जाते हैं। यह सेटिंग प्रोफ़ाइल के अनुसार सहेजी जाती है और उन अन्य प्रदाताओं पर भी लागू होती है जो उसी प्रोफ़ाइल में OpenAI प्रोटोकॉल का उपयोग करते हैं।" }, "modelPicker": { "automaticFetch": "एक्सटेंशन {{serviceName}} पर उपलब्ध मॉडलों की नवीनतम सूची स्वचालित रूप से प्राप्त करता है। यदि आप अनिश्चित हैं कि कौन सा मॉडल चुनना है, तो Zoo Code {{defaultModelId}} के साथ सबसे अच्छा काम करता है। आप वर्तमान में उपलब्ध निःशुल्क विकल्पों के लिए \"free\" भी खोज सकते हैं।", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index c46cc5acf1..c1bd7dc518 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* Gratis hingga {{count}} permintaan per menit. Setelah itu, penagihan tergantung pada ukuran prompt.", "pricingDetails": "Untuk info lebih lanjut, lihat detail harga.", "billingEstimate": "* Penagihan adalah estimasi - biaya sebenarnya tergantung pada ukuran prompt." - } + }, + "strictToolSchemas": "Skema tool yang ketat", + "strictToolSchemasDescription": "Mengaktifkan mode ketat untuk skema fungsi tool, memastikan output tool sesuai dengan skema secara tepat. Beberapa provider mungkin tidak mendukung mode ketat. Tool MCP selalu dijaga tetap tidak ketat terlepas dari pengaturan ini. Pengaturan ini disimpan per profil dan juga berlaku untuk provider lain yang menggunakan protokol OpenAI dalam profil yang sama." }, "modelPicker": { "automaticFetch": "Ekstensi secara otomatis mengambil daftar model terbaru yang tersedia di {{serviceName}}. Jika kamu tidak yakin model mana yang harus dipilih, Zoo Code bekerja terbaik dengan {{defaultModelId}}. Kamu juga dapat mencoba mencari \"free\" untuk opsi tanpa biaya yang saat ini tersedia.", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index ff00dacca7..14d3636f49 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* Gratuito fino a {{count}} richieste al minuto. Dopo, la fatturazione dipende dalla dimensione del prompt.", "pricingDetails": "Per maggiori informazioni, vedi i dettagli sui prezzi.", "billingEstimate": "* La fatturazione è una stima - il costo esatto dipende dalle dimensioni del prompt." - } + }, + "strictToolSchemas": "Schema strumenti rigidi", + "strictToolSchemasDescription": "Abilita la modalità rigida per gli schema delle funzioni degli strumenti, garantendo che gli output degli strumenti corrispondano esattamente allo schema. Alcuni provider potrebbero non supportare la modalità rigida. Gli strumenti MCP vengono sempre mantenuti non rigidi indipendentemente da questa impostazione. Questa impostazione viene salvata per profilo e si applica anche ad altri provider che utilizzano il protocollo OpenAI nello stesso profilo." }, "modelPicker": { "automaticFetch": "L'estensione recupera automaticamente l'elenco più recente dei modelli disponibili su {{serviceName}}. Se non sei sicuro di quale modello scegliere, Zoo Code funziona meglio con {{defaultModelId}}. Puoi anche cercare \"free\" per opzioni gratuite attualmente disponibili.", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index cdcb377cc9..a0fd4201bd 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* 1分間あたり{{count}}リクエストまで無料。それ以降は、プロンプトサイズに応じて課金されます。", "pricingDetails": "詳細は価格情報をご覧ください。", "billingEstimate": "* 課金は見積もりです - 正確な費用はプロンプトのサイズによって異なります。" - } + }, + "strictToolSchemas": "Strict tool schemas", + "strictToolSchemasDescription": "関数ツールスキーマに対してStrictモードを有効にし、ツールの出力がスキーマに正確に一致するようにします。一部のプロバイダーはStrictモードをサポートしていない場合があります。MCPツールはこの設定に関係なく常にnon-strictに保持されます。この設定はプロファイルごとに保存され、同じプロファイル内でOpenAIプロトコルを使用する他のプロバイダーにも適用されます。" }, "modelPicker": { "automaticFetch": "拡張機能は{{serviceName}}で利用可能な最新のモデルリストを自動的に取得します。どのモデルを選ぶべきか迷っている場合、Zoo Codeは{{defaultModelId}}で最適に動作します。また、「free」で検索すると、現在利用可能な無料オプションを見つけることができます。", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 4a7845ac2a..d90e50bc54 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* 분당 {{count}}개의 요청까지 무료. 이후에는 프롬프트 크기에 따라 요금이 부과됩니다.", "pricingDetails": "자세한 내용은 가격 정보를 참조하세요.", "billingEstimate": "* 요금은 추정치입니다 - 정확한 비용은 프롬프트 크기에 따라 달라집니다." - } + }, + "strictToolSchemas": "엄격한 도구 스키마", + "strictToolSchemasDescription": "함수 도구 스키마에 대한 엄격한 모드를 활성화하여 도구 출력이 스키마와 정확히 일치하도록 합니다. 일부 프로바이더는 엄격한 모드를 지원하지 않을 수 있습니다. MCP 도구는 이 설정에 관계없이 항상 non-strict로 유지됩니다. 이 설정은 프로필별로 저장되며 동일한 프로필 내에서 OpenAI 프로토콜을 사용하는 다른 프로바이더에도 적용됩니다." }, "modelPicker": { "automaticFetch": "확장 프로그램은 {{serviceName}}에서 사용 가능한 최신 모델 목록을 자동으로 가져옵니다. 어떤 모델을 선택해야 할지 확실하지 않다면, Zoo Code는 {{defaultModelId}}로 가장 잘 작동합니다. 현재 사용 가능한 무료 옵션을 찾으려면 \"free\"를 검색해 볼 수도 있습니다.", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 768018c3ef..4db1d3a14d 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* Gratis tot {{count}} verzoeken per minuut. Daarna is de prijs afhankelijk van de promptgrootte.", "pricingDetails": "Zie prijsdetails voor meer info.", "billingEstimate": "* Facturering is een schatting - de exacte kosten hangen af van de promptgrootte." - } + }, + "strictToolSchemas": "Strikte tool-schema's", + "strictToolSchemasDescription": "Schakelt de strikte modus in voor functie-tool-schema's en zorgt ervoor dat tool-uitvoer exact overeenkomt met het schema. Sommige providers ondersteunen de strikte modus mogelijk niet. MCP-tools worden altijd als niet-strikt behouden, ongeacht deze instelling. Deze instelling wordt per profiel opgeslagen en geldt ook voor andere providers die het OpenAI-protocol gebruiken binnen hetzelfde profiel." }, "modelPicker": { "automaticFetch": "De extensie haalt automatisch de nieuwste lijst met modellen op van {{serviceName}}. Weet je niet welk model je moet kiezen? Zoo Code werkt het beste met {{defaultModelId}}. Je kunt ook zoeken op 'free' voor gratis opties die nu beschikbaar zijn.", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 37b37df875..9218128758 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* Darmowe do {{count}} zapytań na minutę. Po tym, rozliczanie zależy od rozmiaru podpowiedzi.", "pricingDetails": "Więcej informacji znajdziesz w szczegółach cennika.", "billingEstimate": "* Rozliczenie jest szacunkowe - dokładny koszt zależy od rozmiaru podpowiedzi." - } + }, + "strictToolSchemas": "Ścisłe schematy narzędzi", + "strictToolSchemasDescription": "Włącza tryb ścisły dla schematów funkcji narzędzi, zapewniając, że wyjścia narzędzi dokładnie odpowiadają schematowi. Niektórzy dostawcy mogą nie obsługiwać trybu ścisłego. Narzędzia MCP zawsze pozostają nieścisłe niezależnie od tego ustawienia. To ustawienie jest zapisywane dla profilu i obowiązuje również dla innych dostawców korzystających z protokołu OpenAI w tym samym profilu." }, "modelPicker": { "automaticFetch": "Rozszerzenie automatycznie pobiera najnowszą listę modeli dostępnych w {{serviceName}}. Jeśli nie jesteś pewien, który model wybrać, Zoo Code działa najlepiej z {{defaultModelId}}. Możesz również wyszukać \"free\", aby znaleźć obecnie dostępne opcje bezpłatne.", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index c3b91d6b58..21a9218610 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* Gratuito até {{count}} requisições por minuto. Depois disso, a cobrança depende do tamanho do prompt.", "pricingDetails": "Para mais informações, consulte os detalhes de preços.", "billingEstimate": "* A cobrança é uma estimativa - o custo exato depende do tamanho do prompt." - } + }, + "strictToolSchemas": "Esquemas de ferramentas estritos", + "strictToolSchemasDescription": "Ativa o modo estrito para esquemas de funções de ferramentas, garantindo que as saídas das ferramentas correspondam exatamente ao esquema. Alguns provedores podem não suportar o modo estrito. Ferramentas MCP são sempre mantidas como não estritas, independente dessa configuração. Essa configuração é salva por perfil e também se aplica a outros provedores que usam o protocolo OpenAI dentro do mesmo perfil." }, "modelPicker": { "automaticFetch": "A extensão busca automaticamente a lista mais recente de modelos disponíveis em {{serviceName}}. Se você não tem certeza sobre qual modelo escolher, o Zoo Code funciona melhor com {{defaultModelId}}. Você também pode pesquisar por \"free\" para encontrar opções gratuitas atualmente disponíveis.", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index c428b31ec1..992c41645a 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* Бесплатно до {{count}} запросов в минуту. Далее тарификация зависит от размера подсказки.", "pricingDetails": "Подробнее о ценах.", "billingEstimate": "* Счёт — приблизительный, точная стоимость зависит от размера подсказки." - } + }, + "strictToolSchemas": "Строгие схемы инструментов", + "strictToolSchemasDescription": "Включает строгий режим для схем функций инструментов, гарантируя, что вывод инструментов точно соответствует схеме. Некоторые провайдеры могут не поддерживать строгий режим. Инструменты MCP всегда остаются нестрогими независимо от этой настройки. Эта настройка сохраняется для каждого профиля и также применяется к другим провайдерам, использующим протокол OpenAI в том же профиле." }, "modelPicker": { "automaticFetch": "Расширение автоматически получает актуальный список моделей на {{serviceName}}. Если не уверены, что выбрать, Zoo Code лучше всего работает с {{defaultModelId}}. Также попробуйте поискать \"free\" для бесплатных вариантов.", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index bccb1c08aa..a54592e4c9 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* Dakikada {{count}} isteğe kadar ücretsiz. Bundan sonra, ücretlendirme istem boyutuna bağlıdır.", "pricingDetails": "Daha fazla bilgi için fiyatlandırma ayrıntılarına bakın.", "billingEstimate": "* Ücretlendirme bir tahmindir - kesin maliyet istem boyutuna bağlıdır." - } + }, + "strictToolSchemas": "Sıkı tool şemaları", + "strictToolSchemasDescription": "Fonksiyon tool şemaları için sıkı modu etkinleştirir, tool çıktılarının şemayla tam olarak eşleşmesini sağlar. Bazı sağlayıcılar sıkı modu desteklemeyebilir. MCP tool'ları bu ayar ne olursa olsun her zaman sıkı olmayan şekilde tutulur. Bu ayar profile göre kaydedilir ve aynı profilde OpenAI protokolünü kullanan diğer sağlayıcılara da uygulanır." }, "modelPicker": { "automaticFetch": "Uzantı {{serviceName}} üzerinde bulunan mevcut modellerin en güncel listesini otomatik olarak alır. Hangi modeli seçeceğinizden emin değilseniz, Zoo Code {{defaultModelId}} ile en iyi şekilde çalışır. Şu anda mevcut olan ücretsiz seçenekleri bulmak için \"free\" araması da yapabilirsiniz.", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 6b20b9a9a9..f304e3f74e 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* Miễn phí đến {{count}} yêu cầu mỗi phút. Sau đó, thanh toán phụ thuộc vào kích thước lời nhắc.", "pricingDetails": "Để biết thêm thông tin, xem chi tiết giá.", "billingEstimate": "* Thanh toán là ước tính - chi phí chính xác phụ thuộc vào kích thước lời nhắc." - } + }, + "strictToolSchemas": "Schema công cụ nghiêm ngặt", + "strictToolSchemasDescription": "Bật chế độ nghiêm ngặt cho schema hàm công cụ, đảm bảo đầu ra của công cụ khớp chính xác với schema. Một số nhà cung cấp có thể không hỗ trợ chế độ nghiêm ngặt. Công cụ MCP luôn được giữ ở chế độ không nghiêm ngặt bất kể thiết lập này. Thiết lập này được lưu theo hồ sơ và cũng áp dụng cho các nhà cung cấp khác sử dụng giao thức OpenAI trong cùng hồ sơ." }, "modelPicker": { "automaticFetch": "Tiện ích mở rộng tự động lấy danh sách mới nhất các mô hình có sẵn trên {{serviceName}}. Nếu bạn không chắc chắn nên chọn mô hình nào, Zoo Code hoạt động tốt nhất với {{defaultModelId}}. Bạn cũng có thể thử tìm kiếm \"free\" cho các tùy chọn miễn phí hiện có.", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index c206c26108..fd098ae945 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -968,7 +968,9 @@ "freeRequests": "* 每分钟免费 {{count}} 个请求。之后,计费取决于提示大小。", "pricingDetails": "有关更多信息,请参阅定价详情。", "billingEstimate": "* 计费为估计值 - 具体费用取决于提示大小。" - } + }, + "strictToolSchemas": "严格工具 Schema", + "strictToolSchemasDescription": "为函数工具 Schema 启用严格模式,确保工具输出与 Schema 完全匹配。部分 Provider 可能不支持严格模式。MCP 工具无论此设置如何始终保持非严格状态。此设置按 Profile 保存,同时也适用于同一 Profile 中使用 OpenAI 协议的其他 Provider。" }, "modelPicker": { "automaticFetch": "自动获取 {{serviceName}} 上可用的最新模型列表。如果您不确定选择哪个模型,Zoo Code 与 {{defaultModelId}} 配合最佳。您还可以搜索\"free\"以查找当前可用的免费选项。", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 64eb5e0b29..ad6426ec29 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -995,7 +995,9 @@ "freeRequests": "* 每分鐘可免費使用 {{count}} 次請求,超過後將依提示詞大小計費。", "pricingDetails": "詳細資訊請參閱定價說明。", "billingEstimate": "* 費用為估算值 - 實際費用取決於提示大小。" - } + }, + "strictToolSchemas": "嚴格工具 Schema", + "strictToolSchemasDescription": "為函式工具 Schema 啟用嚴格模式,確保工具輸出與 Schema 完全匹配。部分 Provider 可能不支援嚴格模式。MCP 工具無論此設定如何始終保持非嚴格狀態。此設定依 Profile 儲存,同時也適用於同一 Profile 中使用 OpenAI 協定的其他 Provider。" }, "modelPicker": { "automaticFetch": "此擴充功能會自動從 {{serviceName}} 取得最新的可用模型清單。如果不確定要選哪個模型,建議使用 {{defaultModelId}},這是與 Zoo Code 最佳搭配的模型。您也可以搜尋「free」來檢視目前可用的免費選項。",