From def40180dcfb08bd5f5e3658b610995993a03a9a Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 21 Mar 2025 15:47:13 -0400 Subject: [PATCH 01/23] Don't do partial reads on binary files (#1886) --- src/core/Cline.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index b7bfc021183..20af9ee565c 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -82,6 +82,7 @@ import { validateToolUse, isToolAllowedForMode, ToolName } from "./mode-validato import { parseXml } from "../utils/xml" import { readLines } from "../integrations/misc/read-lines" import { getWorkspacePath } from "../utils/path" +import { isBinaryFile } from "isbinaryfile" type ToolResponse = string | Array type UserContent = Array @@ -2324,6 +2325,8 @@ export class Cline extends EventEmitter { let isFileTruncated = false let sourceCodeDef = "" + const isBinary = await isBinaryFile(absolutePath).catch(() => false) + if (isRangeRead) { if (startLine === undefined) { content = addLineNumbers(await readLines(absolutePath, endLine, startLine)) @@ -2333,7 +2336,7 @@ export class Cline extends EventEmitter { startLine, ) } - } else if (totalLines > maxReadFileLine) { + } else if (!isBinary && totalLines > maxReadFileLine) { // If file is too large, only read the first maxReadFileLine lines isFileTruncated = true From 81692a43c951f2795cfa860f7c0e5f2155044e51 Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Thu, 20 Mar 2025 21:10:54 -0700 Subject: [PATCH 02/23] fix: escape section markers in apply_diff Provide support for escaping section markers so that the model can add or remove lines like: ======= by escaping them in the search or replace string: \======= A state machine tracks apply_diff markers appear in correct sequence: SEARCH -> SEPARATOR -> REPLACE. Prevents syntax corruption from interleaved or malformed blocks by validating before processing matches. If a model tries to interleave diff markers, then the state machine will return a response to the model like this so it can correct. testing shows that this works on Claude 3.5, 3.7 and gemini-2.0-flash-thinking: ```xml ERROR: Special marker '=======' found in your diff content at line 7: When removing merge conflict markers like '=======' from files, you MUST escape them in your SEARCH section by prepending a backslash (\) at the beginning of the line: CORRECT FORMAT: <<<<<<< SEARCH content before \======= <-- Note the backslash here in this example content after ======= replacement content >>>>>>> REPLACE Without escaping, the system confuses your content with diff syntax markers. You may use multiple diff blocks in a single diff request, but ANY of ONLY the following separators that occur within SEARCH or REPLACE content must be must be escaped, as follows: \<<<<<<< SEARCH \======= \>>>>>>> REPLACE ``` Fixes: #1557 Fixes: #1408 Signed-off-by: Eric Wheeler --- .../diff/strategies/multi-search-replace.ts | 95 ++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index ad12448d046..852d3dcfbce 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -73,6 +73,7 @@ Diff format: \`\`\` + Example: Original file: @@ -128,6 +129,7 @@ def calculate_sum(items): >>>>>>> REPLACE \`\`\` + Usage: File path here @@ -139,15 +141,102 @@ Only use a single line of '=======' between search and replacement content, beca ` } + private unescapeMarkers(content: string): string { + return content + .replace(/^\\<<<<<<< SEARCH/gm, "<<<<<<< SEARCH") + .replace(/^\\=======/gm, "=======") + .replace(/^\\>>>>>>> REPLACE/gm, ">>>>>>> REPLACE") + .replace(/^\\-------/gm, "-------") + .replace(/^\\:end_line:/gm, ":end_line:") + .replace(/^\\:start_line:/gm, ":start_line:") + } + + private validateMarkerSequencing(diffContent: string): { success: boolean; error?: string } { + enum State { + START, + AFTER_SEARCH, + AFTER_SEPARATOR, + } + const state = { current: State.START, line: 0 } + + const SEARCH = "<<<<<<< SEARCH" + const SEP = "=======" + const REPLACE = ">>>>>>> REPLACE" + + const reportError = (found: string, expected: string) => ({ + success: false, + error: + `ERROR: Special marker '${found}' found in your diff content at line ${state.line}:\n` + + "\n" + + `When removing merge conflict markers like '${found}' from files, you MUST escape them\n` + + "in your SEARCH section by prepending a backslash (\\) at the beginning of the line:\n" + + "\n" + + "CORRECT FORMAT:\n\n" + + "<<<<<<< SEARCH\n" + + "content before\n" + + `\\${found} <-- Note the backslash here in this example\n` + + "content after\n" + + "=======\n" + + "replacement content\n" + + ">>>>>>> REPLACE\n" + + "\n" + + "Without escaping, the system confuses your content with diff syntax markers.\n" + + "You may use multiple diff blocks in a single diff request, but ANY of ONLY the following separators that occur within SEARCH or REPLACE content must be escaped, as follows:\n" + + `\\${SEARCH}\n` + + `\\${SEP}\n` + + `\\${REPLACE}\n`, + }) + + for (const line of diffContent.split("\n")) { + state.line++ + const marker = line.trim() + + switch (state.current) { + case State.START: + if (marker === SEP) return reportError(SEP, SEARCH) + if (marker === REPLACE) return reportError(REPLACE, SEARCH) + if (marker === SEARCH) state.current = State.AFTER_SEARCH + break + + case State.AFTER_SEARCH: + if (marker === SEARCH) return reportError(SEARCH, SEP) + if (marker === REPLACE) return reportError(REPLACE, SEP) + if (marker === SEP) state.current = State.AFTER_SEPARATOR + break + + case State.AFTER_SEPARATOR: + if (marker === SEARCH) return reportError(SEARCH, REPLACE) + if (marker === SEP) return reportError(SEP, REPLACE) + if (marker === REPLACE) state.current = State.START + break + } + } + + return state.current === State.START + ? { success: true } + : { + success: false, + error: `ERROR: Unexpected end of sequence: Expected '${state.current === State.AFTER_SEARCH ? SEP : REPLACE}' was not found.`, + } + } + async applyDiff( originalContent: string, diffContent: string, _paramStartLine?: number, _paramEndLine?: number, ): Promise { + const validseq = this.validateMarkerSequencing(diffContent) + if (!validseq.success) { + return { + success: false, + error: validseq.error!, + } + } + let matches = [ ...diffContent.matchAll( - /<<<<<<< SEARCH\n(:start_line:\s*(\d+)\n){0,1}(:end_line:\s*(\d+)\n){0,1}(-------\n){0,1}([\s\S]*?)\n?=======\n([\s\S]*?)\n?>>>>>>> REPLACE/g, + /(?>>>>>> REPLACE/g, ), ] @@ -176,6 +265,10 @@ Only use a single line of '=======' between search and replacement content, beca startLine += startLine === 0 ? 0 : delta endLine += delta + // First unescape any escaped markers in the content + searchContent = this.unescapeMarkers(searchContent) + replaceContent = this.unescapeMarkers(replaceContent) + // Strip line numbers from search and replace content if every line starts with a line number if (everyLineHasLineNumbers(searchContent) && everyLineHasLineNumbers(replaceContent)) { searchContent = stripLineNumbers(searchContent) From 1b8bac66fdd5b72bbbe3564fde5194331dc97079 Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Thu, 20 Mar 2025 21:12:37 -0700 Subject: [PATCH 03/23] fix: pass multi-block flag to getDiffStrategy in system instructions When previewing system instructions, getDiffStrategy was not receiving the MULTI_SEARCH_AND_REPLACE flag, causing the instructions to show the wrong diff strategy description. Now correctly passing the flag to ensure the proper diff strategy description is shown in system instructions. Signed-off-by: Eric Wheeler --- src/core/webview/ClineProvider.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index fc7d029cf82..6777642c2e4 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2046,6 +2046,7 @@ export class ClineProvider extends EventEmitter implements apiConfiguration.apiModelId || apiConfiguration.openRouterModelId || "", fuzzyMatchThreshold, Experiments.isEnabled(experiments, EXPERIMENT_IDS.DIFF_STRATEGY), + Experiments.isEnabled(experiments, EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE), ) const cwd = this.cwd From c7321b010729873de9716b831c2c0d361f4acd72 Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Thu, 20 Mar 2025 22:08:23 -0700 Subject: [PATCH 04/23] fix: reject apply_diff when search matches replace content Prevents attempting to apply diffs where search and replace content are identical, which would result in no changes being made. Instead, provide a helpful error message explaining why the operation was rejected. Fixes: #1350 Signed-off-by: Eric Wheeler --- src/core/diff/strategies/multi-search-replace.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index 852d3dcfbce..238d218e627 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -275,6 +275,19 @@ Only use a single line of '=======' between search and replacement content, beca replaceContent = stripLineNumbers(replaceContent) } + // Validate that search and replace content are not identical + if (searchContent === replaceContent) { + diffResults.push({ + success: false, + error: + `Search and replace content are identical - no changes would be made\n\n` + + `Debug Info:\n` + + `- Search and replace must be different to make changes\n` + + `- Use read_file to verify the content you want to change`, + }) + continue + } + // Split content into lines, handling both \n and \r\n const searchLines = searchContent === "" ? [] : searchContent.split(/\r?\n/) const replaceLines = replaceContent === "" ? [] : replaceContent.split(/\r?\n/) From 4b3f7117460f64ebe2e3d2337841aed89105c677 Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Fri, 21 Mar 2025 19:41:41 -0700 Subject: [PATCH 05/23] fix: enforce newlines between diff section separators Require newlines between diff section markers (SEARCH, ======, REPLACE) to prevent content confusion when searching input contains separator markers. Error message mentions required marker newlines. Signed-off-by: Eric Wheeler --- .../__tests__/multi-search-replace.test.ts | 2 -- .../diff/strategies/multi-search-replace.ts | 35 +++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/core/diff/strategies/__tests__/multi-search-replace.test.ts b/src/core/diff/strategies/__tests__/multi-search-replace.test.ts index 8fc16d2303e..20bb6e8f7e0 100644 --- a/src/core/diff/strategies/__tests__/multi-search-replace.test.ts +++ b/src/core/diff/strategies/__tests__/multi-search-replace.test.ts @@ -980,7 +980,6 @@ function test() { :end_line:4 ------- ======= - // End of file >>>>>>> REPLACE` @@ -990,7 +989,6 @@ function test() { expect(result.content).toBe(`function test() { return true; } - // End of file`) } }) diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index 238d218e627..bf80f090528 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -234,16 +234,47 @@ Only use a single line of '=======' between search and replacement content, beca } } + /* + Regex parts: + + 1. (?:^|\n) +   Ensures the first marker starts at the beginning of the file or right after a newline. + + 2. (?>>>>>> REPLACE)(?=\n|$) +   Matches the final “>>>>>>> REPLACE” marker on its own line (and requires a following newline or the end of file). + */ + let matches = [ ...diffContent.matchAll( - /(?>>>>>> REPLACE/g, + /(?:^|\n)(?>>>>>> REPLACE)(?=\n|$)/g, ), ] if (matches.length === 0) { return { success: false, - error: `Invalid diff format - missing required sections\n\nDebug Info:\n- Expected Format: <<<<<<< SEARCH\\n:start_line: start line\\n:end_line: end line\\n-------\\n[search content]\\n=======\\n[replace content]\\n>>>>>>> REPLACE\n- Tip: Make sure to include start_line/end_line/SEARCH/REPLACE sections with correct markers`, + error: `Invalid diff format - missing required sections\n\nDebug Info:\n- Expected Format: <<<<<<< SEARCH\\n:start_line: start line\\n:end_line: end line\\n-------\\n[search content]\\n=======\\n[replace content]\\n>>>>>>> REPLACE\n- Tip: Make sure to include start_line/end_line/SEARCH/=======/REPLACE sections with correct markers on new lines`, } } // Detect line ending from original content From 2032c9e45ff41480cb643f5bfbc2d1ee59741e5b Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Fri, 21 Mar 2025 19:51:20 -0700 Subject: [PATCH 06/23] test: add validateMarkerSequencing test cases Tests valid and invalid marker sequences: - validates single and multiple complete sequences - detects out-of-order markers (separator/replace before search) - detects incorrect sequence termination - validates state transitions between SEARCH/SEP/REPLACE markers Signed-off-by: Eric Wheeler --- .../__tests__/multi-search-replace.test.ts | 589 ++++++++++-------- 1 file changed, 326 insertions(+), 263 deletions(-) diff --git a/src/core/diff/strategies/__tests__/multi-search-replace.test.ts b/src/core/diff/strategies/__tests__/multi-search-replace.test.ts index 20bb6e8f7e0..a53b179d982 100644 --- a/src/core/diff/strategies/__tests__/multi-search-replace.test.ts +++ b/src/core/diff/strategies/__tests__/multi-search-replace.test.ts @@ -1,16 +1,64 @@ import { MultiSearchReplaceDiffStrategy } from "../multi-search-replace" describe("MultiSearchReplaceDiffStrategy", () => { - describe("exact matching", () => { + describe("validateMarkerSequencing", () => { let strategy: MultiSearchReplaceDiffStrategy beforeEach(() => { - strategy = new MultiSearchReplaceDiffStrategy(1.0, 5) // Default 1.0 threshold for exact matching, 5 line buffer for tests + strategy = new MultiSearchReplaceDiffStrategy() }) - it("should replace matching content", async () => { - const originalContent = 'function hello() {\n console.log("hello")\n}\n' - const diffContent = `test.ts + it("validates correct marker sequence", () => { + const diff = "<<<<<<< SEARCH\n" + "some content\n" + "=======\n" + "new content\n" + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + + it("validates multiple correct marker sequences", () => { + const diff = + "<<<<<<< SEARCH\n" + + "content1\n" + + "=======\n" + + "new1\n" + + ">>>>>>> REPLACE\n\n" + + "<<<<<<< SEARCH\n" + + "content2\n" + + "=======\n" + + "new2\n" + + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + + it("detects separator before search", () => { + const diff = "=======\n" + "content\n" + ">>>>>>> REPLACE" + const result = strategy["validateMarkerSequencing"](diff) + expect(result.success).toBe(false) + expect(result.error).toContain("'=======' found in your diff content") + }) + + it("detects replace before separator", () => { + const diff = "<<<<<<< SEARCH\n" + "content\n" + ">>>>>>> REPLACE" + const result = strategy["validateMarkerSequencing"](diff) + expect(result.success).toBe(false) + expect(result.error).toContain("'>>>>>>> REPLACE' found in your diff content") + }) + + it("detects incomplete sequence", () => { + const diff = "<<<<<<< SEARCH\n" + "content\n" + "=======\n" + "new content" + const result = strategy["validateMarkerSequencing"](diff) + expect(result.success).toBe(false) + expect(result.error).toContain("Expected '>>>>>>> REPLACE' was not found") + }) + + describe("exact matching", () => { + let strategy: MultiSearchReplaceDiffStrategy + + beforeEach(() => { + strategy = new MultiSearchReplaceDiffStrategy(1.0, 5) // Default 1.0 threshold for exact matching, 5 line buffer for tests + }) + + it("should replace matching content", async () => { + const originalContent = 'function hello() {\n console.log("hello")\n}\n' + const diffContent = `test.ts <<<<<<< SEARCH function hello() { console.log("hello") @@ -21,16 +69,16 @@ function hello() { } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe('function hello() {\n console.log("hello world")\n}\n') - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe('function hello() {\n console.log("hello world")\n}\n') + } + }) - it("should match content with different surrounding whitespace", async () => { - const originalContent = "\nfunction example() {\n return 42;\n}\n\n" - const diffContent = `test.ts + it("should match content with different surrounding whitespace", async () => { + const originalContent = "\nfunction example() {\n return 42;\n}\n\n" + const diffContent = `test.ts <<<<<<< SEARCH function example() { return 42; @@ -41,16 +89,16 @@ function example() { } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("\nfunction example() {\n return 43;\n}\n\n") - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("\nfunction example() {\n return 43;\n}\n\n") + } + }) - it("should match content with different indentation in search block", async () => { - const originalContent = " function test() {\n return true;\n }\n" - const diffContent = `test.ts + it("should match content with different indentation in search block", async () => { + const originalContent = " function test() {\n return true;\n }\n" + const diffContent = `test.ts <<<<<<< SEARCH function test() { return true; @@ -61,16 +109,16 @@ function test() { } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(" function test() {\n return false;\n }\n") - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe(" function test() {\n return false;\n }\n") + } + }) - it("should handle tab-based indentation", async () => { - const originalContent = "function test() {\n\treturn true;\n}\n" - const diffContent = `test.ts + it("should handle tab-based indentation", async () => { + const originalContent = "function test() {\n\treturn true;\n}\n" + const diffContent = `test.ts <<<<<<< SEARCH function test() { \treturn true; @@ -81,16 +129,16 @@ function test() { } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\n\treturn false;\n}\n") - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("function test() {\n\treturn false;\n}\n") + } + }) - it("should preserve mixed tabs and spaces", async () => { - const originalContent = "\tclass Example {\n\t constructor() {\n\t\tthis.value = 0;\n\t }\n\t}" - const diffContent = `test.ts + it("should preserve mixed tabs and spaces", async () => { + const originalContent = "\tclass Example {\n\t constructor() {\n\t\tthis.value = 0;\n\t }\n\t}" + const diffContent = `test.ts <<<<<<< SEARCH \tclass Example { \t constructor() { @@ -105,18 +153,18 @@ function test() { \t} >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - "\tclass Example {\n\t constructor() {\n\t\tthis.value = 1;\n\t }\n\t}", - ) - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe( + "\tclass Example {\n\t constructor() {\n\t\tthis.value = 1;\n\t }\n\t}", + ) + } + }) - it("should handle additional indentation with tabs", async () => { - const originalContent = "\tfunction test() {\n\t\treturn true;\n\t}" - const diffContent = `test.ts + it("should handle additional indentation with tabs", async () => { + const originalContent = "\tfunction test() {\n\t\treturn true;\n\t}" + const diffContent = `test.ts <<<<<<< SEARCH function test() { \treturn true; @@ -128,16 +176,16 @@ function test() { } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("\tfunction test() {\n\t\t// Add comment\n\t\treturn false;\n\t}") - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("\tfunction test() {\n\t\t// Add comment\n\t\treturn false;\n\t}") + } + }) - it("should preserve exact indentation characters when adding lines", async () => { - const originalContent = "\tfunction test() {\n\t\treturn true;\n\t}" - const diffContent = `test.ts + it("should preserve exact indentation characters when adding lines", async () => { + const originalContent = "\tfunction test() {\n\t\treturn true;\n\t}" + const diffContent = `test.ts <<<<<<< SEARCH \tfunction test() { \t\treturn true; @@ -150,18 +198,18 @@ function test() { \t} >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - "\tfunction test() {\n\t\t// First comment\n\t\t// Second comment\n\t\treturn true;\n\t}", - ) - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe( + "\tfunction test() {\n\t\t// First comment\n\t\t// Second comment\n\t\treturn true;\n\t}", + ) + } + }) - it("should handle Windows-style CRLF line endings", async () => { - const originalContent = "function test() {\r\n return true;\r\n}\r\n" - const diffContent = `test.ts + it("should handle Windows-style CRLF line endings", async () => { + const originalContent = "function test() {\r\n return true;\r\n}\r\n" + const diffContent = `test.ts <<<<<<< SEARCH function test() { return true; @@ -172,16 +220,16 @@ function test() { } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\r\n return false;\r\n}\r\n") - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("function test() {\r\n return false;\r\n}\r\n") + } + }) - it("should return false if search content does not match", async () => { - const originalContent = 'function hello() {\n console.log("hello")\n}\n' - const diffContent = `test.ts + it("should return false if search content does not match", async () => { + const originalContent = 'function hello() {\n console.log("hello")\n}\n' + const diffContent = `test.ts <<<<<<< SEARCH function hello() { console.log("wrong") @@ -192,22 +240,22 @@ function hello() { } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(false) + }) - it("should return false if diff format is invalid", async () => { - const originalContent = 'function hello() {\n console.log("hello")\n}\n' - const diffContent = `test.ts\nInvalid diff format` + it("should return false if diff format is invalid", async () => { + const originalContent = 'function hello() {\n console.log("hello")\n}\n' + const diffContent = `test.ts\nInvalid diff format` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(false) + }) - it("should handle multiple lines with proper indentation", async () => { - const originalContent = - "class Example {\n constructor() {\n this.value = 0\n }\n\n getValue() {\n return this.value\n }\n}\n" - const diffContent = `test.ts + it("should handle multiple lines with proper indentation", async () => { + const originalContent = + "class Example {\n constructor() {\n this.value = 0\n }\n\n getValue() {\n return this.value\n }\n}\n" + const diffContent = `test.ts <<<<<<< SEARCH getValue() { return this.value @@ -220,18 +268,18 @@ function hello() { } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - 'class Example {\n constructor() {\n this.value = 0\n }\n\n getValue() {\n // Add logging\n console.log("Getting value")\n return this.value\n }\n}\n', - ) - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe( + 'class Example {\n constructor() {\n this.value = 0\n }\n\n getValue() {\n // Add logging\n console.log("Getting value")\n return this.value\n }\n}\n', + ) + } + }) - it("should preserve whitespace exactly in the output", async () => { - const originalContent = " indented\n more indented\n back\n" - const diffContent = `test.ts + it("should preserve whitespace exactly in the output", async () => { + const originalContent = " indented\n more indented\n back\n" + const diffContent = `test.ts <<<<<<< SEARCH indented more indented @@ -242,16 +290,16 @@ function hello() { end >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(" modified\n still indented\n end\n") - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe(" modified\n still indented\n end\n") + } + }) - it("should preserve indentation when adding new lines after existing content", async () => { - const originalContent = " onScroll={() => updateHighlights()}" - const diffContent = `test.ts + it("should preserve indentation when adding new lines after existing content", async () => { + const originalContent = " onScroll={() => updateHighlights()}" + const diffContent = `test.ts <<<<<<< SEARCH onScroll={() => updateHighlights()} ======= @@ -262,17 +310,17 @@ function hello() { }} >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - " onScroll={() => updateHighlights()}\n onDragOver={(e) => {\n e.preventDefault()\n e.stopPropagation()\n }}", - ) - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe( + " onScroll={() => updateHighlights()}\n onDragOver={(e) => {\n e.preventDefault()\n e.stopPropagation()\n }}", + ) + } + }) - it("should handle varying indentation levels correctly", async () => { - const originalContent = ` + it("should handle varying indentation levels correctly", async () => { + const originalContent = ` class Example { constructor() { this.value = 0; @@ -282,7 +330,7 @@ class Example { } }`.trim() - const diffContent = `test.ts + const diffContent = `test.ts <<<<<<< SEARCH class Example { constructor() { @@ -305,11 +353,11 @@ class Example { } >>>>>>> REPLACE`.trim() - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - ` + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe( + ` class Example { constructor() { this.value = 1; @@ -320,12 +368,12 @@ class Example { } } }`.trim(), - ) - } - }) + ) + } + }) - it("should handle mixed indentation styles in the same file", async () => { - const originalContent = `class Example { + it("should handle mixed indentation styles in the same file", async () => { + const originalContent = `class Example { constructor() { this.value = 0; if (true) { @@ -333,7 +381,7 @@ class Example { } } }`.trim() - const diffContent = `test.ts + const diffContent = `test.ts <<<<<<< SEARCH constructor() { this.value = 0; @@ -351,10 +399,10 @@ class Example { } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe(`class Example { constructor() { this.value = 1; if (true) { @@ -363,17 +411,17 @@ class Example { } } }`) - } - }) + } + }) - it("should handle Python-style significant whitespace", async () => { - const originalContent = `def example(): + it("should handle Python-style significant whitespace", async () => { + const originalContent = `def example(): if condition: do_something() for item in items: process(item) return True`.trim() - const diffContent = `test.ts + const diffContent = `test.ts <<<<<<< SEARCH if condition: do_something() @@ -387,28 +435,28 @@ class Example { process(item) >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`def example(): + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe(`def example(): if condition: do_something() while items: item = items.pop() process(item) return True`) - } - }) + } + }) - it("should preserve empty lines with indentation", async () => { - const originalContent = `function test() { + it("should preserve empty lines with indentation", async () => { + const originalContent = `function test() { const x = 1; if (x) { return true; } }`.trim() - const diffContent = `test.ts + const diffContent = `test.ts <<<<<<< SEARCH const x = 1; @@ -420,10 +468,10 @@ class Example { if (x) { >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function test() { + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe(`function test() { const x = 1; // Check x @@ -431,18 +479,18 @@ class Example { return true; } }`) - } - }) + } + }) - it("should handle indentation when replacing entire blocks", async () => { - const originalContent = `class Test { + it("should handle indentation when replacing entire blocks", async () => { + const originalContent = `class Test { method() { if (true) { console.log("test"); } } }`.trim() - const diffContent = `test.ts + const diffContent = `test.ts <<<<<<< SEARCH method() { if (true) { @@ -461,10 +509,10 @@ class Example { } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Test { + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe(`class Test { method() { try { if (true) { @@ -475,11 +523,11 @@ class Example { } } }`) - } - }) + } + }) - it("should handle negative indentation relative to search content", async () => { - const originalContent = `class Example { + it("should handle negative indentation relative to search content", async () => { + const originalContent = `class Example { constructor() { if (true) { this.init(); @@ -487,7 +535,7 @@ class Example { } } }`.trim() - const diffContent = `test.ts + const diffContent = `test.ts <<<<<<< SEARCH this.init(); this.setup(); @@ -496,10 +544,10 @@ class Example { this.setup(); >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe(`class Example { constructor() { if (true) { this.init(); @@ -507,39 +555,39 @@ class Example { } } }`) - } - }) + } + }) - it("should handle extreme negative indentation (no indent)", async () => { - const originalContent = `class Example { + it("should handle extreme negative indentation (no indent)", async () => { + const originalContent = `class Example { constructor() { if (true) { this.init(); } } }`.trim() - const diffContent = `test.ts + const diffContent = `test.ts <<<<<<< SEARCH this.init(); ======= this.init(); >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe(`class Example { constructor() { if (true) { this.init(); } } }`) - } - }) + } + }) - it("should handle mixed indentation changes in replace block", async () => { - const originalContent = `class Example { + it("should handle mixed indentation changes in replace block", async () => { + const originalContent = `class Example { constructor() { if (true) { this.init(); @@ -548,7 +596,7 @@ this.init(); } } }`.trim() - const diffContent = `test.ts + const diffContent = `test.ts <<<<<<< SEARCH this.init(); this.setup(); @@ -559,10 +607,10 @@ this.init(); this.validate(); >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe(`class Example { constructor() { if (true) { this.init(); @@ -571,11 +619,11 @@ this.init(); } } }`) - } - }) + } + }) - it("should find matches from middle out", async () => { - const originalContent = ` + it("should find matches from middle out", async () => { + const originalContent = ` function one() { return "target"; } @@ -596,20 +644,20 @@ function five() { return "target"; }`.trim() - const diffContent = `test.ts + const diffContent = `test.ts <<<<<<< SEARCH return "target"; ======= return "updated"; >>>>>>> REPLACE` - // Search around the middle (function three) - // Even though all functions contain the target text, - // it should match the one closest to line 9 first - const result = await strategy.applyDiff(originalContent, diffContent, 9, 9) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { + // Search around the middle (function three) + // Even though all functions contain the target text, + // it should match the one closest to line 9 first + const result = await strategy.applyDiff(originalContent, diffContent, 9, 9) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe(`function one() { return "target"; } @@ -628,21 +676,21 @@ function four() { function five() { return "target"; }`) - } + } + }) }) - }) - describe("line number stripping", () => { describe("line number stripping", () => { - let strategy: MultiSearchReplaceDiffStrategy + describe("line number stripping", () => { + let strategy: MultiSearchReplaceDiffStrategy - beforeEach(() => { - strategy = new MultiSearchReplaceDiffStrategy() - }) + beforeEach(() => { + strategy = new MultiSearchReplaceDiffStrategy() + }) - it("should strip line numbers from both search and replace sections", async () => { - const originalContent = "function test() {\n return true;\n}\n" - const diffContent = `test.ts + it("should strip line numbers from both search and replace sections", async () => { + const originalContent = "function test() {\n return true;\n}\n" + const diffContent = `test.ts <<<<<<< SEARCH 1 | function test() { 2 | return true; @@ -653,16 +701,16 @@ function five() { 3 | } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\n return false;\n}\n") - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("function test() {\n return false;\n}\n") + } + }) - it("should strip line numbers with leading spaces", async () => { - const originalContent = "function test() {\n return true;\n}\n" - const diffContent = `test.ts + it("should strip line numbers with leading spaces", async () => { + const originalContent = "function test() {\n return true;\n}\n" + const diffContent = `test.ts <<<<<<< SEARCH 1 | function test() { 2 | return true; @@ -673,16 +721,16 @@ function five() { 3 | } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\n return false;\n}\n") - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("function test() {\n return false;\n}\n") + } + }) - it("should not strip when not all lines have numbers in either section", async () => { - const originalContent = "function test() {\n return true;\n}\n" - const diffContent = `test.ts + it("should not strip when not all lines have numbers in either section", async () => { + const originalContent = "function test() {\n return true;\n}\n" + const diffContent = `test.ts <<<<<<< SEARCH 1 | function test() { 2 | return true; @@ -693,13 +741,13 @@ function five() { 3 | } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(false) + }) - it("should preserve content that naturally starts with pipe", async () => { - const originalContent = "|header|another|\n|---|---|\n|data|more|\n" - const diffContent = `test.ts + it("should preserve content that naturally starts with pipe", async () => { + const originalContent = "|header|another|\n|---|---|\n|data|more|\n" + const diffContent = `test.ts <<<<<<< SEARCH 1 | |header|another| 2 | |---|---| @@ -710,16 +758,16 @@ function five() { 3 | |data|updated| >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("|header|another|\n|---|---|\n|data|updated|\n") - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("|header|another|\n|---|---|\n|data|updated|\n") + } + }) - it("should preserve indentation when stripping line numbers", async () => { - const originalContent = " function test() {\n return true;\n }\n" - const diffContent = `test.ts + it("should preserve indentation when stripping line numbers", async () => { + const originalContent = " function test() {\n return true;\n }\n" + const diffContent = `test.ts <<<<<<< SEARCH 1 | function test() { 2 | return true; @@ -730,16 +778,16 @@ function five() { 3 | } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(" function test() {\n return false;\n }\n") - } - }) + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe(" function test() {\n return false;\n }\n") + } + }) - it("should handle different line numbers between sections", async () => { - const originalContent = "function test() {\n return true;\n}\n" - const diffContent = `test.ts + it("should handle different line numbers between sections", async () => { + const originalContent = "function test() {\n return true;\n}\n" + const diffContent = `test.ts <<<<<<< SEARCH 10 | function test() { 11 | return true; @@ -750,11 +798,26 @@ function five() { 22 | } >>>>>>> REPLACE` - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\n return false;\n}\n") - } + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("function test() {\n return false;\n}\n") + } + }) + + it("detects search marker when expecting replace", () => { + const diff = "<<<<<<< SEARCH\n" + "content\n" + "=======\n" + "new content\n" + "<<<<<<< SEARCH" + const result = strategy["validateMarkerSequencing"](diff) + expect(result.success).toBe(false) + expect(result.error).toContain("'<<<<<<< SEARCH' found in your diff content") + }) + + it("detects separator when expecting replace", () => { + const diff = "<<<<<<< SEARCH\n" + "content\n" + "=======\n" + "new content\n" + "=======" + const result = strategy["validateMarkerSequencing"](diff) + expect(result.success).toBe(false) + expect(result.error).toContain("'=======' found in your diff content") + }) }) it("should not strip content that starts with pipe but no line number", async () => { From 04854ea0489d7f7f1d97854f338b4a401f67d5fd Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Fri, 21 Mar 2025 20:30:33 -0700 Subject: [PATCH 07/23] test: add tests for escaped marker handling in multi-search-replace Add tests that validate: - Original content with unescaped markers - Search content with escaped markers to match unescaped markers in original content - Proper validation of escaped search, separator, and replace markers in diff content - Successful application of diffs with escaped markers in search content Signed-off-by: Eric Wheeler --- .../__tests__/multi-search-replace.test.ts | 537 ++++++++++++++++++ 1 file changed, 537 insertions(+) diff --git a/src/core/diff/strategies/__tests__/multi-search-replace.test.ts b/src/core/diff/strategies/__tests__/multi-search-replace.test.ts index a53b179d982..e4a52af6d4b 100644 --- a/src/core/diff/strategies/__tests__/multi-search-replace.test.ts +++ b/src/core/diff/strategies/__tests__/multi-search-replace.test.ts @@ -812,6 +812,543 @@ function five() { expect(result.error).toContain("'<<<<<<< SEARCH' found in your diff content") }) + it("allows escaped search marker in content", () => { + const diff = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\<<<<<<< SEARCH\n" + + "after\n" + + "=======\n" + + "new content\n" + + ">>>>>>> REPLACE" + const result = strategy["validateMarkerSequencing"](diff) + expect(result.success).toBe(true) + }) + + it("allows escaped separator in content", () => { + const diff = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\=======\n" + + "after\n" + + "=======\n" + + "new content\n" + + ">>>>>>> REPLACE" + const result = strategy["validateMarkerSequencing"](diff) + expect(result.success).toBe(true) + }) + + it("processes escaped search marker in content", async () => { + const originalContent = "before\n<<<<<<< SEARCH\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\<<<<<<< SEARCH\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped separator in content", async () => { + const originalContent = "before\n=======\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\=======\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped search marker in content", async () => { + const originalContent = "before\n<<<<<<< SEARCH\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\<<<<<<< SEARCH\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped separator in content", async () => { + const originalContent = "before\n=======\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\=======\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped search marker in content", async () => { + const originalContent = "before\n<<<<<<< SEARCH\nafter\n" + const diffContent = + "test.ts\n" + + "<<<<<<< SEARCH\n" + + "before\n" + + "\\<<<<<<< SEARCH\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped search marker in content", async () => { + const originalContent = "before\n<<<<<<< SEARCH\nafter\n" + const diffContent = + "test.ts\n" + + "<<<<<<< SEARCH\n" + + "before\n" + + "\\<<<<<<< SEARCH\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped separator in content", async () => { + const originalContent = "before\n=======\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\=======\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped search marker in content", async () => { + const originalContent = "before\n<<<<<<< SEARCH\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\<<<<<<< SEARCH\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped search marker in content", async () => { + const originalContent = "before\n<<<<<<< SEARCH\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\<<<<<<< SEARCH\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped search marker in content", async () => { + const originalContent = "before\n<<<<<<< SEARCH\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\<<<<<<< SEARCH\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped separator in content", async () => { + const originalContent = "before\n=======\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\=======\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("allows escaped search marker in content", () => { + const diff = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\<<<<<<< SEARCH\n" + + "after\n" + + "=======\n" + + "new content\n" + + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + + it("allows escaped separator in content", () => { + const diff = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\=======\n" + + "after\n" + + "=======\n" + + "new content\n" + + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + + it("allows escaped search marker in content", () => { + const diff = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\<<<<<<< SEARCH\n" + + "after\n" + + "=======\n" + + "new content\n" + + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + + it("allows escaped search marker in content", () => { + const diff = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\<<<<<<< SEARCH\n" + + "after\n" + + "=======\n" + + "new content\n" + + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + + it("allows escaped separator in content", () => { + const diff = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\=======\n" + + "after\n" + + "=======\n" + + "new content\n" + + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + + it("allows escaped replace marker in content", () => { + const diff = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\>>>>>>> REPLACE\n" + + "after\n" + + "=======\n" + + "new content\n" + + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + + it("allows escaped separator in content", () => { + const diff = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\=======\n" + + "after\n" + + "=======\n" + + "new content\n" + + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + + it("allows escaped replace marker in content", () => { + const diff = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\>>>>>>> REPLACE\n" + + "after\n" + + "=======\n" + + "new content\n" + + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + + it("allows escaped replace marker in content", () => { + const diff = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\>>>>>>> REPLACE\n" + + "after\n" + + "=======\n" + + "new content\n" + + ">>>>>>> REPLACE" + expect(strategy["validateMarkerSequencing"](diff).success).toBe(true) + }) + + it("processes escaped search marker in content", async () => { + const originalContent = "before\n<<<<<<< SEARCH\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\<<<<<<< SEARCH\n" + + "after\n" + + "=======\n" + + "replaced content\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("replaced content\n") + } + }) + + it("processes escaped replace marker in content", async () => { + const originalContent = "before\n>>>>>>> REPLACE\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\>>>>>>> REPLACE\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes multiple escaped markers in content", async () => { + const originalContent = "<<<<<<< SEARCH\n=======\n>>>>>>> REPLACE\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "\\<<<<<<< SEARCH\n" + + "\\=======\n" + + "\\>>>>>>> REPLACE\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped separator in content", async () => { + const originalContent = "before\n=======\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\=======\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped search marker in content", async () => { + const originalContent = "before\n<<<<<<< SEARCH\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\<<<<<<< SEARCH\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped replace marker in content", async () => { + const originalContent = "before\n>>>>>>> REPLACE\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\>>>>>>> REPLACE\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped separator in content", async () => { + const originalContent = "before\n=======\nafter\n" + const diffContent = + "test.ts\n" + + "<<<<<<< SEARCH\n" + + "before\n" + + "\\=======\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped replace marker in content", async () => { + const originalContent = "before\n>>>>>>> REPLACE\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\>>>>>>> REPLACE\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes multiple escaped markers in content", async () => { + const originalContent = "<<<<<<< SEARCH\n=======\n>>>>>>> REPLACE\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "\\<<<<<<< SEARCH\n" + + "\\=======\n" + + "\\>>>>>>> REPLACE\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes escaped replace marker in content", async () => { + const originalContent = "before\n>>>>>>> REPLACE\nafter\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\>>>>>>> REPLACE\n" + + "after\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("processes multiple escaped markers in content", async () => { + const originalContent = "<<<<<<< SEARCH\n=======\n>>>>>>> REPLACE\n" + const diffContent = + "<<<<<<< SEARCH\n" + + "\\<<<<<<< SEARCH\n" + + "\\=======\n" + + "\\>>>>>>> REPLACE\n" + + "=======\n" + + "unchanged\n" + + ">>>>>>> REPLACE" + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe("unchanged\n") + } + }) + + it("allows escaped replace marker in content", () => { + const diff = + "<<<<<<< SEARCH\n" + + "before\n" + + "\\>>>>>>> REPLACE\n" + + "after\n" + + "=======\n" + + "new content\n" + + ">>>>>>> REPLACE" + const result = strategy["validateMarkerSequencing"](diff) + expect(result.success).toBe(true) + }) + + it("allows multiple escaped markers in content", () => { + const diff = + "<<<<<<< SEARCH\n" + + "\\<<<<<<< SEARCH\n" + + "\\=======\n" + + "\\>>>>>>> REPLACE\n" + + "=======\n" + + "new content\n" + + ">>>>>>> REPLACE" + const result = strategy["validateMarkerSequencing"](diff) + expect(result.success).toBe(true) + }) + it("detects separator when expecting replace", () => { const diff = "<<<<<<< SEARCH\n" + "content\n" + "=======\n" + "new content\n" + "=======" const result = strategy["validateMarkerSequencing"](diff) From 4338f64ed9b186cb6cfa7c70b6e77b7bb48a9424 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 22 Mar 2025 09:56:01 -0400 Subject: [PATCH 08/23] Update contributors list (#1884) docs: update contributors list [skip ci] Co-authored-by: mrubens --- README.md | 4 ++-- locales/ca/README.md | 4 ++-- locales/de/README.md | 4 ++-- locales/es/README.md | 4 ++-- locales/fr/README.md | 4 ++-- locales/hi/README.md | 4 ++-- locales/it/README.md | 4 ++-- locales/ja/README.md | 4 ++-- locales/ko/README.md | 4 ++-- locales/pl/README.md | 4 ++-- locales/pt-BR/README.md | 4 ++-- locales/tr/README.md | 4 ++-- locales/vi/README.md | 4 ++-- locales/zh-CN/README.md | 4 ++-- locales/zh-TW/README.md | 4 ++-- 15 files changed, 30 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 4d294faae03..f1ba1762ab3 100644 --- a/README.md +++ b/README.md @@ -182,8 +182,8 @@ Thanks to all our contributors who have helped make Roo Code better! | mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| +| NyxJae
NyxJae
| MuriloFP
MuriloFP
| hannesrudolph
hannesrudolph
| d-oit
d-oit
| punkpeye
punkpeye
| monotykamary
monotykamary
| | lloydchang
lloydchang
| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| cannuri
cannuri
| lupuletic
lupuletic
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| | Premshay
Premshay
| psv2522
psv2522
| olweraltuve
olweraltuve
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| | RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/ca/README.md b/locales/ca/README.md index 133bd1d7601..c4e61729798 100644 --- a/locales/ca/README.md +++ b/locales/ca/README.md @@ -180,8 +180,8 @@ Gràcies a tots els nostres col·laboradors que han ajudat a millorar Roo Code! |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/de/README.md b/locales/de/README.md index f519c508d01..9b92ef1d0a2 100644 --- a/locales/de/README.md +++ b/locales/de/README.md @@ -180,8 +180,8 @@ Danke an alle unsere Mitwirkenden, die geholfen haben, Roo Code zu verbessern! |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/es/README.md b/locales/es/README.md index 4dc42be6fb5..1ea3222b55a 100644 --- a/locales/es/README.md +++ b/locales/es/README.md @@ -180,8 +180,8 @@ Usamos [changesets](https://github.com/changesets/changesets) para versionar y p |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/fr/README.md b/locales/fr/README.md index 505ae5692e2..97870b160f3 100644 --- a/locales/fr/README.md +++ b/locales/fr/README.md @@ -180,8 +180,8 @@ Merci à tous nos contributeurs qui ont aidé à améliorer Roo Code ! |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/hi/README.md b/locales/hi/README.md index a8f0f07a4fe..e130502d554 100644 --- a/locales/hi/README.md +++ b/locales/hi/README.md @@ -180,8 +180,8 @@ Roo Code को बेहतर बनाने में मदद करने |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/it/README.md b/locales/it/README.md index dce5ca0362c..837d7635dba 100644 --- a/locales/it/README.md +++ b/locales/it/README.md @@ -180,8 +180,8 @@ Grazie a tutti i nostri contributori che hanno aiutato a migliorare Roo Code! |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/ja/README.md b/locales/ja/README.md index 58d8a5c8313..4711610f915 100644 --- a/locales/ja/README.md +++ b/locales/ja/README.md @@ -180,8 +180,8 @@ Roo Codeの改善に貢献してくれたすべての貢献者に感謝します |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/ko/README.md b/locales/ko/README.md index 3905a68f78a..bd7040a0a67 100644 --- a/locales/ko/README.md +++ b/locales/ko/README.md @@ -180,8 +180,8 @@ Roo Code를 더 좋게 만드는 데 도움을 준 모든 기여자에게 감사 |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/pl/README.md b/locales/pl/README.md index a709fd90c3b..a367e6ff13d 100644 --- a/locales/pl/README.md +++ b/locales/pl/README.md @@ -180,8 +180,8 @@ Dziękujemy wszystkim naszym współtwórcom, którzy pomogli ulepszyć Roo Code |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/pt-BR/README.md b/locales/pt-BR/README.md index d2f51447af0..00d45750309 100644 --- a/locales/pt-BR/README.md +++ b/locales/pt-BR/README.md @@ -180,8 +180,8 @@ Obrigado a todos os nossos contribuidores que ajudaram a tornar o Roo Code melho |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/tr/README.md b/locales/tr/README.md index e3beed92e05..dd2e298d088 100644 --- a/locales/tr/README.md +++ b/locales/tr/README.md @@ -180,8 +180,8 @@ Roo Code'u daha iyi hale getirmeye yardımcı olan tüm katkıda bulunanlara te |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/vi/README.md b/locales/vi/README.md index 3a23b9f85ab..69240c9d3e8 100644 --- a/locales/vi/README.md +++ b/locales/vi/README.md @@ -180,8 +180,8 @@ Cảm ơn tất cả những người đóng góp đã giúp cải thiện Roo C |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/zh-CN/README.md b/locales/zh-CN/README.md index 017a44e98f1..db5210f033f 100644 --- a/locales/zh-CN/README.md +++ b/locales/zh-CN/README.md @@ -180,8 +180,8 @@ code --install-extension bin/roo-cline-.vsix |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| diff --git a/locales/zh-TW/README.md b/locales/zh-TW/README.md index bf038798984..83c12271d7d 100644 --- a/locales/zh-TW/README.md +++ b/locales/zh-TW/README.md @@ -180,8 +180,8 @@ code --install-extension bin/roo-cline-.vsix |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|NyxJae
NyxJae
|MuriloFP
MuriloFP
|hannesrudolph
hannesrudolph
|d-oit
d-oit
|punkpeye
punkpeye
|monotykamary
monotykamary
| |lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|cannuri
cannuri
|lupuletic
lupuletic
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
| |Premshay
Premshay
|psv2522
psv2522
|olweraltuve
olweraltuve
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
| |RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|pugazhendhi-m
pugazhendhi-m
| From 92dee31634039d8a7826ec961eaba986ffd38645 Mon Sep 17 00:00:00 2001 From: KJ7LNW <93454819+KJ7LNW@users.noreply.github.com> Date: Sat, 22 Mar 2025 16:01:27 -0700 Subject: [PATCH 09/23] fix: make dropdown menu background fully opaque (#1907) --- webview-ui/src/components/ui/dropdown-menu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/ui/dropdown-menu.tsx b/webview-ui/src/components/ui/dropdown-menu.tsx index bce7508dd9b..3a3812bf34a 100644 --- a/webview-ui/src/components/ui/dropdown-menu.tsx +++ b/webview-ui/src/components/ui/dropdown-menu.tsx @@ -27,7 +27,7 @@ const DropdownMenuContent = React.forwardRef< "z-50 min-w-[8rem] overflow-hidden rounded-xs p-1 shadow-xs", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "border border-vscode-focusBorder", - "bg-vscode-dropdown-background", + "bg-vscode-dropdown-background bg-opacity-100", "text-vscode-dropdown-foreground", className, )} From 2597347e918bb25ef7f80126a17e27fa4cac4be9 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 23 Mar 2025 01:52:43 -0400 Subject: [PATCH 10/23] Use openrouter stream_options include_usage (#1905) --- .../providers/__tests__/openrouter.test.ts | 22 +++---- src/api/providers/openrouter.ts | 61 +++++-------------- 2 files changed, 24 insertions(+), 59 deletions(-) diff --git a/src/api/providers/__tests__/openrouter.test.ts b/src/api/providers/__tests__/openrouter.test.ts index 981c9ad096f..996644b07f7 100644 --- a/src/api/providers/__tests__/openrouter.test.ts +++ b/src/api/providers/__tests__/openrouter.test.ts @@ -112,6 +112,16 @@ describe("OpenRouterHandler", () => { }, ], } + // Add usage information in the stream response + yield { + id: "test-id", + choices: [{ delta: {} }], + usage: { + prompt_tokens: 10, + completion_tokens: 20, + cost: 0.001, + }, + } }, } @@ -121,17 +131,6 @@ describe("OpenRouterHandler", () => { completions: { create: mockCreate }, } as any - // Mock axios.get for generation details - ;(axios.get as jest.Mock).mockResolvedValue({ - data: { - data: { - native_tokens_prompt: 10, - native_tokens_completion: 20, - total_cost: 0.001, - }, - }, - }) - const systemPrompt = "test system prompt" const messages: Anthropic.Messages.MessageParam[] = [{ role: "user" as const, content: "test message" }] @@ -153,7 +152,6 @@ describe("OpenRouterHandler", () => { inputTokens: 10, outputTokens: 20, totalCost: 0.001, - fullResponseText: "test response", }) // Verify OpenAI client was called with correct parameters diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 3f215bfc7c3..6d93d0b180b 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -24,11 +24,6 @@ type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & { thinking?: BetaThinkingConfigParam } -// Add custom interface for OpenRouter usage chunk. -interface OpenRouterApiStreamUsageChunk extends ApiStreamUsageChunk { - fullResponseText: string -} - export class OpenRouterHandler extends BaseProvider implements SingleCompletionHandler { protected options: ApiHandlerOptions private client: OpenAI @@ -110,7 +105,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH top_p: topP, messages: openAiMessages, stream: true, - include_reasoning: true, + stream_options: { include_usage: true }, // Only include provider if openRouterSpecificProvider is not "[default]". ...(this.options.openRouterSpecificProvider && this.options.openRouterSpecificProvider !== OPENROUTER_DEFAULT_PROVIDER_NAME && { @@ -122,7 +117,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH const stream = await this.client.chat.completions.create(completionParams) - let genId: string | undefined + let lastUsage for await (const chunk of stream as unknown as AsyncIterable) { // OpenRouter returns an error object instead of the OpenAI SDK throwing an error. @@ -132,10 +127,6 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH throw new Error(`OpenRouter API Error ${error?.code}: ${error?.message}`) } - if (!genId && chunk.id) { - genId = chunk.id - } - const delta = chunk.choices[0]?.delta if ("reasoning" in delta && delta.reasoning) { @@ -146,47 +137,23 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH fullResponseText += delta.content yield { type: "text", text: delta.content } as ApiStreamChunk } - } - - const endpoint = `${this.client.baseURL}/generation?id=${genId}` - const config: AxiosRequestConfig = { - headers: { Authorization: `Bearer ${this.options.openRouterApiKey}` }, - timeout: 3_000, + if (chunk.usage) { + lastUsage = chunk.usage + } } - let attempt = 0 - let lastError: Error | undefined - const startTime = Date.now() - - while (attempt++ < 10) { - await delay(attempt * 100) // Give OpenRouter some time to produce the generation metadata. - - try { - const response = await axios.get(endpoint, config) - const generation = response.data?.data - - yield { - type: "usage", - inputTokens: generation?.native_tokens_prompt || 0, - outputTokens: generation?.native_tokens_completion || 0, - totalCost: generation?.total_cost || 0, - fullResponseText, - } as OpenRouterApiStreamUsageChunk - - break - } catch (error: unknown) { - if (error instanceof Error) { - lastError = error - } - } + if (lastUsage) { + yield this.processUsageMetrics(lastUsage) } + } - if (lastError) { - console.error( - `Failed to fetch OpenRouter generation details after attempt #${attempt} (${Date.now() - startTime}ms) [${genId}]`, - lastError, - ) + processUsageMetrics(usage: any): ApiStreamUsageChunk { + return { + type: "usage", + inputTokens: usage?.prompt_tokens || 0, + outputTokens: usage?.completion_tokens || 0, + totalCost: usage?.cost || 0, } } From b41dd739f3d4b49ce243ee307951d0d20590a524 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 23 Mar 2025 02:09:48 -0400 Subject: [PATCH 11/23] fix: prevent negative line index when maxReadFileLine is zero (#1915) When maxReadFileLine setting was set to zero, the code would attempt to read lines with a negative end index (maxReadFileLine - 1 = -1), causing the readLines function to reject the request. This change: - Checks if maxReadFileLine is greater than zero before calling readLines - Returns an empty string when maxReadFileLine is zero - Ensures content is only formatted with line numbers when it's not empty Signed-off-by: Eric Wheeler Co-authored-by: Eric Wheeler --- src/core/Cline.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 20af9ee565c..262c2233b45 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -2341,11 +2341,11 @@ export class Cline extends EventEmitter { isFileTruncated = true const res = await Promise.all([ - readLines(absolutePath, maxReadFileLine - 1, 0), + maxReadFileLine > 0 ? readLines(absolutePath, maxReadFileLine - 1, 0) : "", parseSourceCodeDefinitionsForFile(absolutePath, this.rooIgnoreController), ]) - content = addLineNumbers(res[0]) + content = res[0].length > 0 ? addLineNumbers(res[0]) : "" const result = res[1] if (result) { sourceCodeDef = `\n\n${result}` From 87b9b72b32fae946fc685ff7aa7f352ea79df395 Mon Sep 17 00:00:00 2001 From: Jason Owens <67338327+Jdo300@users.noreply.github.com> Date: Sun, 23 Mar 2025 02:12:53 -0400 Subject: [PATCH 12/23] Corrected bug in openrouter.ts and pre-commit and pre-push husky scripts (#1853) - Corrected error causing free models listed under openrouter to show pricing information - Updated pre-push and pre-commit scripts to work in Windows environments when pushing to branch (windows requires npm/npx to include the ".cmd" extension to recognize and compile. --- .husky/pre-commit | 9 ++++++++- .husky/pre-push | 9 ++++++++- src/api/providers/openrouter.ts | 3 ++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 111a1c0f915..8a87777657a 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -5,4 +5,11 @@ if [ "$branch" = "main" ]; then exit 1 fi -npx lint-staged +# Detect if running on Windows and use npx.cmd, otherwise use npx +if [ "$OS" = "Windows_NT" ]; then + npx_cmd="npx.cmd" +else + npx_cmd="npx" +fi + +"$npx_cmd" lint-staged \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push index a4fea4a34ae..79fe13835d1 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -5,7 +5,14 @@ if [ "$branch" = "main" ]; then exit 1 fi -npm run compile +# Detect if running on Windows and use npm.cmd, otherwise use npm +if [ "$OS" = "Windows_NT" ]; then + npm_cmd="npm.cmd" +else + npm_cmd="npm" +fi + +"$npm_cmd" run compile # Check for new changesets. NEW_CHANGESETS=$(find .changeset -name "*.md" ! -name "README.md" | wc -l | tr -d ' ') diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 6d93d0b180b..72e4fe576a9 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -256,12 +256,13 @@ export async function getOpenRouterModels(options?: ApiHandlerOptions) { modelInfo.maxTokens = 8192 break case rawModel.id.startsWith("anthropic/claude-3-haiku"): - default: modelInfo.supportsPromptCache = true modelInfo.cacheWritesPrice = 0.3 modelInfo.cacheReadsPrice = 0.03 modelInfo.maxTokens = 8192 break + default: + break } models[rawModel.id] = modelInfo From 2ab5aad1830f96e8a60cc09ad23a274805f9353f Mon Sep 17 00:00:00 2001 From: Diarmid Mackenzie Date: Sun, 23 Mar 2025 06:14:42 +0000 Subject: [PATCH 13/23] Fixes for prompts suite unit tests on windows (#1879) * Fixes for prompts suite unit tests on windows * unixLike -> toPosix Fix naming, and re-use code that already provides this function --- .../prompts/__tests__/custom-system-prompt.test.ts | 5 +++-- src/core/prompts/__tests__/responses-rooignore.test.ts | 10 +++++++--- src/core/prompts/__tests__/utils.ts | 7 +++++++ 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 src/core/prompts/__tests__/utils.ts diff --git a/src/core/prompts/__tests__/custom-system-prompt.test.ts b/src/core/prompts/__tests__/custom-system-prompt.test.ts index 812caffbafd..977ab051a00 100644 --- a/src/core/prompts/__tests__/custom-system-prompt.test.ts +++ b/src/core/prompts/__tests__/custom-system-prompt.test.ts @@ -2,6 +2,7 @@ import { SYSTEM_PROMPT } from "../system" import { defaultModeSlug, modes } from "../../../shared/modes" import * as vscode from "vscode" import * as fs from "fs/promises" +import { toPosix } from "./utils" // Mock the fs/promises module jest.mock("fs/promises", () => ({ @@ -89,7 +90,7 @@ describe("File-Based Custom System Prompt", () => { const fileCustomSystemPrompt = "Custom system prompt from file" // When called with utf-8 encoding, return a string mockedFs.readFile.mockImplementation((filePath, options) => { - if (filePath.toString().includes(`.roo/system-prompt-${defaultModeSlug}`) && options === "utf-8") { + if (toPosix(filePath).includes(`.roo/system-prompt-${defaultModeSlug}`) && options === "utf-8") { return Promise.resolve(fileCustomSystemPrompt) } return Promise.reject({ code: "ENOENT" }) @@ -124,7 +125,7 @@ describe("File-Based Custom System Prompt", () => { // Mock the readFile to return content from a file const fileCustomSystemPrompt = "Custom system prompt from file" mockedFs.readFile.mockImplementation((filePath, options) => { - if (filePath.toString().includes(`.roo/system-prompt-${defaultModeSlug}`) && options === "utf-8") { + if (toPosix(filePath).includes(`.roo/system-prompt-${defaultModeSlug}`) && options === "utf-8") { return Promise.resolve(fileCustomSystemPrompt) } return Promise.reject({ code: "ENOENT" }) diff --git a/src/core/prompts/__tests__/responses-rooignore.test.ts b/src/core/prompts/__tests__/responses-rooignore.test.ts index 37b3050dd05..46f1bec438a 100644 --- a/src/core/prompts/__tests__/responses-rooignore.test.ts +++ b/src/core/prompts/__tests__/responses-rooignore.test.ts @@ -2,9 +2,9 @@ import { formatResponse } from "../responses" import { RooIgnoreController, LOCK_TEXT_SYMBOL } from "../../ignore/RooIgnoreController" -import * as path from "path" import { fileExistsAtPath } from "../../../utils/fs" import * as fs from "fs/promises" +import { toPosix } from "./utils" // Mock dependencies jest.mock("../../../utils/fs") @@ -82,7 +82,9 @@ describe("RooIgnore Response Formatting", () => { controller.validateAccess = jest.fn().mockImplementation((filePath: string) => { // Only allow files not matching these patterns return ( - !filePath.includes("node_modules") && !filePath.includes(".git") && !filePath.includes("secrets/") + !filePath.includes("node_modules") && + !filePath.includes(".git") && + !toPosix(filePath).includes("secrets/") ) }) @@ -124,7 +126,9 @@ describe("RooIgnore Response Formatting", () => { controller.validateAccess = jest.fn().mockImplementation((filePath: string) => { // Only allow files not matching these patterns return ( - !filePath.includes("node_modules") && !filePath.includes(".git") && !filePath.includes("secrets/") + !filePath.includes("node_modules") && + !filePath.includes(".git") && + !toPosix(filePath).includes("secrets/") ) }) diff --git a/src/core/prompts/__tests__/utils.ts b/src/core/prompts/__tests__/utils.ts new file mode 100644 index 00000000000..f2ac4fec1e7 --- /dev/null +++ b/src/core/prompts/__tests__/utils.ts @@ -0,0 +1,7 @@ +import * as fs from "fs/promises" +import { PathLike } from "fs" + +// Make a path take a unix-like form. Useful for making path comparisons. +export function toPosix(filePath: PathLike | fs.FileHandle) { + return filePath.toString().toPosix() +} From 003388a5c45acf45789f5df5b1a596cd0fee41a4 Mon Sep 17 00:00:00 2001 From: pugazhendhi-m <132246623+pugazhendhi-m@users.noreply.github.com> Date: Sun, 23 Mar 2025 11:53:29 +0530 Subject: [PATCH 14/23] Fix maxTokens to 8192 only for compatible Anthropic models (#1862) Fix maxTokens to 8096 only for compatible Anthropic models Co-authored-by: Pugazhendhi --- .changeset/twelve-pumpkins-sit.md | 7 +++++++ src/api/providers/unbound.ts | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .changeset/twelve-pumpkins-sit.md diff --git a/.changeset/twelve-pumpkins-sit.md b/.changeset/twelve-pumpkins-sit.md new file mode 100644 index 00000000000..57e50230bf8 --- /dev/null +++ b/.changeset/twelve-pumpkins-sit.md @@ -0,0 +1,7 @@ +--- +"roo-cline": patch +--- + +Changes maxTokens to 8192 for anthropic models that supports it +anthropic/claude-3-sonnet, claude-3-opus, claude-3-haiku supports maxTokens of 4096. +This change keeps the max tokens the same for those models diff --git a/src/api/providers/unbound.ts b/src/api/providers/unbound.ts index 38cd494cdd3..f83ecd457b9 100644 --- a/src/api/providers/unbound.ts +++ b/src/api/providers/unbound.ts @@ -212,7 +212,10 @@ export async function getUnboundModels() { switch (true) { case modelId.startsWith("anthropic/"): - modelInfo.maxTokens = 8192 + // Set max tokens to 8192 for supported Anthropic models + if (modelInfo.maxTokens !== 4096) { + modelInfo.maxTokens = 8192 + } break default: break From e810a886d2431b9b984bc4ea0d8fb7d5688bec29 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 23 Mar 2025 10:08:16 -0400 Subject: [PATCH 15/23] Welcome page OAuth (#1913) * Add Requesty OAuth flow * New 1-click onboarding flow * Requesty: Use correct default model info * When called from the onboard flow, created the default profile Glama OAuth handler changed for consistency. * Add router images * Shuffle the routers * Translate * Appease knip --------- Co-authored-by: Daniel Trugman --- .changeset/late-chefs-remember.md | 5 + .vscodeignore | 3 +- assets/images/openrouter.png | Bin 0 -> 6715 bytes assets/images/requesty.png | Bin 0 -> 13210 bytes src/activate/handleUri.ts | 8 +- src/api/providers/__tests__/requesty.test.ts | 64 +++++++-- src/api/providers/requesty.ts | 6 +- src/core/webview/ClineProvider.ts | 129 +++++++++++------- src/shared/api.ts | 9 -- webview-ui/package-lock.json | 14 ++ webview-ui/package.json | 1 + webview-ui/src/components/common/Alert.tsx | 15 -- .../src/components/settings/ApiOptions.tsx | 28 ++-- .../src/components/welcome/WelcomeView.tsx | 95 +++++++++++-- webview-ui/src/i18n/locales/ca/welcome.json | 15 +- webview-ui/src/i18n/locales/de/welcome.json | 15 +- webview-ui/src/i18n/locales/en/welcome.json | 15 +- webview-ui/src/i18n/locales/es/welcome.json | 15 +- webview-ui/src/i18n/locales/fr/welcome.json | 23 +++- webview-ui/src/i18n/locales/hi/welcome.json | 15 +- webview-ui/src/i18n/locales/it/welcome.json | 15 +- webview-ui/src/i18n/locales/ja/welcome.json | 15 +- webview-ui/src/i18n/locales/ko/welcome.json | 15 +- webview-ui/src/i18n/locales/pl/welcome.json | 15 +- .../src/i18n/locales/pt-BR/welcome.json | 15 +- webview-ui/src/i18n/locales/tr/welcome.json | 23 +++- webview-ui/src/i18n/locales/vi/welcome.json | 15 +- .../src/i18n/locales/zh-CN/welcome.json | 15 +- .../src/i18n/locales/zh-TW/welcome.json | 19 ++- webview-ui/src/oauth/urls.ts | 16 +++ webview-ui/src/types.d.ts | 5 + 31 files changed, 502 insertions(+), 141 deletions(-) create mode 100644 .changeset/late-chefs-remember.md create mode 100644 assets/images/openrouter.png create mode 100644 assets/images/requesty.png delete mode 100644 webview-ui/src/components/common/Alert.tsx create mode 100644 webview-ui/src/oauth/urls.ts create mode 100644 webview-ui/src/types.d.ts diff --git a/.changeset/late-chefs-remember.md b/.changeset/late-chefs-remember.md new file mode 100644 index 00000000000..92fcebab1bb --- /dev/null +++ b/.changeset/late-chefs-remember.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Update the welcome page to provide 1-click OAuth flows with LLM routers or more advanced configuration with custom providers. diff --git a/.vscodeignore b/.vscodeignore index a8cac01b118..560897b9677 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -48,8 +48,9 @@ webview-ui/node_modules/** # Include default themes JSON files used in getTheme !src/integrations/theme/default-themes/** -# Include icons +# Include icons and images !assets/icons/** +!assets/images/** # Include .env file for telemetry !.env diff --git a/assets/images/openrouter.png b/assets/images/openrouter.png new file mode 100644 index 0000000000000000000000000000000000000000..7f2c18d1209c3b5f72683ba432cc4e93a921de59 GIT binary patch literal 6715 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 Ofu1R;Kfx-zLjM<=DJyaS literal 0 HcmV?d00001 diff --git a/assets/images/requesty.png b/assets/images/requesty.png new file mode 100644 index 0000000000000000000000000000000000000000..1a250ccd95ded3f282b0b55fc1b9d5b26da32499 GIT binary patch literal 13210 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 literal 0 HcmV?d00001 diff --git a/src/activate/handleUri.ts b/src/activate/handleUri.ts index 05440c7683e..96a24fe6fac 100644 --- a/src/activate/handleUri.ts +++ b/src/activate/handleUri.ts @@ -6,7 +6,6 @@ export const handleUri = async (uri: vscode.Uri) => { const path = uri.path const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B")) const visibleProvider = ClineProvider.getVisibleInstance() - if (!visibleProvider) { return } @@ -26,6 +25,13 @@ export const handleUri = async (uri: vscode.Uri) => { } break } + case "/requesty": { + const code = query.get("code") + if (code) { + await visibleProvider.handleRequestyCallback(code) + } + break + } default: break } diff --git a/src/api/providers/__tests__/requesty.test.ts b/src/api/providers/__tests__/requesty.test.ts index 47921a1c532..e761b4d765d 100644 --- a/src/api/providers/__tests__/requesty.test.ts +++ b/src/api/providers/__tests__/requesty.test.ts @@ -1,6 +1,6 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" -import { ApiHandlerOptions, ModelInfo, requestyModelInfoSaneDefaults } from "../../../shared/api" +import { ApiHandlerOptions, ModelInfo, requestyDefaultModelInfo } from "../../../shared/api" import { RequestyHandler } from "../requesty" import { convertToOpenAiMessages } from "../../transform/openai-format" import { convertToR1Format } from "../../transform/r1-format" @@ -18,14 +18,17 @@ describe("RequestyHandler", () => { requestyApiKey: "test-key", requestyModelId: "test-model", requestyModelInfo: { - maxTokens: 1000, - contextWindow: 4000, - supportsPromptCache: false, + maxTokens: 8192, + contextWindow: 200_000, supportsImages: true, - inputPrice: 1, - outputPrice: 10, - cacheReadsPrice: 0.1, - cacheWritesPrice: 1.5, + supportsComputerUse: true, + supportsPromptCache: true, + inputPrice: 3.0, + outputPrice: 15.0, + cacheWritesPrice: 3.75, + cacheReadsPrice: 0.3, + description: + "Claude 3.7 Sonnet is an advanced large language model with improved reasoning, coding, and problem-solving capabilities. It introduces a hybrid reasoning approach, allowing users to choose between rapid responses and extended, step-by-step processing for complex tasks. The model demonstrates notable improvements in coding, particularly in front-end development and full-stack updates, and excels in agentic workflows, where it can autonomously navigate multi-step processes. Claude 3.7 Sonnet maintains performance parity with its predecessor in standard mode while offering an extended reasoning mode for enhanced accuracy in math, coding, and instruction-following tasks. Read more at the [blog post here](https://www.anthropic.com/news/claude-3-7-sonnet)", }, openAiStreamingEnabled: true, includeMaxTokens: true, // Add this to match the implementation @@ -115,7 +118,7 @@ describe("RequestyHandler", () => { outputTokens: 10, cacheWriteTokens: 5, cacheReadTokens: 15, - totalCost: 0.000119, // (10 * 1 / 1,000,000) + (5 * 1.5 / 1,000,000) + (15 * 0.1 / 1,000,000) + (10 * 10 / 1,000,000) + totalCost: 0.00020325000000000003, // (10 * 3 / 1,000,000) + (5 * 3.75 / 1,000,000) + (15 * 0.3 / 1,000,000) + (10 * 15 / 1,000,000) (the ...0 is a fp skew) }, ]) @@ -123,8 +126,30 @@ describe("RequestyHandler", () => { model: defaultOptions.requestyModelId, temperature: 0, messages: [ - { role: "system", content: systemPrompt }, - { role: "user", content: "Hello" }, + { + role: "system", + content: [ + { + cache_control: { + type: "ephemeral", + }, + text: systemPrompt, + type: "text", + }, + ], + }, + { + role: "user", + content: [ + { + cache_control: { + type: "ephemeral", + }, + text: "Hello", + type: "text", + }, + ], + }, ], stream: true, stream_options: { include_usage: true }, @@ -191,7 +216,7 @@ describe("RequestyHandler", () => { outputTokens: 5, cacheWriteTokens: 0, cacheReadTokens: 0, - totalCost: 0.00006, // (10 * 1 / 1,000,000) + (5 * 10 / 1,000,000) + totalCost: 0.000105, // (10 * 3 / 1,000,000) + (5 * 15 / 1,000,000) }, ]) @@ -199,7 +224,18 @@ describe("RequestyHandler", () => { model: defaultOptions.requestyModelId, messages: [ { role: "user", content: systemPrompt }, - { role: "user", content: "Hello" }, + { + role: "user", + content: [ + { + cache_control: { + type: "ephemeral", + }, + text: "Hello", + type: "text", + }, + ], + }, ], }) }) @@ -224,7 +260,7 @@ describe("RequestyHandler", () => { const result = handler.getModel() expect(result).toEqual({ id: defaultOptions.requestyModelId, - info: requestyModelInfoSaneDefaults, + info: defaultOptions.requestyModelInfo, }) }) }) diff --git a/src/api/providers/requesty.ts b/src/api/providers/requesty.ts index 434d6f43161..822db1a6b06 100644 --- a/src/api/providers/requesty.ts +++ b/src/api/providers/requesty.ts @@ -1,6 +1,6 @@ import axios from "axios" -import { ModelInfo, requestyModelInfoSaneDefaults, requestyDefaultModelId } from "../../shared/api" +import { ModelInfo, requestyDefaultModelInfo, requestyDefaultModelId } from "../../shared/api" import { calculateApiCostOpenAI, parseApiPrice } from "../../utils/cost" import { ApiStreamUsageChunk } from "../transform/stream" import { OpenAiHandler, OpenAiHandlerOptions } from "./openai" @@ -26,7 +26,7 @@ export class RequestyHandler extends OpenAiHandler { openAiApiKey: options.requestyApiKey, openAiModelId: options.requestyModelId ?? requestyDefaultModelId, openAiBaseUrl: "https://router.requesty.ai/v1", - openAiCustomModelInfo: options.requestyModelInfo ?? requestyModelInfoSaneDefaults, + openAiCustomModelInfo: options.requestyModelInfo ?? requestyDefaultModelInfo, }) } @@ -34,7 +34,7 @@ export class RequestyHandler extends OpenAiHandler { const modelId = this.options.requestyModelId ?? requestyDefaultModelId return { id: modelId, - info: this.options.requestyModelInfo ?? requestyModelInfoSaneDefaults, + info: this.options.requestyModelInfo ?? requestyDefaultModelInfo, } } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 6777642c2e4..3d37c07fc84 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -10,7 +10,18 @@ import * as vscode from "vscode" import { changeLanguage, t } from "../../i18n" import { setPanel } from "../../activate/registerCommands" -import { ApiConfiguration, ApiProvider, ModelInfo, API_CONFIG_KEYS } from "../../shared/api" +import { + ApiConfiguration, + ApiProvider, + ModelInfo, + API_CONFIG_KEYS, + requestyDefaultModelId, + requestyDefaultModelInfo, + openRouterDefaultModelId, + openRouterDefaultModelInfo, + glamaDefaultModelId, + glamaDefaultModelInfo, +} from "../../shared/api" import { findLast } from "../../shared/array" import { supportPrompt } from "../../shared/support-prompt" import { GlobalFileNames } from "../../shared/globalFileNames" @@ -593,6 +604,8 @@ export class ClineProvider extends EventEmitter implements "codicon.css", ]) + const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"]) + const file = "src/index.tsx" const scriptUri = `http://${localServerUrl}/${file}` @@ -611,7 +624,7 @@ export class ClineProvider extends EventEmitter implements `font-src ${webview.cspSource}`, `style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`, `img-src ${webview.cspSource} data:`, - `script-src 'unsafe-eval' https://* https://*.posthog.com http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`, + `script-src 'unsafe-eval' ${webview.cspSource} https://* https://*.posthog.com http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`, `connect-src https://* https://*.posthog.com ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`, ] @@ -624,6 +637,9 @@ export class ClineProvider extends EventEmitter implements + Roo Code @@ -672,6 +688,8 @@ export class ClineProvider extends EventEmitter implements "codicon.css", ]) + const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"]) + // const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.js")) // const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "reset.css")) @@ -704,6 +722,9 @@ export class ClineProvider extends EventEmitter implements + Roo Code @@ -1811,23 +1832,7 @@ export class ClineProvider extends EventEmitter implements break case "upsertApiConfiguration": if (message.text && message.apiConfiguration) { - try { - await this.configManager.saveConfig(message.text, message.apiConfiguration) - const listApiConfig = await this.configManager.listConfig() - - await Promise.all([ - this.updateGlobalState("listApiConfigMeta", listApiConfig), - this.updateApiConfiguration(message.apiConfiguration), - this.updateGlobalState("currentApiConfigName", message.text), - ]) - - await this.postStateToWebview() - } catch (error) { - this.outputChannel.appendLine( - `Error create new api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, - ) - vscode.window.showErrorMessage(t("common:errors.create_api_config")) - } + await this.upsertApiConfiguration(message.text, message.apiConfiguration) } break case "renameApiConfiguration": @@ -2251,9 +2256,10 @@ export class ClineProvider extends EventEmitter implements // OpenRouter async handleOpenRouterCallback(code: string) { + let { apiConfiguration, currentApiConfigName } = await this.getState() + let apiKey: string try { - const { apiConfiguration } = await this.getState() const baseUrl = apiConfiguration.openRouterBaseUrl || "https://openrouter.ai/api/v1" // Extract the base domain for the auth endpoint const baseUrlDomain = baseUrl.match(/^(https?:\/\/[^\/]+)/)?.[1] || "https://openrouter.ai" @@ -2270,17 +2276,15 @@ export class ClineProvider extends EventEmitter implements throw error } - const openrouter: ApiProvider = "openrouter" - await this.contextProxy.setValues({ - apiProvider: openrouter, + const newConfiguration: ApiConfiguration = { + ...apiConfiguration, + apiProvider: "openrouter", openRouterApiKey: apiKey, - }) - - await this.postStateToWebview() - if (this.getCurrentCline()) { - this.getCurrentCline()!.api = buildApiHandler({ apiProvider: openrouter, openRouterApiKey: apiKey }) + openRouterModelId: apiConfiguration?.openRouterModelId || openRouterDefaultModelId, + openRouterModelInfo: apiConfiguration?.openRouterModelInfo || openRouterDefaultModelInfo, } - // await this.postMessageToWebview({ type: "action", action: "settingsButtonClicked" }) // bad ux if user is on welcome + + await this.upsertApiConfiguration(currentApiConfigName, newConfiguration) } // Glama @@ -2301,19 +2305,55 @@ export class ClineProvider extends EventEmitter implements throw error } - const glama: ApiProvider = "glama" - await this.contextProxy.setValues({ - apiProvider: glama, + const { apiConfiguration, currentApiConfigName } = await this.getState() + + const newConfiguration: ApiConfiguration = { + ...apiConfiguration, + apiProvider: "glama", glamaApiKey: apiKey, - }) - await this.postStateToWebview() - if (this.getCurrentCline()) { - this.getCurrentCline()!.api = buildApiHandler({ - apiProvider: glama, - glamaApiKey: apiKey, - }) + glamaModelId: apiConfiguration?.glamaModelId || glamaDefaultModelId, + glamaModelInfo: apiConfiguration?.glamaModelInfo || glamaDefaultModelInfo, + } + + await this.upsertApiConfiguration(currentApiConfigName, newConfiguration) + } + + // Requesty + + async handleRequestyCallback(code: string) { + let { apiConfiguration, currentApiConfigName } = await this.getState() + + const newConfiguration: ApiConfiguration = { + ...apiConfiguration, + apiProvider: "requesty", + requestyApiKey: code, + requestyModelId: apiConfiguration?.requestyModelId || requestyDefaultModelId, + requestyModelInfo: apiConfiguration?.requestyModelInfo || requestyDefaultModelInfo, + } + + await this.upsertApiConfiguration(currentApiConfigName, newConfiguration) + } + + // Save configuration + + async upsertApiConfiguration(configName: string, apiConfiguration: ApiConfiguration) { + try { + await this.configManager.saveConfig(configName, apiConfiguration) + const listApiConfig = await this.configManager.listConfig() + + await Promise.all([ + this.updateGlobalState("listApiConfigMeta", listApiConfig), + this.updateApiConfiguration(apiConfiguration), + this.updateGlobalState("currentApiConfigName", configName), + ]) + + await this.postStateToWebview() + } catch (error) { + this.outputChannel.appendLine( + `Error create new api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, + ) + vscode.window.showErrorMessage(t("common:errors.create_api_config")) } - // await this.postMessageToWebview({ type: "action", action: "settingsButtonClicked" }) // bad ux if user is on welcome } // Task history @@ -2627,14 +2667,7 @@ export class ClineProvider extends EventEmitter implements if (stateValues.apiProvider) { apiProvider = stateValues.apiProvider } else { - // Either new user or legacy user that doesn't have the apiProvider stored in state - // (If they're using OpenRouter or Bedrock, then apiProvider state will exist) - if (secretValues.apiKey) { - apiProvider = "anthropic" - } else { - // New users should default to openrouter - apiProvider = "openrouter" - } + apiProvider = "anthropic" } // Build the apiConfiguration object combining state values and secrets diff --git a/src/shared/api.ts b/src/shared/api.ts index 498bb922b84..ab672c6a110 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -740,15 +740,6 @@ export const openAiModelInfoSaneDefaults: ModelInfo = { outputPrice: 0, } -export const requestyModelInfoSaneDefaults: ModelInfo = { - maxTokens: -1, - contextWindow: 128_000, - supportsImages: true, - supportsPromptCache: false, - inputPrice: 0, - outputPrice: 0, -} - // Gemini // https://ai.google.dev/gemini-api/docs/models/gemini export type GeminiModelId = keyof typeof geminiModels diff --git a/webview-ui/package-lock.json b/webview-ui/package-lock.json index d6da7da4bf4..c884a5af8fd 100644 --- a/webview-ui/package-lock.json +++ b/webview-ui/package-lock.json @@ -31,6 +31,7 @@ "fzf": "^0.5.2", "i18next": "^24.2.2", "i18next-http-backend": "^3.0.2", + "knuth-shuffle-seeded": "^1.0.6", "lucide-react": "^0.475.0", "mermaid": "^11.4.1", "posthog-js": "^1.227.2", @@ -15017,6 +15018,14 @@ "node": ">=6" } }, + "node_modules/knuth-shuffle-seeded": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz", + "integrity": "sha512-9pFH0SplrfyKyojCLxZfMcvkhf5hH0d+UwR9nTVJ/DDQJGuzcXjTwB7TP7sDfehSudlGGaOLblmEWqv04ERVWg==", + "dependencies": { + "seed-random": "~2.2.0" + } + }, "node_modules/kolorist": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", @@ -19786,6 +19795,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/seed-random": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", + "integrity": "sha512-34EQV6AAHQGhoc0tn/96a9Fsi6v2xdqe/dMUwljGRaFOzR3EgRmECvD0O8vi8X+/uQ50LGHfkNu/Eue5TPKZkQ==" + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", diff --git a/webview-ui/package.json b/webview-ui/package.json index a8d143b7d74..6c4c157176a 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -41,6 +41,7 @@ "fzf": "^0.5.2", "i18next": "^24.2.2", "i18next-http-backend": "^3.0.2", + "knuth-shuffle-seeded": "^1.0.6", "lucide-react": "^0.475.0", "mermaid": "^11.4.1", "posthog-js": "^1.227.2", diff --git a/webview-ui/src/components/common/Alert.tsx b/webview-ui/src/components/common/Alert.tsx deleted file mode 100644 index b16e799b910..00000000000 --- a/webview-ui/src/components/common/Alert.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { cn } from "@/lib/utils" -import { HTMLAttributes } from "react" - -type AlertProps = HTMLAttributes - -export const Alert = ({ className, children, ...props }: AlertProps) => ( -

- {children} -
-) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index f202d518b13..f3e0f7822e0 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1,6 +1,7 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from "react" import { useAppTranslation } from "@/i18n/TranslationContext" import { Trans } from "react-i18next" +import { getRequestyAuthUrl, getOpenRouterAuthUrl, getGlamaAuthUrl } from "../../oauth/urls" import { useDebounce, useEvent } from "react-use" import { LanguageModelChatSelector } from "vscode" import { Checkbox } from "vscrui" @@ -279,7 +280,10 @@ const ApiOptions = ({ {t("settings:providers.apiKeyStorageNotice")} {!apiConfiguration?.openRouterApiKey && ( - + {t("settings:providers.getOpenRouterApiKey")} )} @@ -380,7 +384,10 @@ const ApiOptions = ({ {t("settings:providers.apiKeyStorageNotice")} {!apiConfiguration?.glamaApiKey && ( - + {t("settings:providers.getGlamaApiKey")} )} @@ -400,6 +407,14 @@ const ApiOptions = ({
{t("settings:providers.apiKeyStorageNotice")}
+ {!apiConfiguration?.requestyApiKey && ( + + {t("settings:providers.getRequestyApiKey")} + + )} )} @@ -1536,15 +1551,6 @@ const ApiOptions = ({ ) } -export function getGlamaAuthUrl(uriScheme?: string) { - const callbackUrl = `${uriScheme || "vscode"}://rooveterinaryinc.roo-cline/glama` - return `https://glama.ai/oauth/authorize?callback_url=${encodeURIComponent(callbackUrl)}` -} - -export function getOpenRouterAuthUrl(uriScheme?: string) { - return `https://openrouter.ai/auth?callback_url=${uriScheme || "vscode"}://rooveterinaryinc.roo-cline/openrouter` -} - export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) { const provider = apiConfiguration?.apiProvider || "anthropic" const modelId = apiConfiguration?.apiModelId diff --git a/webview-ui/src/components/welcome/WelcomeView.tsx b/webview-ui/src/components/welcome/WelcomeView.tsx index 937c59a5f10..3b81f51b618 100644 --- a/webview-ui/src/components/welcome/WelcomeView.tsx +++ b/webview-ui/src/components/welcome/WelcomeView.tsx @@ -1,18 +1,17 @@ import { useCallback, useState } from "react" import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" - import { useExtensionState } from "../../context/ExtensionStateContext" import { validateApiConfiguration } from "../../utils/validate" import { vscode } from "../../utils/vscode" import ApiOptions from "../settings/ApiOptions" import { Tab, TabContent } from "../common/Tab" -import { Alert } from "../common/Alert" import { useAppTranslation } from "../../i18n/TranslationContext" +import { getRequestyAuthUrl, getOpenRouterAuthUrl } from "../../oauth/urls" +import knuthShuffle from "knuth-shuffle-seeded" const WelcomeView = () => { - const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme } = useExtensionState() + const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme, machineId } = useExtensionState() const { t } = useAppTranslation() - const [errorMessage, setErrorMessage] = useState(undefined) const handleSubmit = useCallback(() => { @@ -27,24 +26,92 @@ const WelcomeView = () => { vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration }) }, [apiConfiguration, currentApiConfigName]) + // Using a lazy initializer so it reads once at mount + const [imagesBaseUri] = useState(() => { + const w = window as any + return w.IMAGES_BASE_URI || "" + }) + return (

{t("welcome:greeting")}

{t("welcome:introduction")}
- {t("welcome:notice")} - setApiConfiguration({ [field]: value })} - errorMessage={errorMessage} - setErrorMessage={setErrorMessage} - /> +
{t("welcome:chooseProvider")}
+ +
+

{t("welcome:startRouter")}

+ +
+ {/* Define the providers */} + {(() => { + // Provider card configuration + const providers = [ + { + slug: "requesty", + name: "Requesty", + description: t("welcome:routers.requesty.description"), + incentive: t("welcome:routers.requesty.incentive"), + authUrl: getRequestyAuthUrl(uriScheme), + }, + { + slug: "openrouter", + name: "OpenRouter", + description: t("welcome:routers.openrouter.description"), + authUrl: getOpenRouterAuthUrl(uriScheme), + }, + ] + + // Shuffle providers based on machine ID (will be consistent for the same machine) + const orderedProviders = [...providers] + knuthShuffle(orderedProviders, (machineId as any) || Date.now()) + + // Render the provider cards + return orderedProviders.map((provider, index) => ( + +
+ {provider.name} +
+
+
{provider.name}
+
+ {provider.description} +
+ {provider.incentive && ( +
{provider.incentive}
+ )} +
+
+ )) + })()} +
+ +
{t("welcome:or")}
+

{t("welcome:startCustom")}

+ setApiConfiguration({ [field]: value })} + errorMessage={errorMessage} + setErrorMessage={setErrorMessage} + /> +
- {t("welcome:start")} + + {t("welcome:start")} + {errorMessage &&
{errorMessage}
}
diff --git a/webview-ui/src/i18n/locales/ca/welcome.json b/webview-ui/src/i18n/locales/ca/welcome.json index bd2ebea05ac..89b1611881c 100644 --- a/webview-ui/src/i18n/locales/ca/welcome.json +++ b/webview-ui/src/i18n/locales/ca/welcome.json @@ -3,6 +3,18 @@ "introduction": "Puc fer tot tipus de tasques gràcies als últims avenços en capacitats de codificació agent i accés a eines que em permeten crear i editar fitxers, explorar projectes complexos, utilitzar el navegador i executar ordres de terminal (amb el teu permís, és clar). Fins i tot puc utilitzar MCP per crear noves eines i ampliar les meves pròpies capacitats.", "notice": "Per començar, aquesta extensió necessita un proveïdor d'API.", "start": "Som-hi!", + "chooseProvider": "Tria un proveïdor d'API per començar:", + "routers": { + "requesty": { + "description": "El teu router LLM optimitzat", + "incentive": "$1 de crèdit gratuït" + }, + "openrouter": { + "description": "Una interfície unificada per a LLMs" + } + }, + "startRouter": "Configuració ràpida a través d'un router", + "startCustom": "Utilitza la teva pròpia clau API", "telemetry": { "title": "Ajuda a millorar Roo Code", "anonymousTelemetry": "Envia dades d'ús i errors anònims per ajudar-nos a corregir errors i millorar l'extensió. No s'envia mai cap codi, text o informació personal.", @@ -10,5 +22,6 @@ "settings": "configuració", "allow": "Permetre", "deny": "Denegar" - } + }, + "or": "o" } diff --git a/webview-ui/src/i18n/locales/de/welcome.json b/webview-ui/src/i18n/locales/de/welcome.json index aded3848861..19fcb0eac34 100644 --- a/webview-ui/src/i18n/locales/de/welcome.json +++ b/webview-ui/src/i18n/locales/de/welcome.json @@ -3,6 +3,18 @@ "introduction": "Ich kann alle Arten von Aufgaben erledigen, dank der neuesten Durchbrüche in agentenbasierten Codierungsfähigkeiten und dem Zugang zu Tools, die es mir ermöglichen, Dateien zu erstellen und zu bearbeiten, komplexe Projekte zu erkunden, den Browser zu verwenden und Terminalbefehle auszuführen (natürlich mit deiner Erlaubnis). Ich kann sogar MCP verwenden, um neue Tools zu erstellen und meine eigenen Fähigkeiten zu erweitern.", "notice": "Um loszulegen, benötigt diese Erweiterung einen API-Anbieter.", "start": "Los geht's!", + "chooseProvider": "Wähle einen API-Anbieter, um zu beginnen:", + "routers": { + "requesty": { + "description": "Dein optimierter LLM-Router", + "incentive": "$1 Guthaben gratis" + }, + "openrouter": { + "description": "Eine einheitliche Schnittstelle für LLMs" + } + }, + "startRouter": "Express-Einrichtung über einen Router", + "startCustom": "Eigenen API-Schlüssel verwenden", "telemetry": { "title": "Hilf, Roo Code zu verbessern", "anonymousTelemetry": "Sende anonyme Fehler- und Nutzungsdaten, um uns bei der Fehlerbehebung und Verbesserung der Erweiterung zu helfen. Es werden niemals Code, Texte oder persönliche Informationen gesendet.", @@ -10,5 +22,6 @@ "settings": "Einstellungen", "allow": "Erlauben", "deny": "Ablehnen" - } + }, + "or": "oder" } diff --git a/webview-ui/src/i18n/locales/en/welcome.json b/webview-ui/src/i18n/locales/en/welcome.json index f02021b7186..811d760c0f1 100644 --- a/webview-ui/src/i18n/locales/en/welcome.json +++ b/webview-ui/src/i18n/locales/en/welcome.json @@ -3,6 +3,18 @@ "introduction": "I can do all kinds of tasks thanks to the latest breakthroughs in agentic coding capabilities and access to tools that let me create & edit files, explore complex projects, use the browser, and execute terminal commands (with your permission, of course). I can even use MCP to create new tools and extend my own capabilities.", "notice": "To get started, this extension needs an API provider.", "start": "Let's go!", + "chooseProvider": "Choose an API provider to get started:", + "routers": { + "requesty": { + "description": "Your optimized LLM router", + "incentive": "$1 free credit" + }, + "openrouter": { + "description": "A unified interface for LLMs" + } + }, + "startRouter": "Express Setup Through a Router", + "startCustom": "Bring Your Own API Key", "telemetry": { "title": "Help Improve Roo Code", "anonymousTelemetry": "Send anonymous error and usage data to help us fix bugs and improve the extension. No code, prompts, or personal information is ever sent.", @@ -10,5 +22,6 @@ "settings": "settings", "allow": "Allow", "deny": "Deny" - } + }, + "or": "or" } diff --git a/webview-ui/src/i18n/locales/es/welcome.json b/webview-ui/src/i18n/locales/es/welcome.json index 9482732d492..b2289200a1c 100644 --- a/webview-ui/src/i18n/locales/es/welcome.json +++ b/webview-ui/src/i18n/locales/es/welcome.json @@ -3,6 +3,18 @@ "introduction": "Puedo realizar todo tipo de tareas gracias a los últimos avances en capacidades de codificación agentica y acceso a herramientas que me permiten crear y editar archivos, explorar proyectos complejos, usar el navegador y ejecutar comandos de terminal (con tu permiso, por supuesto). Incluso puedo usar MCP para crear nuevas herramientas y ampliar mis propias capacidades.", "notice": "Para comenzar, esta extensión necesita un proveedor de API.", "start": "¡Vamos!", + "chooseProvider": "Elige un proveedor de API para comenzar:", + "routers": { + "requesty": { + "description": "Tu router LLM optimizado", + "incentive": "$1 de crédito gratis" + }, + "openrouter": { + "description": "Una interfaz unificada para LLMs" + } + }, + "startRouter": "Configuración rápida a través de un router", + "startCustom": "Usa tu propia clave API", "telemetry": { "title": "Ayuda a mejorar Roo Code", "anonymousTelemetry": "Envía datos de uso y errores anónimos para ayudarnos a corregir errores y mejorar la extensión. Nunca se envía código, texto o información personal.", @@ -10,5 +22,6 @@ "settings": "configuración", "allow": "Permitir", "deny": "Denegar" - } + }, + "or": "o" } diff --git a/webview-ui/src/i18n/locales/fr/welcome.json b/webview-ui/src/i18n/locales/fr/welcome.json index 7e34e61a0a1..ca2038a4f5a 100644 --- a/webview-ui/src/i18n/locales/fr/welcome.json +++ b/webview-ui/src/i18n/locales/fr/welcome.json @@ -1,14 +1,27 @@ { "greeting": "Salut, je suis Roo !", - "introduction": "Je peux effectuer toutes sortes de tâches grâce aux dernières avancées en matière de capacités de codage agentique et à l'accès à des outils qui me permettent de créer et de modifier des fichiers, d'explorer des projets complexes, d'utiliser le navigateur et d'exécuter des commandes terminal (avec votre permission, bien sûr). Je peux même utiliser MCP pour créer de nouveaux outils et étendre mes propres capacités.", + "introduction": "Je peux effectuer toutes sortes de tâches grâce aux dernières avancées en matière de capacités de codage agentique et à l'accès à des outils qui me permettent de créer et de modifier des fichiers, d'explorer des projets complexes, d'utiliser le navigateur et d'exécuter des commandes terminal (avec ta permission, bien sûr). Je peux même utiliser MCP pour créer de nouveaux outils et étendre mes propres capacités.", "notice": "Pour commencer, cette extension a besoin d'un fournisseur d'API.", "start": "C'est parti !", + "chooseProvider": "Choisis un fournisseur d'API pour commencer :", + "routers": { + "requesty": { + "description": "Ton routeur LLM optimisé", + "incentive": "1$ de crédit gratuit" + }, + "openrouter": { + "description": "Une interface unifiée pour les LLMs" + } + }, + "startRouter": "Configuration rapide via un routeur", + "startCustom": "Utiliser ta propre clé API", "telemetry": { - "title": "Aidez à améliorer Roo Code", - "anonymousTelemetry": "Envoyez des données d'utilisation et d'erreurs anonymes pour nous aider à corriger les bugs et améliorer l'extension. Aucun code, texte ou information personnelle n'est jamais envoyé.", - "changeSettings": "Vous pouvez toujours modifier cela en bas des paramètres", + "title": "Aide à améliorer Roo Code", + "anonymousTelemetry": "Envoie des données d'utilisation et d'erreurs anonymes pour nous aider à corriger les bugs et améliorer l'extension. Aucun code, texte ou information personnelle n'est jamais envoyé.", + "changeSettings": "Tu peux toujours modifier cela en bas des paramètres", "settings": "paramètres", "allow": "Autoriser", "deny": "Refuser" - } + }, + "or": "ou" } diff --git a/webview-ui/src/i18n/locales/hi/welcome.json b/webview-ui/src/i18n/locales/hi/welcome.json index ef85bdc8bfc..6ec08c646b7 100644 --- a/webview-ui/src/i18n/locales/hi/welcome.json +++ b/webview-ui/src/i18n/locales/hi/welcome.json @@ -3,6 +3,18 @@ "introduction": "मैं सभी प्रकार के कार्य कर सकता हूँ, एजेंटिक कोडिंग क्षमताओं में नवीनतम सफलताओं और उन टूल्स तक पहुंच के लिए धन्यवाद जो मुझे फाइलें बनाने और संपादित करने, जटिल परियोजनाओं का पता लगाने, ब्राउज़र का उपयोग करने और टर्मिनल कमांड निष्पादित करने की अनुमति देते हैं (आपकी अनुमति से, बिल्कुल)। मैं MCP का उपयोग करके नए टूल बना सकता हूँ और अपनी क्षमताओं का विस्तार कर सकता हूँ।", "notice": "शुरू करने के लिए, इस एक्सटेंशन को एक API प्रदाता की आवश्यकता है।", "start": "चलो शुरू करें!", + "chooseProvider": "शुरू करने के लिए एक API प्रदाता चुनें:", + "routers": { + "requesty": { + "description": "आपका अनुकूलित LLM राउटर", + "incentive": "$1 मुफ्त क्रेडिट" + }, + "openrouter": { + "description": "LLMs के लिए एक एकीकृत इंटरफेस" + } + }, + "startRouter": "राउटर के माध्यम से तेज़ सेटअप", + "startCustom": "अपनी खुद की API कुंजी का उपयोग करें", "telemetry": { "title": "Roo Code को बेहतर बनाने में मदद करें", "anonymousTelemetry": "बग ठीक करने और एक्सटेंशन को बेहतर बनाने में हमारी मदद करने के लिए गुमनाम त्रुटि और उपयोग डेटा भेजें। कोड, संकेत या व्यक्तिगत जानकारी कभी नहीं भेजी जाती है।", @@ -10,5 +22,6 @@ "settings": "सेटिंग्स", "allow": "अनुमति दें", "deny": "अस्वीकार करें" - } + }, + "or": "या" } diff --git a/webview-ui/src/i18n/locales/it/welcome.json b/webview-ui/src/i18n/locales/it/welcome.json index e05fc1c43e3..94dba7c2e90 100644 --- a/webview-ui/src/i18n/locales/it/welcome.json +++ b/webview-ui/src/i18n/locales/it/welcome.json @@ -3,6 +3,18 @@ "introduction": "Posso svolgere tutti i tipi di attività grazie ai più recenti progressi nelle capacità di codifica agentica e all'accesso a strumenti che mi permettono di creare e modificare file, esplorare progetti complessi, utilizzare il browser ed eseguire comandi terminal (con il tuo permesso, ovviamente). Posso persino utilizzare MCP per creare nuovi strumenti ed estendere le mie capacità.", "notice": "Per iniziare, questa estensione necessita di un fornitore di API.", "start": "Andiamo!", + "chooseProvider": "Scegli un fornitore di API per iniziare:", + "routers": { + "requesty": { + "description": "Il tuo router LLM ottimizzato", + "incentive": "$1 di credito gratuito" + }, + "openrouter": { + "description": "Un'interfaccia unificata per LLMs" + } + }, + "startRouter": "Configurazione rapida tramite router", + "startCustom": "Usa la tua chiave API", "telemetry": { "title": "Aiuta a migliorare Roo Code", "anonymousTelemetry": "Invia dati di utilizzo ed errori anonimi per aiutarci a correggere bug e migliorare l'estensione. Non viene mai inviato codice, testo o informazioni personali.", @@ -10,5 +22,6 @@ "settings": "impostazioni", "allow": "Consenti", "deny": "Nega" - } + }, + "or": "o" } diff --git a/webview-ui/src/i18n/locales/ja/welcome.json b/webview-ui/src/i18n/locales/ja/welcome.json index 9e5b30e4c95..6b614d3dd20 100644 --- a/webview-ui/src/i18n/locales/ja/welcome.json +++ b/webview-ui/src/i18n/locales/ja/welcome.json @@ -3,6 +3,18 @@ "introduction": "エージェント型コーディング能力の最新の進歩と、ファイルの作成・編集、複雑なプロジェクトの探索、ブラウザの使用、ターミナルコマンドの実行(もちろんあなたの許可を得て)を可能にするツールへのアクセスにより、あらゆる種類のタスクを実行できます。MCPを使用して新しいツールを作成し、自分の能力を拡張することもできます。", "notice": "開始するには、この拡張機能にはAPIプロバイダーが必要です。", "start": "さあ、始めましょう!", + "chooseProvider": "開始するにはAPIプロバイダーを選択してください:", + "routers": { + "requesty": { + "description": "最適化されたLLMルーター", + "incentive": "$1の無料クレジット" + }, + "openrouter": { + "description": "LLMsのための統一インターフェース" + } + }, + "startRouter": "ルーター経由の簡単セットアップ", + "startCustom": "自分のAPIキーを使用", "telemetry": { "title": "Roo Codeの改善にご協力ください", "anonymousTelemetry": "バグの修正と拡張機能の改善のため、匿名のエラーと使用データを送信してください。コード、プロンプト、個人情報は一切送信されません。", @@ -10,5 +22,6 @@ "settings": "設定", "allow": "許可", "deny": "拒否" - } + }, + "or": "または" } diff --git a/webview-ui/src/i18n/locales/ko/welcome.json b/webview-ui/src/i18n/locales/ko/welcome.json index b8b8cbd2527..83eb000af1b 100644 --- a/webview-ui/src/i18n/locales/ko/welcome.json +++ b/webview-ui/src/i18n/locales/ko/welcome.json @@ -3,6 +3,18 @@ "introduction": "에이전트 코딩 능력의 최신 발전과 파일 생성 및 편집, 복잡한 프로젝트 탐색, 브라우저 사용, 터미널 명령 실행(물론 사용자의 허락 하에)을 가능하게 하는 도구에 대한 접근 덕분에 모든 종류의 작업을 수행할 수 있습니다. MCP를 사용하여 새로운 도구를 만들고 제 능력을 확장할 수도 있습니다.", "notice": "시작하려면 이 확장 프로그램에 API 공급자가 필요합니다.", "start": "시작해 봅시다!", + "chooseProvider": "시작하려면 API 공급자를 선택하세요:", + "routers": { + "requesty": { + "description": "최적화된 LLM 라우터", + "incentive": "$1 무료 크레딧" + }, + "openrouter": { + "description": "LLM을 위한 통합 인터페이스" + } + }, + "startRouter": "라우터를 통한 빠른 설정", + "startCustom": "직접 API 키 사용하기", "telemetry": { "title": "Roo Code 개선에 도움 주세요", "anonymousTelemetry": "버그 수정 및 확장 기능 개선을 위해 익명의 오류 및 사용 데이터를 보내주세요. 코드, 프롬프트 또는 개인 정보는 절대 전송되지 않습니다.", @@ -10,5 +22,6 @@ "settings": "설정", "allow": "허용", "deny": "거부" - } + }, + "or": "또는" } diff --git a/webview-ui/src/i18n/locales/pl/welcome.json b/webview-ui/src/i18n/locales/pl/welcome.json index 0e6546bc21c..fb13723490b 100644 --- a/webview-ui/src/i18n/locales/pl/welcome.json +++ b/webview-ui/src/i18n/locales/pl/welcome.json @@ -3,6 +3,18 @@ "introduction": "Mogę wykonywać wszelkiego rodzaju zadania dzięki najnowszym osiągnięciom w zakresie możliwości kodowania agentowego i dostępu do narzędzi, które pozwalają mi tworzyć i edytować pliki, eksplorować złożone projekty, korzystać z przeglądarki i wykonywać polecenia terminalowe (oczywiście za Twoją zgodą). Mogę nawet używać MCP do tworzenia nowych narzędzi i rozszerzania własnych możliwości.", "notice": "Aby rozpocząć, to rozszerzenie potrzebuje dostawcy API.", "start": "Zaczynajmy!", + "chooseProvider": "Wybierz dostawcę API, aby rozpocząć:", + "routers": { + "requesty": { + "description": "Twój zoptymalizowany router LLM", + "incentive": "$1 darmowego kredytu" + }, + "openrouter": { + "description": "Ujednolicony interfejs dla LLMs" + } + }, + "startRouter": "Szybka konfiguracja przez router", + "startCustom": "Użyj własnego klucza API", "telemetry": { "title": "Pomóż ulepszyć Roo Code", "anonymousTelemetry": "Wyślij anonimowe dane o błędach i użyciu, aby pomóc nam w naprawianiu błędów i ulepszaniu rozszerzenia. Nigdy nie są wysyłane żadne kody, teksty ani informacje osobiste.", @@ -10,5 +22,6 @@ "settings": "ustawienia", "allow": "Zezwól", "deny": "Odmów" - } + }, + "or": "lub" } diff --git a/webview-ui/src/i18n/locales/pt-BR/welcome.json b/webview-ui/src/i18n/locales/pt-BR/welcome.json index c28386a6136..5f0fa0c193b 100644 --- a/webview-ui/src/i18n/locales/pt-BR/welcome.json +++ b/webview-ui/src/i18n/locales/pt-BR/welcome.json @@ -3,6 +3,18 @@ "introduction": "Posso realizar todos os tipos de tarefas graças aos últimos avanços nas capacidades de codificação agentica e ao acesso a ferramentas que me permitem criar e editar arquivos, explorar projetos complexos, usar o navegador e executar comandos de terminal (com sua permissão, é claro). Posso até usar o MCP para criar novas ferramentas e expandir minhas próprias capacidades.", "notice": "Para começar, esta extensão precisa de um provedor de API.", "start": "Vamos lá!", + "chooseProvider": "Escolha um provedor de API para começar:", + "routers": { + "requesty": { + "description": "Seu roteador LLM otimizado", + "incentive": "$1 de crédito grátis" + }, + "openrouter": { + "description": "Uma interface unificada para LLMs" + } + }, + "startRouter": "Configuração rápida através de um roteador", + "startCustom": "Use sua própria chave API", "telemetry": { "title": "Ajude a melhorar o Roo Code", "anonymousTelemetry": "Envie dados de uso e erros anônimos para nos ajudar a corrigir bugs e melhorar a extensão. Nenhum código, texto ou informação pessoal é enviado.", @@ -10,5 +22,6 @@ "settings": "configurações", "allow": "Permitir", "deny": "Negar" - } + }, + "or": "ou" } diff --git a/webview-ui/src/i18n/locales/tr/welcome.json b/webview-ui/src/i18n/locales/tr/welcome.json index 2f25a1e93d5..76bf97429e3 100644 --- a/webview-ui/src/i18n/locales/tr/welcome.json +++ b/webview-ui/src/i18n/locales/tr/welcome.json @@ -1,14 +1,27 @@ { "greeting": "Merhaba, ben Roo!", - "introduction": "Ajan tabanlı kodlama yeteneklerindeki son gelişmeler ve dosya oluşturma ve düzenleme, karmaşık projeleri keşfetme, tarayıcı kullanma ve terminal komutları çalıştırma (tabii ki sizin izninizle) gibi işlemleri yapmamı sağlayan araçlara erişim sayesinde her türlü görevi gerçekleştirebilirim. Hatta MCP'yi kullanarak yeni araçlar oluşturabilir ve kendi yeteneklerimi genişletebilirim.", + "introduction": "Ajan tabanlı kodlama yeteneklerindeki son gelişmeler ve dosya oluşturma ve düzenleme, karmaşık projeleri keşfetme, tarayıcı kullanma ve terminal komutları çalıştırma (tabii ki senin izninle) gibi işlemleri yapmamı sağlayan araçlara erişim sayesinde her türlü görevi gerçekleştirebilirim. Hatta MCP'yi kullanarak yeni araçlar oluşturabilir ve kendi yeteneklerimi genişletebilirim.", "notice": "Başlamak için bu eklentinin bir API sağlayıcısına ihtiyacı var.", "start": "Hadi başlayalım!", + "chooseProvider": "Başlamak için bir API sağlayıcısı seç:", + "routers": { + "requesty": { + "description": "Optimize edilmiş LLM yönlendiricin", + "incentive": "$1 ücretsiz kredi" + }, + "openrouter": { + "description": "LLM'ler için birleşik bir arayüz" + } + }, + "startRouter": "Yönlendirici Üzerinden Hızlı Kurulum", + "startCustom": "Kendi API Anahtarını Kullan", "telemetry": { - "title": "Roo Code'u Geliştirmeye Yardım Edin", - "anonymousTelemetry": "Hataları düzeltmemize ve eklentiyi geliştirmemize yardımcı olmak için anonim hata ve kullanım verileri gönderin. Hiçbir zaman kod, metin veya kişisel bilgi gönderilmez.", - "changeSettings": "Bunu her zaman ayarların altından değiştirebilirsiniz", + "title": "Roo Code'u Geliştirmeye Yardım Et", + "anonymousTelemetry": "Hataları düzeltmemize ve eklentiyi geliştirmemize yardımcı olmak için anonim hata ve kullanım verileri gönder. Hiçbir zaman kod, metin veya kişisel bilgi gönderilmez.", + "changeSettings": "Bunu her zaman ayarların altından değiştirebilirsin", "settings": "ayarlar", "allow": "İzin Ver", "deny": "Reddet" - } + }, + "or": "veya" } diff --git a/webview-ui/src/i18n/locales/vi/welcome.json b/webview-ui/src/i18n/locales/vi/welcome.json index 4801b80b7f4..91dcea70c67 100644 --- a/webview-ui/src/i18n/locales/vi/welcome.json +++ b/webview-ui/src/i18n/locales/vi/welcome.json @@ -3,6 +3,18 @@ "introduction": "Tôi có thể thực hiện nhiều loại nhiệm vụ nhờ vào những đột phá mới nhất trong khả năng lập trình dạng đại lý và quyền truy cập vào các công cụ cho phép tôi tạo & chỉnh sửa tệp, khám phá các dự án phức tạp, sử dụng trình duyệt và thực thi lệnh terminal (với sự cho phép của bạn, tất nhiên). Tôi thậm chí có thể sử dụng MCP để tạo công cụ mới và mở rộng khả năng của mình.", "notice": "Để bắt đầu, tiện ích mở rộng này cần một nhà cung cấp API.", "start": "Bắt đầu thôi!", + "chooseProvider": "Chọn một nhà cung cấp API để bắt đầu:", + "routers": { + "requesty": { + "description": "Bộ định tuyến LLM được tối ưu hóa của bạn", + "incentive": "$1 tín dụng miễn phí" + }, + "openrouter": { + "description": "Giao diện thống nhất cho các LLM" + } + }, + "startRouter": "Thiết lập nhanh qua bộ định tuyến", + "startCustom": "Sử dụng khóa API của riêng bạn", "telemetry": { "title": "Giúp cải thiện Roo Code", "anonymousTelemetry": "Gửi dữ liệu lỗi và sử dụng ẩn danh để giúp chúng tôi sửa lỗi và cải thiện tiện ích mở rộng. Không bao giờ gửi mã, lời nhắc hoặc thông tin cá nhân.", @@ -10,5 +22,6 @@ "settings": "cài đặt", "allow": "Cho phép", "deny": "Từ chối" - } + }, + "or": "hoặc" } diff --git a/webview-ui/src/i18n/locales/zh-CN/welcome.json b/webview-ui/src/i18n/locales/zh-CN/welcome.json index 523e026f526..27fc616cf67 100644 --- a/webview-ui/src/i18n/locales/zh-CN/welcome.json +++ b/webview-ui/src/i18n/locales/zh-CN/welcome.json @@ -3,6 +3,18 @@ "introduction": "得益于最新的代理编码能力突破和对各种工具的访问权限,我可以完成各种任务。我可以创建和编辑文件、探索复杂项目、使用浏览器,以及执行终端命令(当然,需要你的许可)。我甚至可以使用 MCP 创建新工具并扩展自己的能力。", "notice": "首先,请配置一个大模型 API 服务商。", "start": "开始吧!", + "chooseProvider": "选择一个 API 服务商开始:", + "routers": { + "requesty": { + "description": "你的优化 LLM 路由器", + "incentive": "$1 免费额度" + }, + "openrouter": { + "description": "LLM 的统一接口" + } + }, + "startRouter": "通过路由器快速设置", + "startCustom": "使用你自己的 API 密钥", "telemetry": { "title": "帮助改进 Roo 代码", "changeSettings": "可以随时在设置页面底部更改此设置", @@ -10,5 +22,6 @@ "anonymousTelemetry": "发送匿名的错误和使用数据,以帮助我们修复错误并改进扩展程序。不会发送任何代码、提示或个人信息。", "allow": "允许", "deny": "拒绝" - } + }, + "or": "或" } diff --git a/webview-ui/src/i18n/locales/zh-TW/welcome.json b/webview-ui/src/i18n/locales/zh-TW/welcome.json index 7eadb6deb36..4d987628072 100644 --- a/webview-ui/src/i18n/locales/zh-TW/welcome.json +++ b/webview-ui/src/i18n/locales/zh-TW/welcome.json @@ -1,14 +1,27 @@ { "greeting": "嗨,我是 Roo!", - "introduction": "由於最新的代理編碼能力突破,以及能夠讓我創建和編輯文件、探索複雜項目、使用瀏覽器和執行終端命令的工具(當然是在您的許可下),我可以完成各種任務。我甚至可以使用 MCP 創建新工具並擴展自己的能力。", + "introduction": "由於最新的代理編碼能力突破,以及能夠讓我創建和編輯文件、探索複雜項目、使用瀏覽器和執行終端命令的工具(當然是在你的許可下),我可以完成各種任務。我甚至可以使用 MCP 創建新工具並擴展自己的能力。", "notice": "要開始使用,此擴展需要一個 API 提供者。", "start": "我們開始吧!", + "chooseProvider": "選擇一個 API 提供者開始:", + "routers": { + "requesty": { + "description": "你的優化 LLM 路由器", + "incentive": "$1 免費額度" + }, + "openrouter": { + "description": "LLM 的統一接口" + } + }, + "startRouter": "通過路由器快速設置", + "startCustom": "使用你自己的 API 密鑰", "telemetry": { "title": "幫助改進 Roo Code", "anonymousTelemetry": "發送匿名的錯誤和使用數據,以幫助我們修復錯誤並改進擴展功能。不會發送任何代碼、提示或個人信息。", - "changeSettings": "您隨時可以在設置底部更改此選項", + "changeSettings": "你隨時可以在設置底部更改此選項", "settings": "設置", "allow": "允許", "deny": "拒絕" - } + }, + "or": "或" } diff --git a/webview-ui/src/oauth/urls.ts b/webview-ui/src/oauth/urls.ts new file mode 100644 index 00000000000..46a1815377c --- /dev/null +++ b/webview-ui/src/oauth/urls.ts @@ -0,0 +1,16 @@ +export function getCallbackUrl(provider: string, uriScheme?: string) { + const callbackUrl = `${uriScheme || "vscode"}://rooveterinaryinc.roo-cline/${provider}` + return encodeURIComponent(callbackUrl) +} + +export function getGlamaAuthUrl(uriScheme?: string) { + return `https://glama.ai/oauth/authorize?callback_url=${getCallbackUrl("glama", uriScheme)}` +} + +export function getOpenRouterAuthUrl(uriScheme?: string) { + return `https://openrouter.ai/auth?callback_url=${getCallbackUrl("openrouter", uriScheme)}` +} + +export function getRequestyAuthUrl(uriScheme?: string) { + return `https://app.requesty.ai/oauth/authorize?callback_url=${getCallbackUrl("requesty", uriScheme)}` +} diff --git a/webview-ui/src/types.d.ts b/webview-ui/src/types.d.ts new file mode 100644 index 00000000000..ff5ef47841a --- /dev/null +++ b/webview-ui/src/types.d.ts @@ -0,0 +1,5 @@ +// Type declarations for third-party modules + +declare module "knuth-shuffle-seeded" { + export default function knuthShuffle(array: T[], seed: any): T[] +} From a866a1c2711a3a79efd065062b49d91f11e5afd3 Mon Sep 17 00:00:00 2001 From: Sam Hoang Van Date: Mon, 24 Mar 2025 01:31:15 +0700 Subject: [PATCH 16/23] fix add line wrong index (#1927) --- src/core/Cline.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 262c2233b45..cdc9b5d6ec4 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -2333,7 +2333,7 @@ export class Cline extends EventEmitter { } else { content = addLineNumbers( await readLines(absolutePath, endLine, startLine), - startLine, + startLine + 1, ) } } else if (!isBinary && totalLines > maxReadFileLine) { From f07c7e6f0c17fc7d05cec58dc8f958c1885b87a3 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 23 Mar 2025 22:37:56 -0400 Subject: [PATCH 17/23] Fix question display logic when not streaming (#1931) --- webview-ui/src/components/chat/ChatRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 3f7d174b494..99c773873f0 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -252,7 +252,7 @@ export const ChatRowContent = ({ }, [message.ask, message.say, message.text]) const followUpData = useMemo(() => { - if (message.type === "ask" && message.ask === "followup" && message.partial === false) { + if (message.type === "ask" && message.ask === "followup" && !message.partial) { return JSON.parse(message.text || "{}") } return null From 29d784670114092d3e019587f4df4aa6ee3d5577 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 23 Mar 2025 23:15:00 -0400 Subject: [PATCH 18/23] Update suggestion button variant to make it work in all themes (#1932) --- webview-ui/src/components/chat/FollowUpSuggest.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/chat/FollowUpSuggest.tsx b/webview-ui/src/components/chat/FollowUpSuggest.tsx index 76aa32c7fb3..24dc42ca7de 100644 --- a/webview-ui/src/components/chat/FollowUpSuggest.tsx +++ b/webview-ui/src/components/chat/FollowUpSuggest.tsx @@ -28,7 +28,7 @@ const FollowUpSuggest = ({ suggestions = [], onSuggestionClick, ts = 1 }: Follow {suggestions.map((suggestion) => (