diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 1d4320493a..95c3ab1aeb 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1656,36 +1656,15 @@ export class Task extends EventEmitter implements TaskLike { const { contextTokens: prevContextTokens } = this.getTokenUsage() - // Build tools for condensing metadata (same tools used for normal API calls) - const provider = this.providerRef.deref() - let allTools: import("openai").default.Chat.ChatCompletionTool[] = [] - if (provider) { - const modelInfo = this.api.getModel().info - const toolsResult = await buildNativeToolsArrayWithRestrictions({ - provider, - cwd: this.cwd, - mode, - customModes: state?.customModes, - experiments: state?.experiments, - apiConfiguration, - disabledTools: state?.disabledTools, - modelInfo, - includeAllToolsWithRestrictions: false, - }) - allTools = toolsResult.tools - } - - // Build metadata with tools and taskId for the condensing API call + // Build metadata for the condensing API call. + // NOTE: We intentionally omit tools/tool_choice/parallelToolCalls here because + // the condensation flow converts all tool_use/tool_result blocks to plain text + // and the SUMMARY_PROMPT instructs the model not to call tools. Including tool + // definitions with tool_choice:"auto" but no tool blocks in messages causes + // 500 errors on some proxies (e.g. Roo Code Router). See #11743. const metadata: ApiHandlerCreateMessageMetadata = { mode, taskId: this.taskId, - ...(allTools.length > 0 - ? { - tools: allTools, - tool_choice: "auto", - parallelToolCalls: true, - } - : {}), } // Generate environment details to include in the condensed summary const environmentDetails = await getEnvironmentDetails(this, true) @@ -3845,35 +3824,15 @@ export class Task extends EventEmitter implements TaskLike { // Send condenseTaskContextStarted to show in-progress indicator await this.providerRef.deref()?.postMessageToWebview({ type: "condenseTaskContextStarted", text: this.taskId }) - // Build tools for condensing metadata (same tools used for normal API calls) - const provider = this.providerRef.deref() - let allTools: import("openai").default.Chat.ChatCompletionTool[] = [] - if (provider) { - const toolsResult = await buildNativeToolsArrayWithRestrictions({ - provider, - cwd: this.cwd, - mode, - customModes: state?.customModes, - experiments: state?.experiments, - apiConfiguration, - disabledTools: state?.disabledTools, - modelInfo, - includeAllToolsWithRestrictions: false, - }) - allTools = toolsResult.tools - } - - // Build metadata with tools and taskId for the condensing API call + // Build metadata for the condensing API call. + // NOTE: We intentionally omit tools/tool_choice/parallelToolCalls here because + // the condensation flow converts all tool_use/tool_result blocks to plain text + // and the SUMMARY_PROMPT instructs the model not to call tools. Including tool + // definitions with tool_choice:"auto" but no tool blocks in messages causes + // 500 errors on some proxies (e.g. Roo Code Router). See #11743. const metadata: ApiHandlerCreateMessageMetadata = { mode, taskId: this.taskId, - ...(allTools.length > 0 - ? { - tools: allTools, - tool_choice: "auto", - parallelToolCalls: true, - } - : {}), } try { @@ -4057,38 +4016,15 @@ export class Task extends EventEmitter implements TaskLike { ?.postMessageToWebview({ type: "condenseTaskContextStarted", text: this.taskId }) } - // Build tools for condensing metadata (same tools used for normal API calls) - // This ensures the condensing API call includes tool definitions for providers that need them - let contextMgmtTools: import("openai").default.Chat.ChatCompletionTool[] = [] - { - const provider = this.providerRef.deref() - if (provider) { - const toolsResult = await buildNativeToolsArrayWithRestrictions({ - provider, - cwd: this.cwd, - mode, - customModes: state?.customModes, - experiments: state?.experiments, - apiConfiguration, - disabledTools: state?.disabledTools, - modelInfo, - includeAllToolsWithRestrictions: false, - }) - contextMgmtTools = toolsResult.tools - } - } - - // Build metadata with tools and taskId for the condensing API call + // Build metadata for the condensing API call. + // NOTE: We intentionally omit tools/tool_choice/parallelToolCalls here because + // the condensation flow converts all tool_use/tool_result blocks to plain text + // and the SUMMARY_PROMPT instructs the model not to call tools. Including tool + // definitions with tool_choice:"auto" but no tool blocks in messages causes + // 500 errors on some proxies (e.g. Roo Code Router). See #11743. const contextMgmtMetadata: ApiHandlerCreateMessageMetadata = { mode, taskId: this.taskId, - ...(contextMgmtTools.length > 0 - ? { - tools: contextMgmtTools, - tool_choice: "auto", - parallelToolCalls: true, - } - : {}), } // Only generate environment details when context management will actually run. diff --git a/src/core/task/__tests__/Task.spec.ts b/src/core/task/__tests__/Task.spec.ts index d9f546d463..6afc449783 100644 --- a/src/core/task/__tests__/Task.spec.ts +++ b/src/core/task/__tests__/Task.spec.ts @@ -1986,6 +1986,81 @@ describe("Queued message processing after condense", () => { }) }) +describe("condenseContext metadata does not include tools (#11743)", () => { + function createProvider(): any { + const storageUri = { fsPath: path.join(os.tmpdir(), "test-storage") } + const ctx = { + globalState: { + get: vi.fn().mockImplementation((_key: keyof GlobalState) => undefined), + update: vi.fn().mockResolvedValue(undefined), + keys: vi.fn().mockReturnValue([]), + }, + globalStorageUri: storageUri, + workspaceState: { + get: vi.fn().mockImplementation((_key: any) => undefined), + update: vi.fn().mockResolvedValue(undefined), + keys: vi.fn().mockReturnValue([]), + }, + secrets: { + get: vi.fn().mockResolvedValue(undefined), + store: vi.fn().mockResolvedValue(undefined), + delete: vi.fn().mockResolvedValue(undefined), + }, + extensionUri: { fsPath: "/mock/extension/path" }, + extension: { packageJSON: { version: "1.0.0" } }, + } as unknown as vscode.ExtensionContext + + const output = { + appendLine: vi.fn(), + append: vi.fn(), + clear: vi.fn(), + show: vi.fn(), + hide: vi.fn(), + dispose: vi.fn(), + } + + const provider = new ClineProvider(ctx, output as any, "sidebar", new ContextProxy(ctx)) as any + provider.postMessageToWebview = vi.fn().mockResolvedValue(undefined) + provider.postStateToWebview = vi.fn().mockResolvedValue(undefined) + provider.postStateToWebviewWithoutTaskHistory = vi.fn().mockResolvedValue(undefined) + provider.getState = vi.fn().mockResolvedValue({}) + return provider + } + + const apiConfig: ProviderSettings = { + apiProvider: "anthropic", + apiModelId: "claude-3-5-sonnet-20241022", + apiKey: "test-api-key", + } as any + + it("does not pass tools, tool_choice, or parallelToolCalls to summarizeConversation", async () => { + const { summarizeConversation } = await import("../../condense") + const mockSummarize = vi.mocked(summarizeConversation) + mockSummarize.mockClear() + + const provider = createProvider() + const task = new Task({ + provider, + apiConfiguration: apiConfig, + task: "test task", + startTask: false, + }) + + vi.spyOn(task as any, "getSystemPrompt").mockResolvedValue("system") + + await task.condenseContext() + + expect(mockSummarize).toHaveBeenCalledTimes(1) + const callArgs = mockSummarize.mock.calls[0][0] + const metadata = callArgs.metadata + expect(metadata).toBeDefined() + expect(metadata).not.toHaveProperty("tools") + expect(metadata).not.toHaveProperty("tool_choice") + expect(metadata).not.toHaveProperty("parallelToolCalls") + expect(metadata).toHaveProperty("taskId") + }) +}) + describe("pushToolResultToUserContent", () => { let mockProvider: any let mockApiConfig: ProviderSettings