From c838dc604778afa58a88d28fe50a58753f879406 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Fri, 28 Nov 2025 01:00:28 -0700 Subject: [PATCH 1/8] Refactor: Remove line_count parameter from write_to_file tool --- .../assistant-message/NativeToolCallParser.ts | 14 +-- .../__tests__/AssistantMessageParser.spec.ts | 8 +- .../__tests__/parseAssistantMessage.spec.ts | 8 +- .../parseAssistantMessageBenchmark.ts | 6 +- .../architect-mode-prompt.snap | 3 - .../mcp-server-creation-disabled.snap | 3 - .../mcp-server-creation-enabled.snap | 3 - .../partial-reads-enabled.snap | 3 - .../consistent-system-prompt.snap | 3 - .../with-computer-use-support.snap | 3 - .../with-diff-enabled-false.snap | 3 - .../system-prompt/with-diff-enabled-true.snap | 3 - .../with-diff-enabled-undefined.snap | 3 - .../with-different-viewport-size.snap | 3 - .../system-prompt/with-mcp-hub-provided.snap | 3 - .../system-prompt/with-undefined-mcp-hub.snap | 3 - src/core/prompts/responses.ts | 39 ------- .../tools/native-tools/write_to_file.ts | 11 +- src/core/prompts/tools/write-to-file.ts | 3 - src/core/task/Task.ts | 10 +- src/core/tools/WriteToFileTool.ts | 64 ++--------- .../tools/__tests__/writeToFileTool.spec.ts | 9 +- .../editor/__tests__/detect-omission.spec.ts | 108 ++++-------------- src/integrations/editor/detect-omission.ts | 22 ++-- src/shared/tools.ts | 5 +- 25 files changed, 68 insertions(+), 275 deletions(-) diff --git a/src/core/assistant-message/NativeToolCallParser.ts b/src/core/assistant-message/NativeToolCallParser.ts index 87debb1bb22..ac95597779c 100644 --- a/src/core/assistant-message/NativeToolCallParser.ts +++ b/src/core/assistant-message/NativeToolCallParser.ts @@ -394,16 +394,10 @@ export class NativeToolCallParser { break case "write_to_file": - if (partialArgs.path || partialArgs.content || partialArgs.line_count !== undefined) { + if (partialArgs.path || partialArgs.content) { nativeArgs = { path: partialArgs.path, content: partialArgs.content, - line_count: - typeof partialArgs.line_count === "number" - ? partialArgs.line_count - : partialArgs.line_count - ? parseInt(String(partialArgs.line_count), 10) - : undefined, } } break @@ -745,14 +739,10 @@ export class NativeToolCallParser { break case "write_to_file": - if (args.path !== undefined && args.content !== undefined && args.line_count !== undefined) { + if (args.path !== undefined && args.content !== undefined) { nativeArgs = { path: args.path, content: args.content, - line_count: - typeof args.line_count === "number" - ? args.line_count - : parseInt(String(args.line_count), 10), } as NativeArgsFor } break diff --git a/src/core/assistant-message/__tests__/AssistantMessageParser.spec.ts b/src/core/assistant-message/__tests__/AssistantMessageParser.spec.ts index 6b7c3915ee7..cb60c8744f8 100644 --- a/src/core/assistant-message/__tests__/AssistantMessageParser.spec.ts +++ b/src/core/assistant-message/__tests__/AssistantMessageParser.spec.ts @@ -179,7 +179,7 @@ describe("AssistantMessageParser (streaming)", () => { // This has XML-like content: return true; } - 5` + ` const result = streamChunks(parser, message).filter((block) => !isEmptyTextContent(block)) @@ -188,7 +188,6 @@ describe("AssistantMessageParser (streaming)", () => { expect(toolUse.type).toBe("tool_use") expect(toolUse.name).toBe("write_to_file") expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.params.line_count).toBe("5") expect(toolUse.params.content).toContain("function example()") expect(toolUse.params.content).toContain("// This has XML-like content: ") expect(toolUse.params.content).toContain("return true;") @@ -263,7 +262,7 @@ describe("AssistantMessageParser (streaming)", () => { line 1 line 2 line 3 - 3` + ` const result = streamChunks(parser, message).filter((block) => !isEmptyTextContent(block)) expect(result).toHaveLength(1) @@ -274,7 +273,6 @@ describe("AssistantMessageParser (streaming)", () => { expect(toolUse.params.content).toContain("line 1") expect(toolUse.params.content).toContain("line 2") expect(toolUse.params.content).toContain("line 3") - expect(toolUse.params.line_count).toBe("3") expect(toolUse.partial).toBe(false) }) it("should handle a complex message with multiple content types", () => { @@ -287,7 +285,7 @@ describe("AssistantMessageParser (streaming)", () => { src/index.ts // Updated content console.log("Hello world"); - 2 + Let's run the code: diff --git a/src/core/assistant-message/__tests__/parseAssistantMessage.spec.ts b/src/core/assistant-message/__tests__/parseAssistantMessage.spec.ts index f5ae600beed..80d2502626d 100644 --- a/src/core/assistant-message/__tests__/parseAssistantMessage.spec.ts +++ b/src/core/assistant-message/__tests__/parseAssistantMessage.spec.ts @@ -168,7 +168,7 @@ const isEmptyTextContent = (block: AssistantMessageContent) => // This has XML-like content: return true; } - 5` + ` const result = parser(message).filter((block) => !isEmptyTextContent(block)) @@ -177,7 +177,6 @@ const isEmptyTextContent = (block: AssistantMessageContent) => expect(toolUse.type).toBe("tool_use") expect(toolUse.name).toBe("write_to_file") expect(toolUse.params.path).toBe("src/file.ts") - expect(toolUse.params.line_count).toBe("5") expect(toolUse.params.content).toContain("function example()") expect(toolUse.params.content).toContain("// This has XML-like content: ") expect(toolUse.params.content).toContain("return true;") @@ -276,7 +275,7 @@ const isEmptyTextContent = (block: AssistantMessageContent) => line 1 line 2 line 3 - 3` + ` const result = parser(message).filter((block) => !isEmptyTextContent(block)) expect(result).toHaveLength(1) @@ -287,7 +286,6 @@ const isEmptyTextContent = (block: AssistantMessageContent) => expect(toolUse.params.content).toContain("line 1") expect(toolUse.params.content).toContain("line 2") expect(toolUse.params.content).toContain("line 3") - expect(toolUse.params.line_count).toBe("3") expect(toolUse.partial).toBe(false) }) @@ -301,7 +299,7 @@ const isEmptyTextContent = (block: AssistantMessageContent) => src/index.ts // Updated content console.log("Hello world"); - 2 + Let's run the code: diff --git a/src/core/assistant-message/__tests__/parseAssistantMessageBenchmark.ts b/src/core/assistant-message/__tests__/parseAssistantMessageBenchmark.ts index d5450988c94..a32b1173ce0 100644 --- a/src/core/assistant-message/__tests__/parseAssistantMessageBenchmark.ts +++ b/src/core/assistant-message/__tests__/parseAssistantMessageBenchmark.ts @@ -62,17 +62,17 @@ const testCases = [ }, { name: "Message with a complex tool use (write_to_file)", - input: "src/file.ts\nfunction example() {\n // This has XML-like content: \n return true;\n}\n5", + input: "src/file.ts\nfunction example() {\n // This has XML-like content: \n return true;\n}\n", }, { name: "Message with multiple tool uses", - input: "First file: src/file1.ts\nSecond file: src/file2.ts\nLet's write a new file: src/file3.ts\nexport function newFunction() {\n return 'Hello world';\n}\n3", + input: "First file: src/file1.ts\nSecond file: src/file2.ts\nLet's write a new file: src/file3.ts\nexport function newFunction() {\n return 'Hello world';\n}\n", }, { name: "Large message with repeated tool uses", input: Array(50) .fill( - 'src/file.ts\noutput.tsconsole.log("hello");1', + 'src/file.ts\noutput.tsconsole.log("hello");', ) .join("\n"), }, diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap index c99f7b47f7e..d4c04adf721 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap @@ -164,14 +164,12 @@ Description: Request to write content to a file. This tool is primarily used for Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory /test/path) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Usage: File path here Your file content here -total number of lines in the file, including empty lines Example: Requesting to write to frontend-config.json @@ -193,7 +191,6 @@ Example: Requesting to write to frontend-config.json "version": "1.0.0" } -14 ## insert_content diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap index 9125f356b89..fe13e24ed22 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap @@ -163,14 +163,12 @@ Description: Request to write content to a file. This tool is primarily used for Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory /test/path) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Usage: File path here Your file content here -total number of lines in the file, including empty lines Example: Requesting to write to frontend-config.json @@ -192,7 +190,6 @@ Example: Requesting to write to frontend-config.json "version": "1.0.0" } -14 ## insert_content diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap index af4d152cbe1..6e4b868f574 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap @@ -164,14 +164,12 @@ Description: Request to write content to a file. This tool is primarily used for Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory /test/path) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Usage: File path here Your file content here -total number of lines in the file, including empty lines Example: Requesting to write to frontend-config.json @@ -193,7 +191,6 @@ Example: Requesting to write to frontend-config.json "version": "1.0.0" } -14 ## insert_content diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap index 4c5e38b82a8..cbf3d6597ef 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap @@ -169,14 +169,12 @@ Description: Request to write content to a file. This tool is primarily used for Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory /test/path) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Usage: File path here Your file content here -total number of lines in the file, including empty lines Example: Requesting to write to frontend-config.json @@ -198,7 +196,6 @@ Example: Requesting to write to frontend-config.json "version": "1.0.0" } -14 ## insert_content diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap index c99f7b47f7e..d4c04adf721 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap @@ -164,14 +164,12 @@ Description: Request to write content to a file. This tool is primarily used for Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory /test/path) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Usage: File path here Your file content here -total number of lines in the file, including empty lines Example: Requesting to write to frontend-config.json @@ -193,7 +191,6 @@ Example: Requesting to write to frontend-config.json "version": "1.0.0" } -14 ## insert_content diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap index 030e7a05650..aa0d1b5ab3c 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap @@ -164,14 +164,12 @@ Description: Request to write content to a file. This tool is primarily used for Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory /test/path) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Usage: File path here Your file content here -total number of lines in the file, including empty lines Example: Requesting to write to frontend-config.json @@ -193,7 +191,6 @@ Example: Requesting to write to frontend-config.json "version": "1.0.0" } -14 ## insert_content diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap index c99f7b47f7e..d4c04adf721 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap @@ -164,14 +164,12 @@ Description: Request to write content to a file. This tool is primarily used for Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory /test/path) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Usage: File path here Your file content here -total number of lines in the file, including empty lines Example: Requesting to write to frontend-config.json @@ -193,7 +191,6 @@ Example: Requesting to write to frontend-config.json "version": "1.0.0" } -14 ## insert_content diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap index 44a560219b5..bdd544f15e1 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap @@ -252,14 +252,12 @@ Description: Request to write content to a file. This tool is primarily used for Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory /test/path) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Usage: File path here Your file content here -total number of lines in the file, including empty lines Example: Requesting to write to frontend-config.json @@ -281,7 +279,6 @@ Example: Requesting to write to frontend-config.json "version": "1.0.0" } -14 ## insert_content diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap index c99f7b47f7e..d4c04adf721 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap @@ -164,14 +164,12 @@ Description: Request to write content to a file. This tool is primarily used for Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory /test/path) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Usage: File path here Your file content here -total number of lines in the file, including empty lines Example: Requesting to write to frontend-config.json @@ -193,7 +191,6 @@ Example: Requesting to write to frontend-config.json "version": "1.0.0" } -14 ## insert_content diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap index c99f7b47f7e..d4c04adf721 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap @@ -164,14 +164,12 @@ Description: Request to write content to a file. This tool is primarily used for Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory /test/path) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Usage: File path here Your file content here -total number of lines in the file, including empty lines Example: Requesting to write to frontend-config.json @@ -193,7 +191,6 @@ Example: Requesting to write to frontend-config.json "version": "1.0.0" } -14 ## insert_content diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap index af4d152cbe1..6e4b868f574 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap @@ -164,14 +164,12 @@ Description: Request to write content to a file. This tool is primarily used for Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory /test/path) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Usage: File path here Your file content here -total number of lines in the file, including empty lines Example: Requesting to write to frontend-config.json @@ -193,7 +191,6 @@ Example: Requesting to write to frontend-config.json "version": "1.0.0" } -14 ## insert_content diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap index c99f7b47f7e..d4c04adf721 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap @@ -164,14 +164,12 @@ Description: Request to write content to a file. This tool is primarily used for Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory /test/path) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Usage: File path here Your file content here -total number of lines in the file, including empty lines Example: Requesting to write to frontend-config.json @@ -193,7 +191,6 @@ Example: Requesting to write to frontend-config.json "version": "1.0.0" } -14 ## insert_content diff --git a/src/core/prompts/responses.ts b/src/core/prompts/responses.ts index 6535f25f5c0..ccb09e68e19 100644 --- a/src/core/prompts/responses.ts +++ b/src/core/prompts/responses.ts @@ -94,45 +94,6 @@ Otherwise, if you have not completed the task and do not need additional informa return `Missing value for required parameter '${paramName}'. Please retry with complete response.\n\n${instructions}` }, - lineCountTruncationError: ( - actualLineCount: number, - isNewFile: boolean, - diffStrategyEnabled: boolean = false, - protocol?: ToolProtocol, - ) => { - const truncationMessage = `Note: Your response may have been truncated because it exceeded your output limit. You wrote ${actualLineCount} lines of content, but the line_count parameter was either missing or not included in your response.` - - const newFileGuidance = - `This appears to be a new file.\n` + - `${truncationMessage}\n\n` + - `RECOMMENDED APPROACH:\n` + - `1. Try again with the line_count parameter in your response if you forgot to include it\n` + - `2. Or break your content into smaller chunks - first use write_to_file with the initial chunk\n` + - `3. Then use insert_content to append additional chunks\n` - - let existingFileApproaches = [ - `1. Try again with the line_count parameter in your response if you forgot to include it`, - ] - - if (diffStrategyEnabled) { - existingFileApproaches.push(`2. Or try using apply_diff instead of write_to_file for targeted changes`) - } - - existingFileApproaches.push( - `${diffStrategyEnabled ? "3" : "2"}. Or use insert_content to add specific content at particular lines`, - ) - - const existingFileGuidance = - `This appears to be content for an existing file.\n` + - `${truncationMessage}\n\n` + - `RECOMMENDED APPROACH:\n` + - `${existingFileApproaches.join("\n")}\n` - - const instructions = getToolInstructionsReminder(protocol) - - return `${isNewFile ? newFileGuidance : existingFileGuidance}\n${instructions}` - }, - invalidMcpToolArgumentError: (serverName: string, toolName: string, protocol?: ToolProtocol) => { if (isNativeProtocol(protocol ?? TOOL_PROTOCOL.XML)) { return JSON.stringify({ diff --git a/src/core/prompts/tools/native-tools/write_to_file.ts b/src/core/prompts/tools/native-tools/write_to_file.ts index 2febb32b2ec..4ab7c53b6e6 100644 --- a/src/core/prompts/tools/native-tools/write_to_file.ts +++ b/src/core/prompts/tools/native-tools/write_to_file.ts @@ -5,17 +5,14 @@ const WRITE_TO_FILE_DESCRIPTION = `Request to write content to a file. This tool Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Example: Writing a configuration file -{ "path": "frontend-config.json", "content": "{\\n \\"apiEndpoint\\": \\"https://api.example.com\\",\\n \\"theme\\": {\\n \\"primaryColor\\": \\"#007bff\\"\\n }\\n}", "line_count": 5 }` +{ "path": "frontend-config.json", "content": "{\\n \\"apiEndpoint\\": \\"https://api.example.com\\",\\n \\"theme\\": {\\n \\"primaryColor\\": \\"#007bff\\"\\n }\\n}" }` const PATH_PARAMETER_DESCRIPTION = `Path to the file to write, relative to the workspace` const CONTENT_PARAMETER_DESCRIPTION = `Full contents that the file should contain with no omissions or line numbers` -const LINE_COUNT_PARAMETER_DESCRIPTION = `Total number of lines in the written file, counting blank lines` - export default { type: "function", function: { @@ -33,12 +30,8 @@ export default { type: "string", description: CONTENT_PARAMETER_DESCRIPTION, }, - line_count: { - type: "integer", - description: LINE_COUNT_PARAMETER_DESCRIPTION, - }, }, - required: ["path", "content", "line_count"], + required: ["path", "content"], additionalProperties: false, }, }, diff --git a/src/core/prompts/tools/write-to-file.ts b/src/core/prompts/tools/write-to-file.ts index 221103b04f5..c957f0891e1 100644 --- a/src/core/prompts/tools/write-to-file.ts +++ b/src/core/prompts/tools/write-to-file.ts @@ -6,14 +6,12 @@ Description: Request to write content to a file. This tool is primarily used for Parameters: - path: (required) The path of the file to write to (relative to the current workspace directory ${args.cwd}) - content: (required) The content to write to the file. When performing a full rewrite of an existing file or creating a new one, ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file. -- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing. Usage: File path here Your file content here -total number of lines in the file, including empty lines Example: Requesting to write to frontend-config.json @@ -35,6 +33,5 @@ Example: Requesting to write to frontend-config.json "version": "1.0.0" } -14 ` } diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index c4a52246aa8..520d7c34308 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -83,6 +83,10 @@ import { TerminalRegistry } from "../../integrations/terminal/TerminalRegistry" // utils import { calculateApiCostAnthropic, calculateApiCostOpenAI } from "../../shared/cost" import { getWorkspacePath } from "../../utils/path" +import { + isMergeEnvironmentDetailsMergeNeeded, + mergeEnvironmentDetailsIntoUserContent, +} from "../../utils/mergeEnvironmentDetails" // prompts import { formatResponse } from "../prompts/responses" @@ -2157,8 +2161,10 @@ export class Task extends EventEmitter implements TaskLike { // Add environment details as its own text block, separate from tool // results. - const finalUserContent = [...contentWithoutEnvDetails, { type: "text" as const, text: environmentDetails }] - + let finalUserContent = [...contentWithoutEnvDetails, { type: "text" as const, text: environmentDetails }] + if (isMergeEnvironmentDetailsMergeNeeded(modelId)) { + finalUserContent = mergeEnvironmentDetailsIntoUserContent(contentWithoutEnvDetails, environmentDetails) + } // Only add user message to conversation history if: // 1. This is the first attempt (retryAttempt === 0), AND // 2. The original userContent was not empty (empty signals delegation resume where diff --git a/src/core/tools/WriteToFileTool.ts b/src/core/tools/WriteToFileTool.ts index f7d7d2c6a62..f1bae7ff2fc 100644 --- a/src/core/tools/WriteToFileTool.ts +++ b/src/core/tools/WriteToFileTool.ts @@ -18,12 +18,10 @@ import { EXPERIMENT_IDS, experiments } from "../../shared/experiments" import { convertNewFileToUnifiedDiff, computeDiffStats, sanitizeUnifiedDiff } from "../diff/stats" import { BaseTool, ToolCallbacks } from "./BaseTool" import type { ToolUse } from "../../shared/tools" -import { resolveToolProtocol } from "../../utils/resolveToolProtocol" interface WriteToFileParams { path: string content: string - line_count: number } export class WriteToFileTool extends BaseTool<"write_to_file"> { @@ -33,15 +31,13 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> { return { path: params.path || "", content: params.content || "", - line_count: parseInt(params.line_count ?? "0", 10), } } async execute(params: WriteToFileParams, task: Task, callbacks: ToolCallbacks): Promise { - const { pushToolResult, handleError, askApproval, removeClosingTag, toolProtocol } = callbacks + const { pushToolResult, handleError, askApproval, removeClosingTag } = callbacks const relPath = params.path let newContent = params.content - const predictedLineCount = params.line_count if (!relPath) { task.consecutiveMistakeCount++ @@ -63,7 +59,7 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> { if (!accessAllowed) { await task.say("rooignore_error", relPath) - pushToolResult(formatResponse.rooIgnoreError(relPath, toolProtocol)) + pushToolResult(formatResponse.rooIgnoreError(relPath)) return } @@ -109,38 +105,6 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> { } try { - if (predictedLineCount === undefined || predictedLineCount === 0) { - task.consecutiveMistakeCount++ - task.recordToolError("write_to_file") - task.didToolFailInCurrentTurn = true - - const actualLineCount = newContent.split("\n").length - const isNewFile = !fileExists - const diffStrategyEnabled = !!task.diffStrategy - const modelInfo = task.api.getModel().info - const toolProtocol = resolveToolProtocol(task.apiConfiguration, modelInfo) - - await task.say( - "error", - `Roo tried to use write_to_file${ - relPath ? ` for '${relPath.toPosix()}'` : "" - } but the required parameter 'line_count' was missing or truncated after ${actualLineCount} lines of content were written. Retrying...`, - ) - - pushToolResult( - formatResponse.toolError( - formatResponse.lineCountTruncationError( - actualLineCount, - isNewFile, - diffStrategyEnabled, - toolProtocol, - ), - ), - ) - await task.diffViewProvider.revertChanges() - return - } - task.consecutiveMistakeCount = 0 const provider = task.providerRef.deref() @@ -161,24 +125,22 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> { task.diffViewProvider.originalContent = "" } - if (detectCodeOmission(task.diffViewProvider.originalContent || "", newContent, predictedLineCount)) { + if (detectCodeOmission(task.diffViewProvider.originalContent || "", newContent)) { if (task.diffStrategy) { pushToolResult( formatResponse.toolError( - `Content appears to be truncated (file has ${ - newContent.split("\n").length - } lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`, + `Content appears to contain comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`, ), ) return } else { vscode.window .showWarningMessage( - "Potential code truncation detected. cline happens when the AI reaches its max output limit.", - "Follow cline guide to fix the issue", + "Potential code truncation detected. This happens when the AI reaches its max output limit.", + "Follow guide to fix the issue", ) .then((selection) => { - if (selection === "Follow cline guide to fix the issue") { + if (selection === "Follow guide to fix the issue") { vscode.env.openExternal( vscode.Uri.parse( "https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Cline-Deleting-Code-with-%22Rest-of-Code-Here%22-Comments", @@ -221,26 +183,24 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> { await delay(300) task.diffViewProvider.scrollToFirstDiff() - if (detectCodeOmission(task.diffViewProvider.originalContent || "", newContent, predictedLineCount)) { + if (detectCodeOmission(task.diffViewProvider.originalContent || "", newContent)) { if (task.diffStrategy) { await task.diffViewProvider.revertChanges() pushToolResult( formatResponse.toolError( - `Content appears to be truncated (file has ${ - newContent.split("\n").length - } lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`, + `Content appears to contain comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`, ), ) return } else { vscode.window .showWarningMessage( - "Potential code truncation detected. cline happens when the AI reaches its max output limit.", - "Follow cline guide to fix the issue", + "Potential code truncation detected. This happens when the AI reaches its max output limit.", + "Follow guide to fix the issue", ) .then((selection) => { - if (selection === "Follow cline guide to fix the issue") { + if (selection === "Follow guide to fix the issue") { vscode.env.openExternal( vscode.Uri.parse( "https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Cline-Deleting-Code-with-%22Rest-of-Code-Here%22-Comments", diff --git a/src/core/tools/__tests__/writeToFileTool.spec.ts b/src/core/tools/__tests__/writeToFileTool.spec.ts index 589806a617d..ff9ad76dade 100644 --- a/src/core/tools/__tests__/writeToFileTool.spec.ts +++ b/src/core/tools/__tests__/writeToFileTool.spec.ts @@ -36,9 +36,6 @@ vi.mock("../../prompts/responses", () => ({ formatResponse: { toolError: vi.fn((msg) => `Error: ${msg}`), rooIgnoreError: vi.fn((path) => `Access denied: ${path}`), - lineCountTruncationError: vi.fn( - (count, isNew, diffEnabled) => `Line count error: ${count}, new: ${isNew}, diff: ${diffEnabled}`, - ), createPrettyPatch: vi.fn(() => "mock-diff"), }, })) @@ -224,7 +221,6 @@ describe("writeToFileTool", () => { params: { path: testFilePath, content: testContent, - line_count: "3", ...params, }, partial: isPartial, @@ -383,8 +379,9 @@ describe("writeToFileTool", () => { expect(mockedIsPathOutsideWorkspace).toHaveBeenCalled() }) - it("processes files with very large line counts", async () => { - await executeWriteFileTool({ line_count: "999999" }) + it("processes files with large content", async () => { + const largeContent = "Line\n".repeat(10000) + await executeWriteFileTool({ content: largeContent }) // Should process normally without issues expect(mockCline.consecutiveMistakeCount).toBe(0) diff --git a/src/integrations/editor/__tests__/detect-omission.spec.ts b/src/integrations/editor/__tests__/detect-omission.spec.ts index 3f0ffceb7dc..6ff31c390a5 100644 --- a/src/integrations/editor/__tests__/detect-omission.spec.ts +++ b/src/integrations/editor/__tests__/detect-omission.spec.ts @@ -8,7 +8,8 @@ describe("detectCodeOmission", () => { return x + y; }` - const generateLongContent = (commentLine: string, length: number = 90) => { + // Generate content with a specified number of lines (100+ lines triggers detection) + const generateLongContent = (commentLine: string, length: number = 110) => { return `${commentLine} ${Array.from({ length }, (_, i) => `const x${i} = ${i};`).join("\n")} const y = 2;` @@ -17,126 +18,63 @@ describe("detectCodeOmission", () => { it("should skip comment checks for files under 100 lines", () => { const newContent = `// Lines 1-50 remain unchanged const z = 3;` - const predictedLineCount = 50 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false) + expect(detectCodeOmission(originalContent, newContent)).toBe(false) }) it("should not detect regular comments without omission keywords", () => { const newContent = generateLongContent("// Adding new functionality") - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false) + expect(detectCodeOmission(originalContent, newContent)).toBe(false) }) it("should not detect when comment is part of original content", () => { const originalWithComment = `// Content remains unchanged ${originalContent}` const newContent = generateLongContent("// Content remains unchanged") - const predictedLineCount = 150 - expect(detectCodeOmission(originalWithComment, newContent, predictedLineCount)).toBe(false) + expect(detectCodeOmission(originalWithComment, newContent)).toBe(false) }) it("should not detect code that happens to contain omission keywords", () => { const newContent = generateLongContent(`const remains = 'some value'; const unchanged = true;`) - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false) + expect(detectCodeOmission(originalContent, newContent)).toBe(false) }) - it("should detect suspicious single-line comment when content is more than 20% shorter", () => { + it("should detect suspicious single-line comment for files with 100+ lines", () => { const newContent = generateLongContent("// Previous content remains here\nconst x = 1;") - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true) + expect(detectCodeOmission(originalContent, newContent)).toBe(true) }) - it("should not flag suspicious single-line comment when content is less than 20% shorter", () => { - const newContent = generateLongContent("// Previous content remains here", 130) - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false) - }) - - it("should detect suspicious Python-style comment when content is more than 20% shorter", () => { + it("should detect suspicious Python-style comment for files with 100+ lines", () => { const newContent = generateLongContent("# Previous content remains here\nconst x = 1;") - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true) + expect(detectCodeOmission(originalContent, newContent)).toBe(true) }) - it("should not flag suspicious Python-style comment when content is less than 20% shorter", () => { - const newContent = generateLongContent("# Previous content remains here", 130) - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false) - }) - - it("should detect suspicious multi-line comment when content is more than 20% shorter", () => { + it("should detect suspicious multi-line comment for files with 100+ lines", () => { const newContent = generateLongContent("/* Previous content remains the same */\nconst x = 1;") - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true) - }) - - it("should not flag suspicious multi-line comment when content is less than 20% shorter", () => { - const newContent = generateLongContent("/* Previous content remains the same */", 130) - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false) + expect(detectCodeOmission(originalContent, newContent)).toBe(true) }) - it("should detect suspicious JSX comment when content is more than 20% shorter", () => { + it("should detect suspicious JSX comment for files with 100+ lines", () => { const newContent = generateLongContent("{/* Rest of the code remains the same */}\nconst x = 1;") - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true) - }) - - it("should not flag suspicious JSX comment when content is less than 20% shorter", () => { - const newContent = generateLongContent("{/* Rest of the code remains the same */}", 130) - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false) + expect(detectCodeOmission(originalContent, newContent)).toBe(true) }) - it("should detect suspicious HTML comment when content is more than 20% shorter", () => { + it("should detect suspicious HTML comment for files with 100+ lines", () => { const newContent = generateLongContent("\nconst x = 1;") - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true) - }) - - it("should not flag suspicious HTML comment when content is less than 20% shorter", () => { - const newContent = generateLongContent("", 130) - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false) + expect(detectCodeOmission(originalContent, newContent)).toBe(true) }) - it("should detect suspicious square bracket notation when content is more than 20% shorter", () => { + it("should detect suspicious square bracket notation for files with 100+ lines", () => { const newContent = generateLongContent( "[Previous content from line 1-305 remains exactly the same]\nconst x = 1;", ) - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true) + expect(detectCodeOmission(originalContent, newContent)).toBe(true) }) - it("should not flag suspicious square bracket notation when content is less than 20% shorter", () => { - const newContent = generateLongContent("[Previous content from line 1-305 remains exactly the same]", 130) - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false) - }) - - it("should not flag content very close to predicted length", () => { - const newContent = generateLongContent( - `const x = 1; -const y = 2; -// This is a legitimate comment that remains here`, - 130, - ) - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false) - }) - - it("should not flag when content is longer than predicted", () => { - const newContent = generateLongContent( - `const x = 1; -const y = 2; -// Previous content remains here but we added more -const z = 3; -const w = 4;`, - 160, - ) - const predictedLineCount = 150 - expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false) + it("should not flag legitimate comments in files with 100+ lines when in original", () => { + const originalWithComment = `// This is a legitimate comment that remains here +${originalContent}` + const newContent = generateLongContent("// This is a legitimate comment that remains here") + expect(detectCodeOmission(originalWithComment, newContent)).toBe(false) }) }) diff --git a/src/integrations/editor/detect-omission.ts b/src/integrations/editor/detect-omission.ts index 50bef62281e..d55acd4183c 100644 --- a/src/integrations/editor/detect-omission.ts +++ b/src/integrations/editor/detect-omission.ts @@ -1,23 +1,18 @@ /** * Detects potential AI-generated code omissions in the given file content. + * Looks for comments containing omission keywords that weren't in the original file. * @param originalFileContent The original content of the file. * @param newFileContent The new content of the file to check. - * @param predictedLineCount The predicted number of lines in the new content. * @returns True if a potential omission is detected, false otherwise. */ -export function detectCodeOmission( - originalFileContent: string, - newFileContent: string, - predictedLineCount: number, -): boolean { - // Skip all checks if predictedLineCount is less than 100 - if (!predictedLineCount || predictedLineCount < 100) { +export function detectCodeOmission(originalFileContent: string, newFileContent: string): boolean { + const actualLineCount = newFileContent.split("\n").length + + // Skip checks for small files (less than 100 lines) + if (actualLineCount < 100) { return false } - const actualLineCount = newFileContent.split("\n").length - const lengthRatio = actualLineCount / predictedLineCount - const originalLines = originalFileContent.split("\n") const newLines = newFileContent.split("\n") const omissionKeywords = [ @@ -48,10 +43,7 @@ export function detectCodeOmission( const words = line.toLowerCase().split(/\s+/) if (omissionKeywords.some((keyword) => words.includes(keyword))) { if (!originalLines.includes(line)) { - // For files with 100+ lines, only flag if content is more than 20% shorter - if (lengthRatio <= 0.8) { - return true - } + return true } } } diff --git a/src/shared/tools.ts b/src/shared/tools.ts index e993c81dd9c..b754bb36149 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -39,7 +39,6 @@ export const toolParamNames = [ "command", "path", "content", - "line_count", "regex", "file_pattern", "recursive", @@ -106,7 +105,7 @@ export type NativeToolArgs = { switch_mode: { mode_slug: string; reason: string } update_todo_list: { todos: string } use_mcp_tool: { server_name: string; tool_name: string; arguments?: Record } - write_to_file: { path: string; content: string; line_count: number } + write_to_file: { path: string; content: string } // Add more tools as they are migrated to native protocol } @@ -164,7 +163,7 @@ export interface FetchInstructionsToolUse extends ToolUse<"fetch_instructions"> export interface WriteToFileToolUse extends ToolUse<"write_to_file"> { name: "write_to_file" - params: Partial, "path" | "content" | "line_count">> + params: Partial, "path" | "content">> } export interface InsertCodeBlockToolUse extends ToolUse<"insert_content"> { From 731a4894af5421e059344f482d6f54ae99a0d46b Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Fri, 28 Nov 2025 01:02:55 -0700 Subject: [PATCH 2/8] Fix: Add missing mergeEnvironmentDetails utility --- src/utils/mergeEnvironmentDetails.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/utils/mergeEnvironmentDetails.ts diff --git a/src/utils/mergeEnvironmentDetails.ts b/src/utils/mergeEnvironmentDetails.ts new file mode 100644 index 00000000000..da4d5e9f30b --- /dev/null +++ b/src/utils/mergeEnvironmentDetails.ts @@ -0,0 +1,12 @@ +import { Anthropic } from "@anthropic-ai/sdk" + +export function isMergeEnvironmentDetailsMergeNeeded(modelId: string): boolean { + return false +} + +export function mergeEnvironmentDetailsIntoUserContent( + content: Anthropic.Messages.ContentBlockParam[], + environmentDetails: string, +): Anthropic.Messages.ContentBlockParam[] { + return [...content, { type: "text", text: environmentDetails }] +} From a0bd3b6ed45cbc26e57652f8c6d96ddc1c45bbf7 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Fri, 28 Nov 2025 01:03:58 -0700 Subject: [PATCH 3/8] Fix: Allow modelId to be undefined in mergeEnvironmentDetails --- src/utils/mergeEnvironmentDetails.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/mergeEnvironmentDetails.ts b/src/utils/mergeEnvironmentDetails.ts index da4d5e9f30b..e90846e1e2f 100644 --- a/src/utils/mergeEnvironmentDetails.ts +++ b/src/utils/mergeEnvironmentDetails.ts @@ -1,6 +1,6 @@ import { Anthropic } from "@anthropic-ai/sdk" -export function isMergeEnvironmentDetailsMergeNeeded(modelId: string): boolean { +export function isMergeEnvironmentDetailsMergeNeeded(modelId?: string): boolean { return false } From 00b26f695b40f6ead998f4cd70670bdd02b6abb6 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Fri, 28 Nov 2025 01:07:53 -0700 Subject: [PATCH 4/8] chore: remove unused mergeEnvironmentDetails utility --- src/utils/mergeEnvironmentDetails.ts | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 src/utils/mergeEnvironmentDetails.ts diff --git a/src/utils/mergeEnvironmentDetails.ts b/src/utils/mergeEnvironmentDetails.ts deleted file mode 100644 index e90846e1e2f..00000000000 --- a/src/utils/mergeEnvironmentDetails.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Anthropic } from "@anthropic-ai/sdk" - -export function isMergeEnvironmentDetailsMergeNeeded(modelId?: string): boolean { - return false -} - -export function mergeEnvironmentDetailsIntoUserContent( - content: Anthropic.Messages.ContentBlockParam[], - environmentDetails: string, -): Anthropic.Messages.ContentBlockParam[] { - return [...content, { type: "text", text: environmentDetails }] -} From c48ac84ab3b423416a3c13b82fc4a74c6e5a6781 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Fri, 28 Nov 2025 01:33:27 -0700 Subject: [PATCH 5/8] fix: add missing mergeEnvironmentDetails utility file --- src/utils/mergeEnvironmentDetails.ts | 64 ++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/utils/mergeEnvironmentDetails.ts diff --git a/src/utils/mergeEnvironmentDetails.ts b/src/utils/mergeEnvironmentDetails.ts new file mode 100644 index 00000000000..1e285e408cd --- /dev/null +++ b/src/utils/mergeEnvironmentDetails.ts @@ -0,0 +1,64 @@ +import { Anthropic } from "@anthropic-ai/sdk" + +/** + * Checks if a model needs environment details merged into user content + * rather than added as a separate text block. + * + * Some models have limitations with multiple text blocks in a single message, + * so environment details need to be merged into existing content. + * + * @param modelId - The model identifier string (can be undefined) + * @returns true if environment details should be merged, false otherwise + */ +export function isMergeEnvironmentDetailsMergeNeeded(modelId: string | undefined): boolean { + // Currently no models require this special handling + // This function exists to allow easy addition of model-specific behavior + // without modifying the main Task.ts logic + return false +} + +/** + * Merges environment details into user content by appending to the last text block + * or adding as a new text block if no text block exists. + * + * This is used for models that have issues with multiple separate text blocks + * in a single user message. + * + * @param content - The existing user content blocks + * @param environmentDetails - The environment details string to merge + * @returns The merged content blocks + */ +export function mergeEnvironmentDetailsIntoUserContent( + content: Anthropic.Messages.ContentBlockParam[], + environmentDetails: string, +): Anthropic.Messages.ContentBlockParam[] { + if (content.length === 0) { + return [{ type: "text" as const, text: environmentDetails }] + } + + // Find the last text block to append to (using reverse iteration for ES2020 compatibility) + let lastTextBlockIndex = -1 + for (let i = content.length - 1; i >= 0; i--) { + if (content[i].type === "text") { + lastTextBlockIndex = i + break + } + } + + if (lastTextBlockIndex === -1) { + // No text blocks, add environment details as a new text block + return [...content, { type: "text" as const, text: environmentDetails }] + } + + // Clone the content array to avoid mutation + const result = [...content] + + // Append environment details to the last text block + const lastTextBlock = result[lastTextBlockIndex] as Anthropic.Messages.TextBlockParam + result[lastTextBlockIndex] = { + type: "text" as const, + text: `${lastTextBlock.text}\n\n${environmentDetails}`, + } + + return result +} From 6394ac402bff67319348c5cc5572ae411fd75ff4 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Tue, 2 Dec 2025 08:36:33 -0700 Subject: [PATCH 6/8] ci: retrigger CI checks From 8c6434e3dc773fa3d77824d6526f3ab16b0b95d9 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Tue, 2 Dec 2025 09:28:32 -0700 Subject: [PATCH 7/8] Delete src/utils/mergeEnvironmentDetails.ts --- src/utils/mergeEnvironmentDetails.ts | 64 ---------------------------- 1 file changed, 64 deletions(-) delete mode 100644 src/utils/mergeEnvironmentDetails.ts diff --git a/src/utils/mergeEnvironmentDetails.ts b/src/utils/mergeEnvironmentDetails.ts deleted file mode 100644 index 1e285e408cd..00000000000 --- a/src/utils/mergeEnvironmentDetails.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Anthropic } from "@anthropic-ai/sdk" - -/** - * Checks if a model needs environment details merged into user content - * rather than added as a separate text block. - * - * Some models have limitations with multiple text blocks in a single message, - * so environment details need to be merged into existing content. - * - * @param modelId - The model identifier string (can be undefined) - * @returns true if environment details should be merged, false otherwise - */ -export function isMergeEnvironmentDetailsMergeNeeded(modelId: string | undefined): boolean { - // Currently no models require this special handling - // This function exists to allow easy addition of model-specific behavior - // without modifying the main Task.ts logic - return false -} - -/** - * Merges environment details into user content by appending to the last text block - * or adding as a new text block if no text block exists. - * - * This is used for models that have issues with multiple separate text blocks - * in a single user message. - * - * @param content - The existing user content blocks - * @param environmentDetails - The environment details string to merge - * @returns The merged content blocks - */ -export function mergeEnvironmentDetailsIntoUserContent( - content: Anthropic.Messages.ContentBlockParam[], - environmentDetails: string, -): Anthropic.Messages.ContentBlockParam[] { - if (content.length === 0) { - return [{ type: "text" as const, text: environmentDetails }] - } - - // Find the last text block to append to (using reverse iteration for ES2020 compatibility) - let lastTextBlockIndex = -1 - for (let i = content.length - 1; i >= 0; i--) { - if (content[i].type === "text") { - lastTextBlockIndex = i - break - } - } - - if (lastTextBlockIndex === -1) { - // No text blocks, add environment details as a new text block - return [...content, { type: "text" as const, text: environmentDetails }] - } - - // Clone the content array to avoid mutation - const result = [...content] - - // Append environment details to the last text block - const lastTextBlock = result[lastTextBlockIndex] as Anthropic.Messages.TextBlockParam - result[lastTextBlockIndex] = { - type: "text" as const, - text: `${lastTextBlock.text}\n\n${environmentDetails}`, - } - - return result -} From 8879980de8ec3b00e6ec8301359aaa4ea32ede4f Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Tue, 2 Dec 2025 09:36:56 -0700 Subject: [PATCH 8/8] fix: remove mergeEnvironmentDetails import and usage --- src/core/task/Task.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 520d7c34308..50b822de8fa 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -83,10 +83,6 @@ import { TerminalRegistry } from "../../integrations/terminal/TerminalRegistry" // utils import { calculateApiCostAnthropic, calculateApiCostOpenAI } from "../../shared/cost" import { getWorkspacePath } from "../../utils/path" -import { - isMergeEnvironmentDetailsMergeNeeded, - mergeEnvironmentDetailsIntoUserContent, -} from "../../utils/mergeEnvironmentDetails" // prompts import { formatResponse } from "../prompts/responses" @@ -2162,9 +2158,6 @@ export class Task extends EventEmitter implements TaskLike { // Add environment details as its own text block, separate from tool // results. let finalUserContent = [...contentWithoutEnvDetails, { type: "text" as const, text: environmentDetails }] - if (isMergeEnvironmentDetailsMergeNeeded(modelId)) { - finalUserContent = mergeEnvironmentDetailsIntoUserContent(contentWithoutEnvDetails, environmentDetails) - } // Only add user message to conversation history if: // 1. This is the first attempt (retryAttempt === 0), AND // 2. The original userContent was not empty (empty signals delegation resume where