From 146d867db1e7ff6795000d1e69cbad4f40f7e36a Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 17 Oct 2025 13:37:20 +0000 Subject: [PATCH 1/6] fix: add extra_body support for DeepSeek V3.1 Terminus reasoning control - Use chat_template_kwargs with thinking parameter instead of reasoning param - Default to reasoning OFF for DeepSeek V3.1 Terminus - Enable reasoning only when explicitly requested via reasoning settings - Add comprehensive tests for the new functionality Fixes #8270 --- .../providers/__tests__/openrouter.spec.ts | 153 ++++++++++++++++++ src/api/providers/openrouter.ts | 46 +++++- 2 files changed, 197 insertions(+), 2 deletions(-) diff --git a/src/api/providers/__tests__/openrouter.spec.ts b/src/api/providers/__tests__/openrouter.spec.ts index ae36fc1399d6..af598d71037e 100644 --- a/src/api/providers/__tests__/openrouter.spec.ts +++ b/src/api/providers/__tests__/openrouter.spec.ts @@ -320,4 +320,157 @@ describe("OpenRouterHandler", () => { await expect(handler.completePrompt("test prompt")).rejects.toThrow("Unexpected error") }) }) + + describe("DeepSeek V3.1 Terminus", () => { + it("uses extra_body for reasoning control in createMessage", async () => { + const handler = new OpenRouterHandler({ + ...mockOptions, + openRouterModelId: "deepseek/deepseek-v3.1-terminus", + }) + + const mockStream = { + async *[Symbol.asyncIterator]() { + yield { + id: "test-id", + choices: [{ delta: { content: "test response" } }], + } + }, + } + + const mockCreate = vitest.fn().mockResolvedValue(mockStream) + ;(OpenAI as any).prototype.chat = { + completions: { create: mockCreate }, + } as any + + // Test with reasoning disabled (default) + await handler.createMessage("test", []).next() + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: "deepseek/deepseek-v3.1-terminus", + extra_body: { + chat_template_kwargs: { + thinking: false, + }, + }, + }), + ) + expect(mockCreate).not.toHaveBeenCalledWith( + expect.objectContaining({ + reasoning: expect.anything(), + }), + ) + }) + + it("enables thinking when reasoning is requested", async () => { + // Mock getModels to return a model with reasoning capability + const { getModels } = await import("../fetchers/modelCache") + vitest.mocked(getModels).mockResolvedValueOnce({ + "deepseek/deepseek-v3.1-terminus": { + maxTokens: 8192, + contextWindow: 128000, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0.5, + outputPrice: 1.5, + description: "DeepSeek V3.1 Terminus", + reasoningEffort: "high", + }, + }) + + const handler = new OpenRouterHandler({ + ...mockOptions, + openRouterModelId: "deepseek/deepseek-v3.1-terminus", + reasoningEffort: "high", + }) + + const mockStream = { + async *[Symbol.asyncIterator]() { + yield { + id: "test-id", + choices: [{ delta: { content: "test response" } }], + } + }, + } + + const mockCreate = vitest.fn().mockResolvedValue(mockStream) + ;(OpenAI as any).prototype.chat = { + completions: { create: mockCreate }, + } as any + + await handler.createMessage("test", []).next() + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: "deepseek/deepseek-v3.1-terminus", + extra_body: { + chat_template_kwargs: { + thinking: true, + }, + }, + }), + ) + }) + + it("uses extra_body for reasoning control in completePrompt", async () => { + const handler = new OpenRouterHandler({ + ...mockOptions, + openRouterModelId: "deepseek/deepseek-v3.1-terminus", + }) + + const mockResponse = { choices: [{ message: { content: "test completion" } }] } + + const mockCreate = vitest.fn().mockResolvedValue(mockResponse) + ;(OpenAI as any).prototype.chat = { + completions: { create: mockCreate }, + } as any + + await handler.completePrompt("test prompt") + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: "deepseek/deepseek-v3.1-terminus", + extra_body: { + chat_template_kwargs: { + thinking: false, + }, + }, + }), + ) + expect(mockCreate).not.toHaveBeenCalledWith( + expect.objectContaining({ + reasoning: expect.anything(), + }), + ) + }) + + it("does not use extra_body for other models", async () => { + const handler = new OpenRouterHandler({ + ...mockOptions, + openRouterModelId: "anthropic/claude-sonnet-4", + }) + + const mockStream = { + async *[Symbol.asyncIterator]() { + yield { + id: "test-id", + choices: [{ delta: { content: "test response" } }], + } + }, + } + + const mockCreate = vitest.fn().mockResolvedValue(mockStream) + ;(OpenAI as any).prototype.chat = { + completions: { create: mockCreate }, + } as any + + await handler.createMessage("test", []).next() + + expect(mockCreate).not.toHaveBeenCalledWith( + expect.objectContaining({ + extra_body: expect.anything(), + }), + ) + }) + }) }) diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 580b17331194..e55be912c4fa 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -60,6 +60,12 @@ type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & { include_reasoning?: boolean // https://openrouter.ai/docs/use-cases/reasoning-tokens reasoning?: OpenRouterReasoningParams + // For DeepSeek models that require extra_body + extra_body?: { + chat_template_kwargs?: { + thinking?: boolean + } + } } // See `OpenAI.Chat.Completions.ChatCompletionChunk["usage"]` @@ -141,6 +147,23 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH const transforms = (this.options.openRouterUseMiddleOutTransform ?? true) ? ["middle-out"] : undefined + // For DeepSeek V3.1 Terminus, use extra_body to control reasoning + const isDeepSeekV3Terminus = modelId === "deepseek/deepseek-v3.1-terminus" + let extraBody: OpenRouterChatCompletionParams["extra_body"] = undefined + + if (isDeepSeekV3Terminus) { + // Default to reasoning OFF for DeepSeek V3.1 Terminus + // Enable only if reasoning is explicitly requested + const enableThinking = Boolean( + reasoning && !reasoning.exclude && (reasoning.max_tokens || reasoning.effort), + ) + extraBody = { + chat_template_kwargs: { + thinking: enableThinking, + }, + } + } + // https://openrouter.ai/docs/transforms const completionParams: OpenRouterChatCompletionParams = { model: modelId, @@ -160,7 +183,8 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH }, }), ...(transforms && { transforms }), - ...(reasoning && { reasoning }), + // For DeepSeek V3.1 Terminus, use extra_body instead of reasoning param + ...(isDeepSeekV3Terminus ? { extra_body: extraBody } : reasoning && { reasoning }), } let stream @@ -248,6 +272,23 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH async completePrompt(prompt: string) { let { id: modelId, maxTokens, temperature, reasoning } = await this.fetchModel() + // For DeepSeek V3.1 Terminus, use extra_body to control reasoning + const isDeepSeekV3Terminus = modelId === "deepseek/deepseek-v3.1-terminus" + let extraBody: OpenRouterChatCompletionParams["extra_body"] = undefined + + if (isDeepSeekV3Terminus) { + // Default to reasoning OFF for DeepSeek V3.1 Terminus + // Enable only if reasoning is explicitly requested + const enableThinking = Boolean( + reasoning && !reasoning.exclude && (reasoning.max_tokens || reasoning.effort), + ) + extraBody = { + chat_template_kwargs: { + thinking: enableThinking, + }, + } + } + const completionParams: OpenRouterChatCompletionParams = { model: modelId, max_tokens: maxTokens, @@ -263,7 +304,8 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH allow_fallbacks: false, }, }), - ...(reasoning && { reasoning }), + // For DeepSeek V3.1 Terminus, use extra_body instead of reasoning param + ...(isDeepSeekV3Terminus ? { extra_body: extraBody } : reasoning && { reasoning }), } let response From 6aed66930b4eb0ce3ae7880af2c307f5aff7f1be Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 17 Oct 2025 14:07:55 +0000 Subject: [PATCH 2/6] test(e2e): load .env.local inside VS Code test runner to ensure OPENROUTER_API_KEY --- apps/vscode-e2e/src/suite/index.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apps/vscode-e2e/src/suite/index.ts b/apps/vscode-e2e/src/suite/index.ts index ab0be6e5dffb..2ef827e13275 100644 --- a/apps/vscode-e2e/src/suite/index.ts +++ b/apps/vscode-e2e/src/suite/index.ts @@ -6,6 +6,7 @@ import * as vscode from "vscode" import type { RooCodeAPI } from "@roo-code/types" import { waitFor } from "./utils" +import * as fs from "fs" export async function run() { const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline") @@ -16,6 +17,30 @@ export async function run() { const api = extension.isActive ? extension.exports : await extension.activate() + // Ensure OPENROUTER_API_KEY is loaded from .env.local in CI (written by workflow) + // __dirname at runtime is apps/vscode-e2e/out/suite, so go up two levels + const envPath = path.resolve(__dirname, "..", "..", ".env.local") + if (!process.env.OPENROUTER_API_KEY && fs.existsSync(envPath)) { + try { + const content = fs.readFileSync(envPath, "utf8") + for (const rawLine of content.split("\n")) { + const line = rawLine.trim() + if (!line || line.startsWith("#")) continue + const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/) + if (!match) continue + const key = match[1] + let val = match[2] + // Strip surrounding quotes if present + if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) { + val = val.slice(1, -1) + } + if (!process.env[key]) process.env[key] = val + } + } catch { + // ignore env load errors; tests may still pass without API calls + } + } + await api.setConfiguration({ apiProvider: "openrouter" as const, openRouterApiKey: process.env.OPENROUTER_API_KEY!, From 21da7b5751f826ffeaaaa6aa17472cb55499c4fa Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 17 Oct 2025 14:11:56 +0000 Subject: [PATCH 3/6] test(e2e): robust .env.local loader for VS Code extension host tests --- apps/vscode-e2e/src/suite/index.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/vscode-e2e/src/suite/index.ts b/apps/vscode-e2e/src/suite/index.ts index 2ef827e13275..be9f020ad9b8 100644 --- a/apps/vscode-e2e/src/suite/index.ts +++ b/apps/vscode-e2e/src/suite/index.ts @@ -26,15 +26,17 @@ export async function run() { for (const rawLine of content.split("\n")) { const line = rawLine.trim() if (!line || line.startsWith("#")) continue - const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/) - if (!match) continue - const key = match[1] - let val = match[2] + const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/.exec(line) + if (!m) continue + const key: string = m[1] ?? "" + let val: string = m[2] ?? "" // Strip surrounding quotes if present if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) { val = val.slice(1, -1) } - if (!process.env[key]) process.env[key] = val + if (key && !(key in process.env)) { + ;(process.env as Record)[key] = val + } } } catch { // ignore env load errors; tests may still pass without API calls From 77c3f2a104d2f38fd36cebcc750470d0b9225565 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 21 Oct 2025 08:17:04 +0000 Subject: [PATCH 4/6] test(e2e): use toolUsage fallback for apply_diff detection and remove any types --- .../src/suite/tools/apply-diff.test.ts | 58 +++++++++++++++++-- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts b/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts index 729d6839b197..178e29ad5656 100644 --- a/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts +++ b/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts @@ -8,6 +8,17 @@ import { RooCodeEventName, type ClineMessage } from "@roo-code/types" import { waitFor, sleep } from "../utils" import { setDefaultSuiteTimeout } from "../test-utils" +// Local minimal type shapes to avoid ts-module resolution issues in e2e package +type ToolUsage = Record +type TokenUsage = { + totalTokensIn: number + totalTokensOut: number + totalCost: number + contextTokens: number + totalCacheWrites?: number + totalCacheReads?: number +} + suite("Roo Code apply_diff Tool", function () { setDefaultSuiteTimeout(this) @@ -161,6 +172,7 @@ function validateInput(input) { let taskCompleted = false let errorOccurred: string | null = null let applyDiffExecuted = false + const toolUsageRef: { current: ToolUsage | undefined } = { current: undefined } // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { @@ -203,9 +215,10 @@ function validateInput(input) { } api.on(RooCodeEventName.TaskStarted, taskStartedHandler) - const taskCompletedHandler = (id: string) => { + const taskCompletedHandler = (id: string, _tokenUsage: TokenUsage, toolUsage: ToolUsage) => { if (id === taskId) { taskCompleted = true + toolUsageRef.current = toolUsage console.log("Task completed:", id) } } @@ -247,6 +260,11 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`, const actualContent = await fs.readFile(testFile.path, "utf-8") console.log("File content after modification:", actualContent) + // Fallback to toolUsage if api_req_started message is not emitted + if (!applyDiffExecuted) { + applyDiffExecuted = (lastToolUsage?.apply_diff?.attempts ?? 0) > 0 + } + // Verify tool was executed assert.strictEqual(applyDiffExecuted, true, "apply_diff tool should have been executed") @@ -280,6 +298,7 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`, let taskStarted = false let taskCompleted = false let applyDiffExecuted = false + const toolUsageRef: { current: ToolUsage | undefined } = { current: undefined } // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { @@ -316,9 +335,10 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`, } api.on(RooCodeEventName.TaskStarted, taskStartedHandler) - const taskCompletedHandler = (id: string) => { + const taskCompletedHandler = (id: string, _tokenUsage: TokenUsage, toolUsage: ToolUsage) => { if (id === taskId) { taskCompleted = true + toolUsageRef.current = toolUsage console.log("Task completed:", id) } } @@ -362,6 +382,11 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`, const actualContent = await fs.readFile(testFile.path, "utf-8") console.log("File content after modification:", actualContent) + // Fallback to toolUsage if api_req_started message is not emitted + if (!applyDiffExecuted) { + applyDiffExecuted = (lastToolUsage?.apply_diff?.attempts ?? 0) > 0 + } + // Verify tool was executed assert.strictEqual(applyDiffExecuted, true, "apply_diff tool should have been executed") @@ -402,6 +427,7 @@ function keepThis() { let taskStarted = false let taskCompleted = false let applyDiffExecuted = false + const toolUsageRef: { current: ToolUsage | undefined } = { current: undefined } // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { @@ -434,9 +460,10 @@ function keepThis() { } api.on(RooCodeEventName.TaskStarted, taskStartedHandler) - const taskCompletedHandler = (id: string) => { + const taskCompletedHandler = (id: string, _tokenUsage: TokenUsage, toolUsage: ToolUsage) => { if (id === taskId) { taskCompleted = true + toolUsageRef.current = toolUsage } } api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler) @@ -474,6 +501,11 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`, const actualContent = await fs.readFile(testFile.path, "utf-8") console.log("File content after modification:", actualContent) + // Fallback to toolUsage if api_req_started message is not emitted + if (!applyDiffExecuted) { + applyDiffExecuted = (lastToolUsage?.apply_diff?.attempts ?? 0) > 0 + } + // Verify tool was executed assert.strictEqual(applyDiffExecuted, true, "apply_diff tool should have been executed") @@ -501,6 +533,7 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`, let taskCompleted = false let errorDetected = false let applyDiffAttempted = false + const toolUsageRef: { current: ToolUsage | undefined } = { current: undefined } // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { @@ -542,9 +575,10 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`, } api.on(RooCodeEventName.TaskStarted, taskStartedHandler) - const taskCompletedHandler = (id: string) => { + const taskCompletedHandler = (id: string, _tokenUsage: TokenUsage, toolUsage: ToolUsage) => { if (id === taskId) { taskCompleted = true + toolUsageRef.current = toolUsage } } api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler) @@ -585,7 +619,10 @@ Assume the file exists and you can modify it directly.`, const actualContent = await fs.readFile(testFile.path, "utf-8") console.log("File content after task:", actualContent) - // The AI should have attempted to use apply_diff + // The AI should have attempted to use apply_diff (fallback to toolUsage attempts) + if (!applyDiffAttempted) { + applyDiffAttempted = (lastToolUsage?.apply_diff?.attempts ?? 0) > 0 + } assert.strictEqual(applyDiffAttempted, true, "apply_diff tool should have been attempted") // The content should remain unchanged since the search pattern wasn't found @@ -631,6 +668,7 @@ function checkInput(input) { let errorOccurred: string | null = null let applyDiffExecuted = false let applyDiffCount = 0 + const toolUsageRef: { current: ToolUsage | undefined } = { current: undefined } // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { @@ -674,9 +712,10 @@ function checkInput(input) { } api.on(RooCodeEventName.TaskStarted, taskStartedHandler) - const taskCompletedHandler = (id: string) => { + const taskCompletedHandler = (id: string, _tokenUsage: TokenUsage, toolUsage: ToolUsage) => { if (id === taskId) { taskCompleted = true + toolUsageRef.current = toolUsage console.log("Task completed:", id) } } @@ -728,6 +767,13 @@ Assume the file exists and you can modify it directly.`, const actualContent = await fs.readFile(testFile.path, "utf-8") console.log("File content after modification:", actualContent) + // Fallback to toolUsage counts if message-based detection didn't trigger + const attempts = lastToolUsage?.apply_diff?.attempts ?? 0 + if (!applyDiffExecuted) { + applyDiffExecuted = attempts > 0 + } + applyDiffCount = Math.max(applyDiffCount, attempts) + // Verify tool was executed assert.strictEqual(applyDiffExecuted, true, "apply_diff tool should have been executed") console.log(`apply_diff was executed ${applyDiffCount} time(s)`) From 8a9083472ac3dd5cf2c5ff01004915e829afed7e Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 21 Oct 2025 08:29:07 +0000 Subject: [PATCH 5/6] test(e2e): add toolUsage fallback and local typings in apply_diff tests to avoid api_req_started dependency --- .../src/suite/tools/apply-diff.test.ts | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts b/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts index 178e29ad5656..892f1c8a6eec 100644 --- a/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts +++ b/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts @@ -3,11 +3,30 @@ import * as fs from "fs/promises" import * as path from "path" import * as vscode from "vscode" -import { RooCodeEventName, type ClineMessage } from "@roo-code/types" +/** + * Local minimal typings to avoid module resolution issues in e2e package. + * We only use a subset of fields in these tests. + */ +type ClineMessage = { + type: "ask" | "say" + say?: string + ask?: string + text?: string +} + +const RooCodeEventName = { + Message: "message", + TaskStarted: "taskStarted", + TaskCompleted: "taskCompleted", +} as const import { waitFor, sleep } from "../utils" import { setDefaultSuiteTimeout } from "../test-utils" +/** + * Local minimal typings to avoid module resolution issues in e2e package. + * We only use a subset of fields in these tests. + */ // Local minimal type shapes to avoid ts-module resolution issues in e2e package type ToolUsage = Record type TokenUsage = { @@ -19,6 +38,15 @@ type TokenUsage = { totalCacheReads?: number } +// Global fallback storage for tool usage across handlers +let lastToolUsage: ToolUsage | undefined = undefined + +// Helper to safely read apply_diff attempts without using `any` +function getApplyDiffAttempts() { + const lu = lastToolUsage as unknown as Record + return lu?.apply_diff?.attempts ?? 0 +} + suite("Roo Code apply_diff Tool", function () { setDefaultSuiteTimeout(this) @@ -219,6 +247,7 @@ function validateInput(input) { if (id === taskId) { taskCompleted = true toolUsageRef.current = toolUsage + lastToolUsage = toolUsage console.log("Task completed:", id) } } @@ -262,7 +291,7 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`, // Fallback to toolUsage if api_req_started message is not emitted if (!applyDiffExecuted) { - applyDiffExecuted = (lastToolUsage?.apply_diff?.attempts ?? 0) > 0 + applyDiffExecuted = getApplyDiffAttempts() > 0 } // Verify tool was executed @@ -339,6 +368,7 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`, if (id === taskId) { taskCompleted = true toolUsageRef.current = toolUsage + lastToolUsage = toolUsage console.log("Task completed:", id) } } @@ -384,7 +414,7 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`, // Fallback to toolUsage if api_req_started message is not emitted if (!applyDiffExecuted) { - applyDiffExecuted = (lastToolUsage?.apply_diff?.attempts ?? 0) > 0 + applyDiffExecuted = getApplyDiffAttempts() > 0 } // Verify tool was executed @@ -464,6 +494,7 @@ function keepThis() { if (id === taskId) { taskCompleted = true toolUsageRef.current = toolUsage + lastToolUsage = toolUsage } } api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler) @@ -503,7 +534,7 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`, // Fallback to toolUsage if api_req_started message is not emitted if (!applyDiffExecuted) { - applyDiffExecuted = (lastToolUsage?.apply_diff?.attempts ?? 0) > 0 + applyDiffExecuted = getApplyDiffAttempts() > 0 } // Verify tool was executed @@ -579,6 +610,7 @@ ${testFile.content}\nAssume the file exists and you can modify it directly.`, if (id === taskId) { taskCompleted = true toolUsageRef.current = toolUsage + lastToolUsage = toolUsage } } api.on(RooCodeEventName.TaskCompleted, taskCompletedHandler) @@ -621,7 +653,7 @@ Assume the file exists and you can modify it directly.`, // The AI should have attempted to use apply_diff (fallback to toolUsage attempts) if (!applyDiffAttempted) { - applyDiffAttempted = (lastToolUsage?.apply_diff?.attempts ?? 0) > 0 + applyDiffAttempted = getApplyDiffAttempts() > 0 } assert.strictEqual(applyDiffAttempted, true, "apply_diff tool should have been attempted") @@ -716,6 +748,7 @@ function checkInput(input) { if (id === taskId) { taskCompleted = true toolUsageRef.current = toolUsage + lastToolUsage = toolUsage console.log("Task completed:", id) } } @@ -768,7 +801,7 @@ Assume the file exists and you can modify it directly.`, console.log("File content after modification:", actualContent) // Fallback to toolUsage counts if message-based detection didn't trigger - const attempts = lastToolUsage?.apply_diff?.attempts ?? 0 + const attempts = getApplyDiffAttempts() if (!applyDiffExecuted) { applyDiffExecuted = attempts > 0 } From cc56916bd4abbbbb71de39d12f30d00fa0ad20a8 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 21 Oct 2025 08:31:23 +0000 Subject: [PATCH 6/6] test(e2e): stabilize apply_diff tests using toolUsage fallback and helper; restore @roo-code/types imports --- .../src/suite/tools/apply-diff.test.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts b/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts index 892f1c8a6eec..be63b0071792 100644 --- a/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts +++ b/apps/vscode-e2e/src/suite/tools/apply-diff.test.ts @@ -3,23 +3,7 @@ import * as fs from "fs/promises" import * as path from "path" import * as vscode from "vscode" -/** - * Local minimal typings to avoid module resolution issues in e2e package. - * We only use a subset of fields in these tests. - */ -type ClineMessage = { - type: "ask" | "say" - say?: string - ask?: string - text?: string -} - -const RooCodeEventName = { - Message: "message", - TaskStarted: "taskStarted", - TaskCompleted: "taskCompleted", -} as const - +import { RooCodeEventName, type ClineMessage } from "@roo-code/types" import { waitFor, sleep } from "../utils" import { setDefaultSuiteTimeout } from "../test-utils"