From bea36c897725403d4072bb160d98a32eabc8cb82 Mon Sep 17 00:00:00 2001 From: Nico Bihan Date: Thu, 17 Apr 2025 22:19:40 -0500 Subject: [PATCH 01/15] Gemini 2.5 Flash Preview fix Max Tokens Count (#2735) Gemini 2.5 Flash Preview fix Max Tokens --- src/shared/api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/api.ts b/src/shared/api.ts index da7b6d79b8..391eecd5ed 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -486,7 +486,7 @@ export const vertexModels = { outputPrice: 0.6, }, "gemini-2.5-flash-preview-04-17": { - maxTokens: 65_536, + maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, supportsPromptCache: false, @@ -641,7 +641,7 @@ export type GeminiModelId = keyof typeof geminiModels export const geminiDefaultModelId: GeminiModelId = "gemini-2.0-flash-001" export const geminiModels = { "gemini-2.5-flash-preview-04-17": { - maxTokens: 65_536, + maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, supportsPromptCache: false, From 86636526bd467fb06d8c08be311b7f09b5c2d681 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Thu, 17 Apr 2025 23:25:23 -0400 Subject: [PATCH 02/15] Update the style of the suggestions (#2734) * Update the style of the suggestions * Cleanup --- webview-ui/src/components/chat/FollowUpSuggest.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/webview-ui/src/components/chat/FollowUpSuggest.tsx b/webview-ui/src/components/chat/FollowUpSuggest.tsx index b300add5fb..5f1402bf44 100644 --- a/webview-ui/src/components/chat/FollowUpSuggest.tsx +++ b/webview-ui/src/components/chat/FollowUpSuggest.tsx @@ -1,5 +1,5 @@ import { useCallback } from "react" -import { ArrowRight, Edit } from "lucide-react" +import { Edit } from "lucide-react" import { Button } from "@/components/ui" @@ -26,18 +26,15 @@ export const FollowUpSuggest = ({ suggestions = [], onSuggestionClick, ts = 1 }: } return ( -
+
{suggestions.map((suggestion) => (
Date: Thu, 17 Apr 2025 23:25:35 -0400 Subject: [PATCH 03/15] Fix context window bar color (#2733) * Fix context window bar color * Make task header cost badge match the other ones * Fix test --- .../src/__tests__/ContextWindowProgress.test.tsx | 5 +++++ webview-ui/src/components/chat/TaskHeader.tsx | 11 ++++++----- .../src/components/chat/__tests__/TaskHeader.test.tsx | 5 +++++ .../src/i18n/__tests__/TranslationContext.test.tsx | 6 ------ webview-ui/src/setupTests.ts | 4 ++++ 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/webview-ui/src/__tests__/ContextWindowProgress.test.tsx b/webview-ui/src/__tests__/ContextWindowProgress.test.tsx index bf0af4598d..431cb6136e 100644 --- a/webview-ui/src/__tests__/ContextWindowProgress.test.tsx +++ b/webview-ui/src/__tests__/ContextWindowProgress.test.tsx @@ -9,6 +9,11 @@ jest.mock("@/utils/format", () => ({ formatLargeNumber: jest.fn((num) => num.toString()), })) +// Mock VSCodeBadge component for all tests +jest.mock("@vscode/webview-ui-toolkit/react", () => ({ + VSCodeBadge: ({ children }: { children: React.ReactNode }) =>
{children}
, +})) + // Mock ExtensionStateContext since we use useExtensionState jest.mock("../context/ExtensionStateContext", () => ({ useExtensionState: jest.fn(() => ({ diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx index 3053099b10..982b76bc0d 100644 --- a/webview-ui/src/components/chat/TaskHeader.tsx +++ b/webview-ui/src/components/chat/TaskHeader.tsx @@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next" import { vscode } from "@/utils/vscode" import { formatLargeNumber } from "@/utils/format" import { calculateTokenDistribution, getMaxTokensForModel } from "@/utils/model-utils" -import { Button, Badge } from "@/components/ui" +import { Button } from "@/components/ui" import { ClineMessage } from "../../../../src/shared/ExtensionMessage" import { mentionRegexGlobal } from "../../../../src/shared/context-mentions" @@ -17,6 +17,7 @@ import Thumbnails from "../common/Thumbnails" import { normalizeApiConfiguration } from "../settings/ApiOptions" import { DeleteTaskDialog } from "../history/DeleteTaskDialog" import { cn } from "@/lib/utils" +import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react" interface TaskHeaderProps { task: ClineMessage @@ -95,7 +96,7 @@ const TaskHeader: React.FC = ({ contextTokens={contextTokens || 0} maxTokens={getMaxTokensForModel(selectedModelInfo, apiConfiguration)} /> - {!!totalCost && ${totalCost.toFixed(2)}} + {!!totalCost && ${totalCost.toFixed(2)}}
)} {/* Expanded state: Show task text and images */} @@ -278,7 +279,7 @@ const ContextWindowProgress = ({ contextWindow, contextTokens, maxTokens }: Cont /> {/* Main progress bar container */} -
+
{/* Current tokens container */}
{/* Invisible overlay for current tokens section */} @@ -291,7 +292,7 @@ const ContextWindowProgress = ({ contextWindow, contextTokens, maxTokens }: Cont data-testid="context-tokens-used" /> {/* Current tokens used - darkest */} -
+
{/* Container for reserved tokens */} @@ -305,7 +306,7 @@ const ContextWindowProgress = ({ contextWindow, contextTokens, maxTokens }: Cont data-testid="context-reserved-tokens" /> {/* Reserved for output section - medium gray */} -
+
{/* Empty section (if any) */} diff --git a/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx b/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx index 6a81a9b540..6880de29d7 100644 --- a/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx +++ b/webview-ui/src/components/chat/__tests__/TaskHeader.test.tsx @@ -12,6 +12,11 @@ jest.mock("@/utils/vscode", () => ({ }, })) +// Mock the VSCodeBadge component +jest.mock("@vscode/webview-ui-toolkit/react", () => ({ + VSCodeBadge: ({ children }: { children: React.ReactNode }) =>
{children}
, +})) + // Mock the ExtensionStateContext jest.mock("../../../context/ExtensionStateContext", () => ({ useExtensionState: () => ({ diff --git a/webview-ui/src/i18n/__tests__/TranslationContext.test.tsx b/webview-ui/src/i18n/__tests__/TranslationContext.test.tsx index 0b4dbf8238..78fbfc23d4 100644 --- a/webview-ui/src/i18n/__tests__/TranslationContext.test.tsx +++ b/webview-ui/src/i18n/__tests__/TranslationContext.test.tsx @@ -2,7 +2,6 @@ import React from "react" import { render } from "@testing-library/react" import "@testing-library/jest-dom" import TranslationProvider, { useAppTranslation } from "../TranslationContext" -import { setupI18nForTests } from "../test-utils" // Mock the useExtensionState hook jest.mock("@/context/ExtensionStateContext", () => ({ @@ -23,11 +22,6 @@ const TestComponent = () => { } describe("TranslationContext", () => { - beforeAll(() => { - // Initialize i18next with test translations - setupI18nForTests() - }) - it("should provide translations via context", () => { const { getByTestId } = render( diff --git a/webview-ui/src/setupTests.ts b/webview-ui/src/setupTests.ts index 298fa25f8f..01034af37f 100644 --- a/webview-ui/src/setupTests.ts +++ b/webview-ui/src/setupTests.ts @@ -1,4 +1,8 @@ import "@testing-library/jest-dom" +import { setupI18nForTests } from "./i18n/test-utils" + +// Set up i18n for all tests +setupI18nForTests() // Mock crypto.getRandomValues Object.defineProperty(window, "crypto", { From fffebf1a2e6f576f0bd83fabbec9a3f3994b3403 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 18 Apr 2025 00:28:55 -0400 Subject: [PATCH 04/15] Remove experiment for append block (#2738) * Remove experiment for append block * Fix bugs in experiment lookups --- .../__snapshots__/system.test.ts.snap | 300 +++--------------- src/core/prompts/sections/rules.ts | 17 +- src/core/prompts/tools/index.ts | 11 +- src/exports/roo-code.d.ts | 1 - src/exports/types.ts | 1 - src/schemas/index.ts | 3 +- src/shared/__tests__/experiments.test.ts | 3 - src/shared/experiments.ts | 2 - src/shared/modes.ts | 7 +- 9 files changed, 63 insertions(+), 282 deletions(-) diff --git a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap index 380aee682f..70fa15d5be 100644 --- a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap +++ b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap @@ -361,6 +361,9 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. +- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. @@ -865,7 +868,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), insert_content (for adding lines to existing files), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files), insert_content (for adding lines to existing files), search_and_replace (for finding and replacing individual pieces of text). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. - The insert_content tool adds lines of text to files, such as adding a new function to a JavaScript file or inserting a new route in a Python file. This tool will insert it at the specified line location. It can support multiple operations at once. - The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. @@ -1338,7 +1342,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), search_and_replace (for finding and replacing individual pieces of text). +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files), search_and_replace (for finding and replacing individual pieces of text). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. - The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. @@ -1760,6 +1765,9 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. +- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. @@ -2179,6 +2187,9 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. +- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. @@ -2598,6 +2609,9 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. +- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. @@ -3072,6 +3086,9 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. +- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. @@ -3560,6 +3577,9 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. +- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. @@ -4034,6 +4054,9 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. +- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. @@ -4544,7 +4567,8 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using apply_diff or write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. -- For editing files, you have access to these tools: apply_diff (for replacing lines in existing files), write_to_file (for creating new files or complete file rewrites). +- For editing files, you have access to these tools: apply_diff (for replacing lines in existing files), write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. - You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. @@ -4965,6 +4989,9 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. +- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. @@ -5266,91 +5293,6 @@ Example: Requesting to append to a log file -## insert_content -Description: Inserts content at specific line positions in a file. This is the primary tool for adding new content and code (functions/methods/classes, imports, attributes etc.) as it allows for precise insertions without overwriting existing content. The tool uses an efficient line-based insertion system that maintains file integrity and proper ordering of multiple insertions. Beware to use the proper indentation. This tool is the preferred way to add new content and code to files. -Parameters: -- path: (required) The path of the file to insert content into (relative to the current workspace directory /test/path) -- operations: (required) A JSON array of insertion operations. Each operation is an object with: - * start_line: (required) The line number where the content should be inserted. The content currently at that line will end up below the inserted content. - * content: (required) The content to insert at the specified position. IMPORTANT NOTE: If the content is a single line, it can be a string. If it's a multi-line content, it should be a string with newline characters ( -) for line breaks. Make sure to include the correct indentation for the content. -Usage: - -File path here -[ - { - "start_line": 10, - "content": "Your content here" - } -] - -Example: Insert a new function and its import statement - -File path here -[ - { - "start_line": 1, - "content": "import { sum } from './utils';" - }, - { - "start_line": 10, - "content": "function calculateTotal(items: number[]): number { - return items.reduce((sum, item) => sum + item, 0); -}" - } -] - - -## search_and_replace -Description: Request to perform search and replace operations on a file. Each operation can specify a search pattern (string or regex) and replacement text, with optional line range restrictions and regex flags. Shows a diff preview before applying changes. -Parameters: -- path: (required) The path of the file to modify (relative to the current workspace directory /test/path) -- operations: (required) A JSON array of search/replace operations. Each operation is an object with: - * search: (required) The text or pattern to search for - * replace: (required) The text to replace matches with. If multiple lines need to be replaced, use " -" for newlines - * start_line: (optional) Starting line number for restricted replacement - * end_line: (optional) Ending line number for restricted replacement - * use_regex: (optional) Whether to treat search as a regex pattern - * ignore_case: (optional) Whether to ignore case when matching - * regex_flags: (optional) Additional regex flags when use_regex is true -Usage: - -File path here -[ - { - "search": "text to find", - "replace": "replacement text", - "start_line": 1, - "end_line": 10 - } -] - -Example: Replace "foo" with "bar" in lines 1-10 of example.ts - -example.ts -[ - { - "search": "foo", - "replace": "bar", - "start_line": 1, - "end_line": 10 - } -] - -Example: Replace all occurrences of "old" with "new" using regex - -example.ts -[ - { - "search": "old\\w+", - "replace": "new$&", - "use_regex": true, - "ignore_case": true - } -] - - ## execute_command Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: \`touch ./testdata/example.file\`, \`dir ./examples/model1/data/yaml\`, or \`go test ./cmd/front --config ./cmd/front/config.yml\`. If directed by the user, you may open a terminal in a different directory by using the \`cwd\` parameter. Parameters: @@ -5573,6 +5515,9 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. +- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. @@ -5847,91 +5792,6 @@ Example: Requesting to append to a log file -## insert_content -Description: Inserts content at specific line positions in a file. This is the primary tool for adding new content and code (functions/methods/classes, imports, attributes etc.) as it allows for precise insertions without overwriting existing content. The tool uses an efficient line-based insertion system that maintains file integrity and proper ordering of multiple insertions. Beware to use the proper indentation. This tool is the preferred way to add new content and code to files. -Parameters: -- path: (required) The path of the file to insert content into (relative to the current workspace directory /test/path) -- operations: (required) A JSON array of insertion operations. Each operation is an object with: - * start_line: (required) The line number where the content should be inserted. The content currently at that line will end up below the inserted content. - * content: (required) The content to insert at the specified position. IMPORTANT NOTE: If the content is a single line, it can be a string. If it's a multi-line content, it should be a string with newline characters ( -) for line breaks. Make sure to include the correct indentation for the content. -Usage: - -File path here -[ - { - "start_line": 10, - "content": "Your content here" - } -] - -Example: Insert a new function and its import statement - -File path here -[ - { - "start_line": 1, - "content": "import { sum } from './utils';" - }, - { - "start_line": 10, - "content": "function calculateTotal(items: number[]): number { - return items.reduce((sum, item) => sum + item, 0); -}" - } -] - - -## search_and_replace -Description: Request to perform search and replace operations on a file. Each operation can specify a search pattern (string or regex) and replacement text, with optional line range restrictions and regex flags. Shows a diff preview before applying changes. -Parameters: -- path: (required) The path of the file to modify (relative to the current workspace directory /test/path) -- operations: (required) A JSON array of search/replace operations. Each operation is an object with: - * search: (required) The text or pattern to search for - * replace: (required) The text to replace matches with. If multiple lines need to be replaced, use " -" for newlines - * start_line: (optional) Starting line number for restricted replacement - * end_line: (optional) Ending line number for restricted replacement - * use_regex: (optional) Whether to treat search as a regex pattern - * ignore_case: (optional) Whether to ignore case when matching - * regex_flags: (optional) Additional regex flags when use_regex is true -Usage: - -File path here -[ - { - "search": "text to find", - "replace": "replacement text", - "start_line": 1, - "end_line": 10 - } -] - -Example: Replace "foo" with "bar" in lines 1-10 of example.ts - -example.ts -[ - { - "search": "foo", - "replace": "bar", - "start_line": 1, - "end_line": 10 - } -] - -Example: Replace all occurrences of "old" with "new" using regex - -example.ts -[ - { - "search": "old\\w+", - "replace": "new$&", - "use_regex": true, - "ignore_case": true - } -] - - ## ask_followup_question Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth. Parameters: @@ -6070,6 +5930,9 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. +- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. @@ -6421,6 +6284,9 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. +- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. @@ -6713,91 +6579,6 @@ Example: Requesting to append to a log file -## insert_content -Description: Inserts content at specific line positions in a file. This is the primary tool for adding new content and code (functions/methods/classes, imports, attributes etc.) as it allows for precise insertions without overwriting existing content. The tool uses an efficient line-based insertion system that maintains file integrity and proper ordering of multiple insertions. Beware to use the proper indentation. This tool is the preferred way to add new content and code to files. -Parameters: -- path: (required) The path of the file to insert content into (relative to the current workspace directory /test/path) -- operations: (required) A JSON array of insertion operations. Each operation is an object with: - * start_line: (required) The line number where the content should be inserted. The content currently at that line will end up below the inserted content. - * content: (required) The content to insert at the specified position. IMPORTANT NOTE: If the content is a single line, it can be a string. If it's a multi-line content, it should be a string with newline characters ( -) for line breaks. Make sure to include the correct indentation for the content. -Usage: - -File path here -[ - { - "start_line": 10, - "content": "Your content here" - } -] - -Example: Insert a new function and its import statement - -File path here -[ - { - "start_line": 1, - "content": "import { sum } from './utils';" - }, - { - "start_line": 10, - "content": "function calculateTotal(items: number[]): number { - return items.reduce((sum, item) => sum + item, 0); -}" - } -] - - -## search_and_replace -Description: Request to perform search and replace operations on a file. Each operation can specify a search pattern (string or regex) and replacement text, with optional line range restrictions and regex flags. Shows a diff preview before applying changes. -Parameters: -- path: (required) The path of the file to modify (relative to the current workspace directory /test/path) -- operations: (required) A JSON array of search/replace operations. Each operation is an object with: - * search: (required) The text or pattern to search for - * replace: (required) The text to replace matches with. If multiple lines need to be replaced, use " -" for newlines - * start_line: (optional) Starting line number for restricted replacement - * end_line: (optional) Ending line number for restricted replacement - * use_regex: (optional) Whether to treat search as a regex pattern - * ignore_case: (optional) Whether to ignore case when matching - * regex_flags: (optional) Additional regex flags when use_regex is true -Usage: - -File path here -[ - { - "search": "text to find", - "replace": "replacement text", - "start_line": 1, - "end_line": 10 - } -] - -Example: Replace "foo" with "bar" in lines 1-10 of example.ts - -example.ts -[ - { - "search": "foo", - "replace": "bar", - "start_line": 1, - "end_line": 10 - } -] - -Example: Replace all occurrences of "old" with "new" using regex - -example.ts -[ - { - "search": "old\\w+", - "replace": "new$&", - "use_regex": true, - "ignore_case": true - } -] - - ## execute_command Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: \`touch ./testdata/example.file\`, \`dir ./examples/model1/data/yaml\`, or \`go test ./cmd/front --config ./cmd/front/config.yml\`. If directed by the user, you may open a terminal in a different directory by using the \`cwd\` parameter. Parameters: @@ -7026,6 +6807,9 @@ RULES - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '/test/path', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '/test/path'). For example, if you needed to run \`npm install\` in a project outside of '/test/path', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`. - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using write_to_file to make informed changes. - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser. +- For editing files, you have access to these tools: write_to_file (for creating new files or complete file rewrites), append_to_file (for appending content to the end of files). +- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file. +- You should always prefer using other editing tools over write_to_file when making changes to existing files since write_to_file is much slower and cannot handle large files. - When using the write_to_file tool to modify a file, use the tool directly with the desired content. You do not need to display the content before using the tool. ALWAYS provide the COMPLETE file content in your response. This is NON-NEGOTIABLE. Partial updates or placeholders like '// rest of code unchanged' are STRICTLY FORBIDDEN. You MUST include ALL parts of the file, even if they haven't been modified. Failure to do so will result in incomplete or broken code, severely impacting the user's project. - Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode. - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write. diff --git a/src/core/prompts/sections/rules.ts b/src/core/prompts/sections/rules.ts index 2e5d1be5b7..5b4b4dd771 100644 --- a/src/core/prompts/sections/rules.ts +++ b/src/core/prompts/sections/rules.ts @@ -13,19 +13,22 @@ function getEditingInstructions(diffStrategy?: DiffStrategy, experiments?: Recor } else { availableTools.push("write_to_file (for creating new files or complete file rewrites)") } + + availableTools.push("append_to_file (for appending content to the end of files)") + if (experiments?.["insert_content"]) { availableTools.push("insert_content (for adding lines to existing files)") } - if (experiments?.["append_to_file"]) { - availableTools.push("append_to_file (for appending content to the end of files)") - } if (experiments?.["search_and_replace"]) { availableTools.push("search_and_replace (for finding and replacing individual pieces of text)") } // Base editing instruction mentioning all available tools if (availableTools.length > 1) { - instructions.push(`- For editing files, you have access to these tools: ${availableTools.join(", ")}.`) + instructions.push( + `- For editing files, you have access to these tools: ${availableTools.join(", ")}.`, + "- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file.", + ) } // Additional details for experimental features @@ -35,12 +38,6 @@ function getEditingInstructions(diffStrategy?: DiffStrategy, experiments?: Recor ) } - if (experiments?.["append_to_file"]) { - instructions.push( - "- The append_to_file tool adds content to the end of files, such as appending new log entries or adding new data records. This tool will always add the content at the end of the file.", - ) - } - if (experiments?.["search_and_replace"]) { instructions.push( "- The search_and_replace tool finds and replaces text or regex in files. This tool allows you to search for a specific regex pattern or text and replace it with another value. Be cautious when using this tool to ensure you are replacing the correct text. It can support multiple operations at once.", diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index 642b9fd652..d462eace48 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -71,7 +71,16 @@ export function getToolDescriptionsForMode( const toolGroup = TOOL_GROUPS[groupName] if (toolGroup) { toolGroup.tools.forEach((tool) => { - if (isToolAllowedForMode(tool as ToolName, mode, customModes ?? [], experiments ?? {})) { + if ( + isToolAllowedForMode( + tool as ToolName, + mode, + customModes ?? [], + undefined, + undefined, + experiments ?? {}, + ) + ) { tools.add(tool) } }) diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 7c7439f538..70ae8b08b4 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -286,7 +286,6 @@ type GlobalSettings = { search_and_replace: boolean insert_content: boolean powerSteering: boolean - append_to_file: boolean } | undefined language?: diff --git a/src/exports/types.ts b/src/exports/types.ts index 9d91b1e1f7..878f0b95b2 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -289,7 +289,6 @@ type GlobalSettings = { search_and_replace: boolean insert_content: boolean powerSteering: boolean - append_to_file: boolean } | undefined language?: diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 259d968c00..48f5b2989e 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -276,7 +276,7 @@ export type CustomSupportPrompts = z.infer * ExperimentId */ -export const experimentIds = ["search_and_replace", "insert_content", "powerSteering", "append_to_file"] as const +export const experimentIds = ["search_and_replace", "insert_content", "powerSteering"] as const export const experimentIdsSchema = z.enum(experimentIds) @@ -290,7 +290,6 @@ const experimentsSchema = z.object({ search_and_replace: z.boolean(), insert_content: z.boolean(), powerSteering: z.boolean(), - append_to_file: z.boolean(), }) export type Experiments = z.infer diff --git a/src/shared/__tests__/experiments.test.ts b/src/shared/__tests__/experiments.test.ts index 163722230f..ff2f4fd040 100644 --- a/src/shared/__tests__/experiments.test.ts +++ b/src/shared/__tests__/experiments.test.ts @@ -16,7 +16,6 @@ describe("experiments", () => { powerSteering: false, search_and_replace: false, insert_content: false, - append_to_file: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) }) @@ -26,7 +25,6 @@ describe("experiments", () => { powerSteering: true, search_and_replace: false, insert_content: false, - append_to_file: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(true) }) @@ -36,7 +34,6 @@ describe("experiments", () => { search_and_replace: false, insert_content: false, powerSteering: false, - append_to_file: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false) }) diff --git a/src/shared/experiments.ts b/src/shared/experiments.ts index 15c1ab8fbf..aeaefa2c94 100644 --- a/src/shared/experiments.ts +++ b/src/shared/experiments.ts @@ -7,7 +7,6 @@ export const EXPERIMENT_IDS = { INSERT_BLOCK: "insert_content", SEARCH_AND_REPLACE: "search_and_replace", POWER_STEERING: "powerSteering", - APPEND_BLOCK: "append_to_file", } as const satisfies Record type _AssertExperimentIds = AssertEqual>> @@ -22,7 +21,6 @@ export const experimentConfigsMap: Record = { INSERT_BLOCK: { enabled: false }, SEARCH_AND_REPLACE: { enabled: false }, POWER_STEERING: { enabled: false }, - APPEND_BLOCK: { enabled: false }, } export const experimentDefault = Object.fromEntries( diff --git a/src/shared/modes.ts b/src/shared/modes.ts index 9aa4d2eb86..4bec0b01c2 100644 --- a/src/shared/modes.ts +++ b/src/shared/modes.ts @@ -1,9 +1,9 @@ import * as vscode from "vscode" -import { GroupOptions, GroupEntry, ModeConfig, PromptComponent, CustomModePrompts } from "../schemas" +import { GroupOptions, GroupEntry, ModeConfig, PromptComponent, CustomModePrompts, ExperimentId } from "../schemas" import { TOOL_GROUPS, ToolGroup, ALWAYS_AVAILABLE_TOOLS } from "./tools" import { addCustomInstructions } from "../core/prompts/sections/custom-instructions" - +import { EXPERIMENT_IDS } from "./experiments" export type Mode = string export type { GroupOptions, GroupEntry, ModeConfig, PromptComponent, CustomModePrompts } @@ -161,8 +161,7 @@ export function isToolAllowedForMode( if (ALWAYS_AVAILABLE_TOOLS.includes(tool as any)) { return true } - - if (experiments && tool in experiments) { + if (experiments && Object.values(EXPERIMENT_IDS).includes(tool as ExperimentId)) { if (!experiments[tool]) { return false } From b5a77e34a4c43af949a70e0ab237dad0ccf064df Mon Sep 17 00:00:00 2001 From: Nico Bihan Date: Thu, 17 Apr 2025 23:35:06 -0500 Subject: [PATCH 05/15] Fixes maximum token limit for Gemini provider 2.5 pro exp (#2737) Corrects the maximum token limit for the "gemini-2.5-pro-exp-03-25" model, ensuring accurate configuration. --- src/shared/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/api.ts b/src/shared/api.ts index 391eecd5ed..4d71d947ba 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -649,7 +649,7 @@ export const geminiModels = { outputPrice: 0.6, }, "gemini-2.5-pro-exp-03-25": { - maxTokens: 65_536, + maxTokens: 65_535, contextWindow: 1_048_576, supportsImages: true, supportsPromptCache: false, From 87af3b3424b9e6a36bd7b93689dbb80003a8ccd4 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Thu, 17 Apr 2025 22:21:14 -0700 Subject: [PATCH 06/15] Record tool usages in the `Cline` object, and persist them in the db for evals (#2729) --- evals/apps/cli/src/index.ts | 7 +- evals/apps/web/src/app/home.tsx | 16 +- evals/apps/web/src/app/runs/[id]/run.tsx | 2 +- evals/apps/web/src/lib/format-currency.ts | 6 - evals/apps/web/src/lib/format-duration.ts | 22 -- evals/apps/web/src/lib/format-tokens.ts | 15 - evals/apps/web/src/lib/formatters.ts | 48 +++ evals/apps/web/src/lib/index.ts | 3 - evals/packages/db/.gitignore | 1 + .../db/drizzle/0003_sweet_chimera.sql | 1 + .../db/drizzle/meta/0003_snapshot.json | 296 ++++++++++++++++++ evals/packages/db/drizzle/meta/_journal.json | 7 + evals/packages/db/package.json | 5 +- .../db/src/queries/__tests__/runs.test.ts | 87 +++++ evals/packages/db/src/queries/runs.ts | 22 +- evals/packages/db/src/schema.ts | 7 +- evals/packages/db/tsconfig.json | 3 + evals/packages/db/vitest.config.ts | 7 + evals/packages/db/vitest.setup.ts | 20 ++ evals/packages/types/src/roo-code-defaults.ts | 1 + evals/packages/types/src/roo-code.ts | 48 ++- evals/pnpm-lock.yaml | 6 + evals/turbo.json | 4 +- src/core/Cline.ts | 55 +++- src/core/__tests__/Cline.test.ts | 1 - src/core/__tests__/CodeActionProvider.test.ts | 3 + src/core/__tests__/EditorUtils.test.ts | 3 + src/core/__tests__/mode-validator.test.ts | 2 + .../read-file-maxReadFileLine.test.ts | 7 +- src/core/__tests__/read-file-tool.test.ts | 2 + src/core/__tests__/read-file-xml.test.ts | 4 + .../parse-assistant-message.ts | 3 +- src/core/mode-validator.ts | 2 +- src/core/prompts/tools/index.ts | 12 +- .../__tests__/executeCommandTool.test.ts | 2 + src/core/tools/accessMcpResourceTool.ts | 19 +- src/core/tools/appendToFileTool.ts | 25 +- src/core/tools/applyDiffTool.ts | 34 +- src/core/tools/askFollowupQuestionTool.ts | 14 +- src/core/tools/attemptCompletionTool.ts | 18 +- src/core/tools/browserActionTool.ts | 30 +- src/core/tools/executeCommandTool.ts | 9 +- src/core/tools/fetchInstructionsTool.ts | 27 +- src/core/tools/insertContentTool.ts | 13 +- src/core/tools/listCodeDefinitionNamesTool.ts | 22 +- src/core/tools/listFilesTool.ts | 21 +- src/core/tools/newTaskTool.ts | 9 + src/core/tools/readFileTool.ts | 18 +- src/core/tools/searchAndReplaceTool.ts | 19 +- src/core/tools/searchFilesTool.ts | 23 +- src/core/tools/switchModeTool.ts | 23 +- src/core/tools/useMcpToolTool.ts | 26 +- src/core/tools/writeToFileTool.ts | 27 +- src/exports/api.ts | 6 +- src/exports/roo-code.d.ts | 6 + src/exports/types.ts | 6 + src/schemas/index.ts | 41 ++- src/shared/tools.ts | 33 +- 58 files changed, 977 insertions(+), 222 deletions(-) delete mode 100644 evals/apps/web/src/lib/format-currency.ts delete mode 100644 evals/apps/web/src/lib/format-duration.ts delete mode 100644 evals/apps/web/src/lib/format-tokens.ts create mode 100644 evals/apps/web/src/lib/formatters.ts delete mode 100644 evals/apps/web/src/lib/index.ts create mode 100644 evals/packages/db/.gitignore create mode 100644 evals/packages/db/drizzle/0003_sweet_chimera.sql create mode 100644 evals/packages/db/drizzle/meta/0003_snapshot.json create mode 100644 evals/packages/db/src/queries/__tests__/runs.test.ts create mode 100644 evals/packages/db/vitest.config.ts create mode 100644 evals/packages/db/vitest.setup.ts diff --git a/evals/apps/cli/src/index.ts b/evals/apps/cli/src/index.ts index 61c0a019f6..d552092fa5 100644 --- a/evals/apps/cli/src/index.ts +++ b/evals/apps/cli/src/index.ts @@ -275,7 +275,12 @@ const runExercise = async ({ run, task, server }: { run: Run; task: Task; server }) } - if (eventName === RooCodeEventName.TaskCompleted || eventName === RooCodeEventName.TaskAborted) { + if (eventName === RooCodeEventName.TaskCompleted && taskMetricsId) { + const toolUsage = payload[2] + await updateTaskMetrics(taskMetricsId, { toolUsage }) + } + + if (eventName === RooCodeEventName.TaskAborted || eventName === RooCodeEventName.TaskCompleted) { taskFinishedAt = Date.now() await updateTask(task.id, { finishedAt: new Date() }) } diff --git a/evals/apps/web/src/app/home.tsx b/evals/apps/web/src/app/home.tsx index 6ba4a34ede..90f9d02b3e 100644 --- a/evals/apps/web/src/app/home.tsx +++ b/evals/apps/web/src/app/home.tsx @@ -8,7 +8,7 @@ import { Ellipsis, Rocket } from "lucide-react" import type { Run, TaskMetrics } from "@evals/db" import { deleteRun } from "@/lib/server/runs" -import { formatCurrency, formatDuration, formatTokens } from "@/lib" +import { formatCurrency, formatDuration, formatTokens, formatToolUsageSuccessRate } from "@/lib/formatters" import { Button, Table, @@ -59,7 +59,8 @@ export function Home({ runs }: { runs: (Run & { taskMetrics: TaskMetrics | null Passed Failed % Correct - Tokens In / Out + Tokens In / Out + Diff Edits Cost Duration @@ -79,12 +80,21 @@ export function Home({ runs }: { runs: (Run & { taskMetrics: TaskMetrics | null {taskMetrics && ( -
+
{formatTokens(taskMetrics.tokensIn)}
/
{formatTokens(taskMetrics.tokensOut)}
)} + + {taskMetrics?.toolUsage?.apply_diff && ( +
+
{taskMetrics.toolUsage.apply_diff.attempts}
+
/
+
{formatToolUsageSuccessRate(taskMetrics.toolUsage.apply_diff)}
+
+ )} +
{taskMetrics && formatCurrency(taskMetrics.cost)} {taskMetrics && formatDuration(taskMetrics.duration)} diff --git a/evals/apps/web/src/app/runs/[id]/run.tsx b/evals/apps/web/src/app/runs/[id]/run.tsx index 84749fc916..9d5e74f98b 100644 --- a/evals/apps/web/src/app/runs/[id]/run.tsx +++ b/evals/apps/web/src/app/runs/[id]/run.tsx @@ -5,7 +5,7 @@ import { LoaderCircle } from "lucide-react" import * as db from "@evals/db" -import { formatCurrency, formatDuration, formatTokens } from "@/lib" +import { formatCurrency, formatDuration, formatTokens } from "@/lib/formatters" import { useRunStatus } from "@/hooks/use-run-status" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui" diff --git a/evals/apps/web/src/lib/format-currency.ts b/evals/apps/web/src/lib/format-currency.ts deleted file mode 100644 index c628815951..0000000000 --- a/evals/apps/web/src/lib/format-currency.ts +++ /dev/null @@ -1,6 +0,0 @@ -const formatter = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", -}) - -export const formatCurrency = (amount: number) => formatter.format(amount) diff --git a/evals/apps/web/src/lib/format-duration.ts b/evals/apps/web/src/lib/format-duration.ts deleted file mode 100644 index 7de767f947..0000000000 --- a/evals/apps/web/src/lib/format-duration.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const formatDuration = (durationMs: number) => { - const seconds = Math.floor(durationMs / 1000) - const hours = Math.floor(seconds / 3600) - const minutes = Math.floor((seconds % 3600) / 60) - const remainingSeconds = seconds % 60 - - const parts = [] - - if (hours > 0) { - parts.push(`${hours}h`) - } - - if (minutes > 0) { - parts.push(`${minutes}m`) - } - - if (remainingSeconds > 0 || parts.length === 0) { - parts.push(`${remainingSeconds}s`) - } - - return parts.join(" ") -} diff --git a/evals/apps/web/src/lib/format-tokens.ts b/evals/apps/web/src/lib/format-tokens.ts deleted file mode 100644 index c51009478a..0000000000 --- a/evals/apps/web/src/lib/format-tokens.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const formatTokens = (tokens: number) => { - if (tokens < 1000) { - return tokens.toString() - } - - if (tokens < 1000000) { - return `${(tokens / 1000).toFixed(1)}k` - } - - if (tokens < 1000000000) { - return `${(tokens / 1000000).toFixed(1)}M` - } - - return `${(tokens / 1000000000).toFixed(1)}B` -} diff --git a/evals/apps/web/src/lib/formatters.ts b/evals/apps/web/src/lib/formatters.ts new file mode 100644 index 0000000000..207e13a5e1 --- /dev/null +++ b/evals/apps/web/src/lib/formatters.ts @@ -0,0 +1,48 @@ +const formatter = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", +}) + +export const formatCurrency = (amount: number) => formatter.format(amount) + +export const formatDuration = (durationMs: number) => { + const seconds = Math.floor(durationMs / 1000) + const hours = Math.floor(seconds / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + const remainingSeconds = seconds % 60 + + const parts = [] + + if (hours > 0) { + parts.push(`${hours}h`) + } + + if (minutes > 0) { + parts.push(`${minutes}m`) + } + + if (remainingSeconds > 0 || parts.length === 0) { + parts.push(`${remainingSeconds}s`) + } + + return parts.join(" ") +} + +export const formatTokens = (tokens: number) => { + if (tokens < 1000) { + return tokens.toString() + } + + if (tokens < 1000000) { + return `${(tokens / 1000).toFixed(1)}k` + } + + if (tokens < 1000000000) { + return `${(tokens / 1000000).toFixed(1)}M` + } + + return `${(tokens / 1000000000).toFixed(1)}B` +} + +export const formatToolUsageSuccessRate = (usage: { attempts: number; failures: number }) => + usage.attempts === 0 ? '0%' : `${(((usage.attempts - usage.failures) / usage.attempts) * 100).toFixed(1)}%` diff --git a/evals/apps/web/src/lib/index.ts b/evals/apps/web/src/lib/index.ts deleted file mode 100644 index f4262c384f..0000000000 --- a/evals/apps/web/src/lib/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { formatCurrency } from "./format-currency" -export { formatDuration } from "./format-duration" -export { formatTokens } from "./format-tokens" diff --git a/evals/packages/db/.gitignore b/evals/packages/db/.gitignore new file mode 100644 index 0000000000..c370cb644f --- /dev/null +++ b/evals/packages/db/.gitignore @@ -0,0 +1 @@ +test.db diff --git a/evals/packages/db/drizzle/0003_sweet_chimera.sql b/evals/packages/db/drizzle/0003_sweet_chimera.sql new file mode 100644 index 0000000000..7248ec01df --- /dev/null +++ b/evals/packages/db/drizzle/0003_sweet_chimera.sql @@ -0,0 +1 @@ +ALTER TABLE `taskMetrics` ADD `toolUsage` text; \ No newline at end of file diff --git a/evals/packages/db/drizzle/meta/0003_snapshot.json b/evals/packages/db/drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000000..0b7fa5b94d --- /dev/null +++ b/evals/packages/db/drizzle/meta/0003_snapshot.json @@ -0,0 +1,296 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "61d48d20-f662-445d-9962-cf9cb165cbe7", + "prevId": "f49d9b0b-fda9-467a-9adb-c941d6cbf7ce", + "tables": { + "runs": { + "name": "runs", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "taskMetricsId": { + "name": "taskMetricsId", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "blob", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pid": { + "name": "pid", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "socketPath": { + "name": "socketPath", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "concurrency": { + "name": "concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 2 + }, + "passed": { + "name": "passed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "failed": { + "name": "failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "runs_taskMetricsId_taskMetrics_id_fk": { + "name": "runs_taskMetricsId_taskMetrics_id_fk", + "tableFrom": "runs", + "tableTo": "taskMetrics", + "columnsFrom": ["taskMetricsId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "taskMetrics": { + "name": "taskMetrics", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "tokensIn": { + "name": "tokensIn", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tokensOut": { + "name": "tokensOut", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tokensContext": { + "name": "tokensContext", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cacheWrites": { + "name": "cacheWrites", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cacheReads": { + "name": "cacheReads", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cost": { + "name": "cost", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "toolUsage": { + "name": "toolUsage", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tasks": { + "name": "tasks", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "runId": { + "name": "runId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "taskMetricsId": { + "name": "taskMetricsId", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "exercise": { + "name": "exercise", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "passed": { + "name": "passed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "startedAt": { + "name": "startedAt", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finishedAt": { + "name": "finishedAt", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tasks_language_exercise_idx": { + "name": "tasks_language_exercise_idx", + "columns": ["runId", "language", "exercise"], + "isUnique": true + } + }, + "foreignKeys": { + "tasks_runId_runs_id_fk": { + "name": "tasks_runId_runs_id_fk", + "tableFrom": "tasks", + "tableTo": "runs", + "columnsFrom": ["runId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasks_taskMetricsId_taskMetrics_id_fk": { + "name": "tasks_taskMetricsId_taskMetrics_id_fk", + "tableFrom": "tasks", + "tableTo": "taskMetrics", + "columnsFrom": ["taskMetricsId"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/evals/packages/db/drizzle/meta/_journal.json b/evals/packages/db/drizzle/meta/_journal.json index c35d084ff7..d40254559a 100644 --- a/evals/packages/db/drizzle/meta/_journal.json +++ b/evals/packages/db/drizzle/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1743698195142, "tag": "0002_white_flatman", "breakpoints": true + }, + { + "idx": 3, + "version": "6", + "when": 1744950664129, + "tag": "0003_sweet_chimera", + "breakpoints": true } ] } diff --git a/evals/packages/db/package.json b/evals/packages/db/package.json index 833750e7d5..ffc298ea01 100644 --- a/evals/packages/db/package.json +++ b/evals/packages/db/package.json @@ -6,6 +6,7 @@ "scripts": { "lint": "eslint src/**/*.ts --max-warnings=0", "check-types": "tsc --noEmit", + "test": "vitest --globals --run", "format": "prettier --write src", "drizzle-kit": "dotenvx run -f ../../.env -- tsx node_modules/drizzle-kit/bin.cjs", "db:generate": "pnpm drizzle-kit generate", @@ -29,6 +30,8 @@ "devDependencies": { "@evals/eslint-config": "workspace:^", "@evals/typescript-config": "workspace:^", - "drizzle-kit": "^0.30.5" + "drizzle-kit": "^0.30.5", + "execa": "^9.5.2", + "vitest": "^3.0.9" } } diff --git a/evals/packages/db/src/queries/__tests__/runs.test.ts b/evals/packages/db/src/queries/__tests__/runs.test.ts new file mode 100644 index 0000000000..9032871176 --- /dev/null +++ b/evals/packages/db/src/queries/__tests__/runs.test.ts @@ -0,0 +1,87 @@ +import { createRun, finishRun } from "../runs.js" +import { createTask } from "../tasks.js" +import { createTaskMetrics } from "../taskMetrics.js" + +describe("finishRun", () => { + it("aggregates task metrics, including tool usage", async () => { + const run = await createRun({ model: "gpt-4.1-mini", socketPath: "/tmp/roo.sock" }) + + await createTask({ + runId: run.id, + taskMetricsId: ( + await createTaskMetrics({ + duration: 45_000, + tokensIn: 100_000, + tokensOut: 2_000, + tokensContext: 102_000, + cacheWrites: 0, + cacheReads: 0, + cost: 0.05, + toolUsage: { + read_file: { + attempts: 3, + failures: 0, + }, + apply_diff: { + attempts: 3, + failures: 1, + }, + }, + }) + ).id, + language: "go", + exercise: "go/say", + passed: true, + startedAt: new Date(), + finishedAt: new Date(), + }) + + await createTask({ + runId: run.id, + taskMetricsId: ( + await createTaskMetrics({ + duration: 30_000, + tokensIn: 75_000, + tokensOut: 1_000, + tokensContext: 76_000, + cacheWrites: 0, + cacheReads: 0, + cost: 0.04, + toolUsage: { + read_file: { + attempts: 3, + failures: 0, + }, + apply_diff: { + attempts: 2, + failures: 0, + }, + }, + }) + ).id, + language: "go", + exercise: "go/octal", + passed: true, + startedAt: new Date(), + finishedAt: new Date(), + }) + + const { taskMetrics } = await finishRun(run.id) + + expect(taskMetrics).toEqual({ + id: expect.any(Number), + tokensIn: 175000, + tokensOut: 3000, + tokensContext: 178000, + cacheWrites: 0, + cacheReads: 0, + cost: 0.09, + duration: 75000, + toolUsage: { + read_file: { attempts: 6, failures: 0 }, + apply_diff: { attempts: 5, failures: 1 }, + }, + createdAt: expect.any(Date), + }) + }) +}) diff --git a/evals/packages/db/src/queries/runs.ts b/evals/packages/db/src/queries/runs.ts index 88d446f284..1a4f6d4c57 100644 --- a/evals/packages/db/src/queries/runs.ts +++ b/evals/packages/db/src/queries/runs.ts @@ -1,10 +1,13 @@ import { desc, eq, inArray, sql, sum } from "drizzle-orm" +import { ToolUsage } from "@evals/types" + import { RecordNotFoundError, RecordNotCreatedError } from "./errors.js" import type { InsertRun, UpdateRun } from "../schema.js" import { insertRunSchema, schema } from "../schema.js" import { db } from "../db.js" import { createTaskMetrics } from "./taskMetrics.js" +import { getTasks } from "./tasks.js" const table = schema.runs @@ -71,17 +74,30 @@ export const finishRun = async (runId: number) => { throw new RecordNotFoundError() } + const tasks = await getTasks(runId) + + const toolUsage = tasks.reduce((acc, task) => { + Object.entries(task.taskMetrics?.toolUsage || {}).forEach(([key, { attempts, failures }]) => { + const tool = key as keyof ToolUsage + acc[tool] ??= { attempts: 0, failures: 0 } + acc[tool].attempts += attempts + acc[tool].failures += failures + }) + + return acc + }, {} as ToolUsage) + const { passed, failed, ...rest } = values - const taskMetrics = await createTaskMetrics(rest) + const taskMetrics = await createTaskMetrics({ ...rest, toolUsage }) await updateRun(runId, { taskMetricsId: taskMetrics.id, passed, failed }) - const run = await db.query.runs.findFirst({ where: eq(table.id, runId), with: { taskMetrics: true } }) + const run = await findRun(runId) if (!run) { throw new RecordNotFoundError() } - return run + return { ...run, taskMetrics } } export const deleteRun = async (runId: number) => { diff --git a/evals/packages/db/src/schema.ts b/evals/packages/db/src/schema.ts index f2fa86a826..902bb91a42 100644 --- a/evals/packages/db/src/schema.ts +++ b/evals/packages/db/src/schema.ts @@ -2,7 +2,7 @@ import { sqliteTable, text, real, integer, blob, uniqueIndex } from "drizzle-orm import { relations } from "drizzle-orm" import { createInsertSchema } from "drizzle-zod" -import { RooCodeSettings, exerciseLanguages, rooCodeSettingsSchema } from "@evals/types" +import { RooCodeSettings, ToolUsage, exerciseLanguages, rooCodeSettingsSchema, toolUsageSchema } from "@evals/types" /** * runs @@ -84,12 +84,15 @@ export const taskMetrics = sqliteTable("taskMetrics", { cacheReads: integer({ mode: "number" }).notNull(), cost: real().notNull(), duration: integer({ mode: "number" }).notNull(), + toolUsage: text({ mode: "json" }).$type(), createdAt: integer({ mode: "timestamp" }).notNull(), }) export type TaskMetrics = typeof taskMetrics.$inferSelect -export const insertTaskMetricsSchema = createInsertSchema(taskMetrics).omit({ id: true, createdAt: true }) +export const insertTaskMetricsSchema = createInsertSchema(taskMetrics) + .omit({ id: true, createdAt: true }) + .extend({ toolUsage: toolUsageSchema.optional() }) export type InsertTaskMetrics = Omit diff --git a/evals/packages/db/tsconfig.json b/evals/packages/db/tsconfig.json index 48fa99573e..e23679a84c 100644 --- a/evals/packages/db/tsconfig.json +++ b/evals/packages/db/tsconfig.json @@ -1,5 +1,8 @@ { "extends": "@evals/typescript-config/base.json", + "compilerOptions": { + "types": ["vitest/globals"] + }, "include": ["src"], "exclude": ["node_modules"] } diff --git a/evals/packages/db/vitest.config.ts b/evals/packages/db/vitest.config.ts new file mode 100644 index 0000000000..e8586252d2 --- /dev/null +++ b/evals/packages/db/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config" + +export default defineConfig({ + test: { + globalSetup: ["./vitest.setup.ts"], + }, +}) diff --git a/evals/packages/db/vitest.setup.ts b/evals/packages/db/vitest.setup.ts new file mode 100644 index 0000000000..c296ef6cf1 --- /dev/null +++ b/evals/packages/db/vitest.setup.ts @@ -0,0 +1,20 @@ +import fs from "node:fs/promises" +import path from "node:path" + +import { execa } from "execa" + +const TEST_DB_PATH = path.join(process.cwd(), "test.db") + +export default async function () { + const exists = await fs.stat(TEST_DB_PATH).catch(() => false) + + if (exists) { + await fs.unlink(TEST_DB_PATH) + } + + await execa({ + env: { BENCHMARKS_DB_PATH: `file:${TEST_DB_PATH}` }, + })`pnpm db:push` + + process.env.BENCHMARKS_DB_PATH = `file:${TEST_DB_PATH}` +} diff --git a/evals/packages/types/src/roo-code-defaults.ts b/evals/packages/types/src/roo-code-defaults.ts index e02bda5d38..596a5810ae 100644 --- a/evals/packages/types/src/roo-code-defaults.ts +++ b/evals/packages/types/src/roo-code-defaults.ts @@ -59,6 +59,7 @@ export const rooCodeDefaults: RooCodeSettings = { search_and_replace: false, insert_content: false, powerSteering: false, + append_to_file: false, }, language: "en", diff --git a/evals/packages/types/src/roo-code.ts b/evals/packages/types/src/roo-code.ts index fc87247ee4..bb525f71b4 100644 --- a/evals/packages/types/src/roo-code.ts +++ b/evals/packages/types/src/roo-code.ts @@ -271,7 +271,7 @@ export type CustomSupportPrompts = z.infer * ExperimentId */ -export const experimentIds = ["search_and_replace", "insert_content", "powerSteering"] as const +export const experimentIds = ["search_and_replace", "insert_content", "powerSteering", "append_to_file"] as const export const experimentIdsSchema = z.enum(experimentIds) @@ -285,6 +285,7 @@ const experimentsSchema = z.object({ search_and_replace: z.boolean(), insert_content: z.boolean(), powerSteering: z.boolean(), + append_to_file: z.boolean(), }) export type Experiments = z.infer @@ -802,6 +803,49 @@ export const tokenUsageSchema = z.object({ export type TokenUsage = z.infer +/** + * ToolName + */ + +export const toolNames = [ + "execute_command", + "read_file", + "write_to_file", + "append_to_file", + "apply_diff", + "insert_content", + "search_and_replace", + "search_files", + "list_files", + "list_code_definition_names", + "browser_action", + "use_mcp_tool", + "access_mcp_resource", + "ask_followup_question", + "attempt_completion", + "switch_mode", + "new_task", + "fetch_instructions", +] as const + +export const toolNamesSchema = z.enum(toolNames) + +export type ToolName = z.infer + +/** + * ToolUsage + */ + +export const toolUsageSchema = z.record( + toolNamesSchema, + z.object({ + attempts: z.number(), + failures: z.number(), + }), +) + +export type ToolUsage = z.infer + /** * RooCodeEvent */ @@ -837,7 +881,7 @@ export const rooCodeEventsSchema = z.object({ [RooCodeEventName.TaskAskResponded]: z.tuple([z.string()]), [RooCodeEventName.TaskAborted]: z.tuple([z.string()]), [RooCodeEventName.TaskSpawned]: z.tuple([z.string(), z.string()]), - [RooCodeEventName.TaskCompleted]: z.tuple([z.string(), tokenUsageSchema]), + [RooCodeEventName.TaskCompleted]: z.tuple([z.string(), tokenUsageSchema, toolUsageSchema]), [RooCodeEventName.TaskTokenUsageUpdated]: z.tuple([z.string(), tokenUsageSchema]), }) diff --git a/evals/pnpm-lock.yaml b/evals/pnpm-lock.yaml index c1f145099a..ef2171d29d 100644 --- a/evals/pnpm-lock.yaml +++ b/evals/pnpm-lock.yaml @@ -274,6 +274,12 @@ importers: drizzle-kit: specifier: ^0.30.5 version: 0.30.5 + execa: + specifier: ^9.5.2 + version: 9.5.2 + vitest: + specifier: ^3.0.9 + version: 3.0.9(@types/node@20.17.24)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3) packages/ipc: dependencies: diff --git a/evals/turbo.json b/evals/turbo.json index 5f567ac63b..5692ec9065 100644 --- a/evals/turbo.json +++ b/evals/turbo.json @@ -15,9 +15,7 @@ ], "tasks": { "lint": {}, - "check-types": { - "dependsOn": [] - }, + "check-types": {}, "test": {}, "format": {}, "dev": { diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 69278bd125..1b06517b79 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -13,7 +13,7 @@ import { serializeError } from "serialize-error" import * as vscode from "vscode" // schemas -import { TokenUsage } from "../schemas" +import { TokenUsage, ToolUsage, ToolName } from "../schemas" // api import { ApiHandler, buildApiHandler } from "../api" @@ -39,7 +39,7 @@ import { GlobalFileNames } from "../shared/globalFileNames" import { defaultModeSlug, getModeBySlug, getFullModeDetails, isToolAllowedForMode } from "../shared/modes" import { EXPERIMENT_IDS, experiments as Experiments, ExperimentId } from "../shared/experiments" import { formatLanguage } from "../shared/language" -import { ToolParamName, ToolName, ToolResponse } from "../shared/tools" +import { ToolParamName, ToolResponse } from "../shared/tools" // services import { UrlContentFetcher } from "../services/browser/UrlContentFetcher" @@ -106,8 +106,8 @@ export type ClineEvents = { taskAskResponded: [] taskAborted: [] taskSpawned: [taskId: string] - taskCompleted: [taskId: string, usage: TokenUsage] - taskTokenUsageUpdated: [taskId: string, usage: TokenUsage] + taskCompleted: [taskId: string, tokenUsage: TokenUsage, toolUsage: ToolUsage] + taskTokenUsageUpdated: [taskId: string, tokenUsage: TokenUsage] } export type ClineOptions = { @@ -189,6 +189,9 @@ export class Cline extends EventEmitter { private didAlreadyUseTool = false private didCompleteReadingStream = false + // metrics + private toolUsage: ToolUsage = {} + constructor({ provider, apiConfiguration, @@ -366,19 +369,15 @@ export class Cline extends EventEmitter { this.emit("message", { action: "updated", message: partialMessage }) } - getTokenUsage() { - const usage = getApiMetrics(combineApiRequests(combineCommandSequences(this.clineMessages.slice(1)))) - this.emit("taskTokenUsageUpdated", this.taskId, usage) - return usage - } - private async saveClineMessages() { try { const taskDir = await this.ensureTaskDirectoryExists() const filePath = path.join(taskDir, GlobalFileNames.uiMessages) await fs.writeFile(filePath, JSON.stringify(this.clineMessages)) - // combined as they are in ChatView - const apiMetrics = this.getTokenUsage() + + const tokenUsage = this.getTokenUsage() + this.emit("taskTokenUsageUpdated", this.taskId, tokenUsage) + const taskMessage = this.clineMessages[0] // first message is always the task say const lastRelevantMessage = this.clineMessages[ @@ -403,11 +402,11 @@ export class Cline extends EventEmitter { number: this.taskNumber, ts: lastRelevantMessage.ts, task: taskMessage.text ?? "", - tokensIn: apiMetrics.totalTokensIn, - tokensOut: apiMetrics.totalTokensOut, - cacheWrites: apiMetrics.totalCacheWrites, - cacheReads: apiMetrics.totalCacheReads, - totalCost: apiMetrics.totalCost, + tokensIn: tokenUsage.totalTokensIn, + tokensOut: tokenUsage.totalTokensOut, + cacheWrites: tokenUsage.totalCacheWrites, + cacheReads: tokenUsage.totalCacheReads, + totalCost: tokenUsage.totalCost, size: taskDirSize, workspace: this.cwd, }) @@ -2693,4 +2692,26 @@ export class Cline extends EventEmitter { public getFileContextTracker(): FileContextTracker { return this.fileContextTracker } + + // Metrics + + public getTokenUsage() { + return getApiMetrics(combineApiRequests(combineCommandSequences(this.clineMessages.slice(1)))) + } + + public recordToolUsage({ toolName, success = true }: { toolName: ToolName; success?: boolean }) { + if (!this.toolUsage[toolName]) { + this.toolUsage[toolName] = { attempts: 0, failures: 0 } + } + + this.toolUsage[toolName].attempts++ + + if (!success) { + this.toolUsage[toolName].failures++ + } + } + + public getToolUsage() { + return this.toolUsage + } } diff --git a/src/core/__tests__/Cline.test.ts b/src/core/__tests__/Cline.test.ts index 90e365caf1..90e26655a8 100644 --- a/src/core/__tests__/Cline.test.ts +++ b/src/core/__tests__/Cline.test.ts @@ -3,7 +3,6 @@ import * as os from "os" import * as path from "path" -import pWaitFor from "p-wait-for" import * as vscode from "vscode" import { Anthropic } from "@anthropic-ai/sdk" diff --git a/src/core/__tests__/CodeActionProvider.test.ts b/src/core/__tests__/CodeActionProvider.test.ts index 6ea2adf894..be462e1e06 100644 --- a/src/core/__tests__/CodeActionProvider.test.ts +++ b/src/core/__tests__/CodeActionProvider.test.ts @@ -1,4 +1,7 @@ +// npx jest src/core/__tests__/CodeActionProvider.test.ts + import * as vscode from "vscode" + import { CodeActionProvider, ACTION_NAMES } from "../CodeActionProvider" import { EditorUtils } from "../EditorUtils" diff --git a/src/core/__tests__/EditorUtils.test.ts b/src/core/__tests__/EditorUtils.test.ts index 1a01838693..44b079fcd1 100644 --- a/src/core/__tests__/EditorUtils.test.ts +++ b/src/core/__tests__/EditorUtils.test.ts @@ -1,4 +1,7 @@ +// npx jest src/core/__tests__/EditorUtils.test.ts + import * as vscode from "vscode" + import { EditorUtils } from "../EditorUtils" // Use simple classes to simulate VSCode's Range and Position behavior. diff --git a/src/core/__tests__/mode-validator.test.ts b/src/core/__tests__/mode-validator.test.ts index 66b23ff2ed..72c08d0028 100644 --- a/src/core/__tests__/mode-validator.test.ts +++ b/src/core/__tests__/mode-validator.test.ts @@ -1,3 +1,5 @@ +// npx jest src/core/__tests__/mode-validator.test.ts + import { isToolAllowedForMode, getModeConfig, modes, ModeConfig } from "../../shared/modes" import { TOOL_GROUPS } from "../../shared/tools" import { validateToolUse } from "../mode-validator" diff --git a/src/core/__tests__/read-file-maxReadFileLine.test.ts b/src/core/__tests__/read-file-maxReadFileLine.test.ts index 3a3f7e97bb..4d9f9e1cfa 100644 --- a/src/core/__tests__/read-file-maxReadFileLine.test.ts +++ b/src/core/__tests__/read-file-maxReadFileLine.test.ts @@ -1,11 +1,14 @@ +// npx jest src/core/__tests__/read-file-maxReadFileLine.test.ts + import * as path from "path" import { countFileLines } from "../../integrations/misc/line-counter" import { readLines } from "../../integrations/misc/read-lines" -import { extractTextFromFile, addLineNumbers } from "../../integrations/misc/extract-text" +import { extractTextFromFile } from "../../integrations/misc/extract-text" import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter" import { isBinaryFile } from "isbinaryfile" import { ReadFileToolUse } from "../../shared/tools" +import { ToolUsage } from "../../schemas" // Mock dependencies jest.mock("../../integrations/misc/line-counter") @@ -69,7 +72,6 @@ describe("read_file tool with maxReadFileLine setting", () => { const mockedCountFileLines = countFileLines as jest.MockedFunction const mockedReadLines = readLines as jest.MockedFunction const mockedExtractTextFromFile = extractTextFromFile as jest.MockedFunction - const mockedAddLineNumbers = addLineNumbers as jest.MockedFunction const mockedParseSourceCodeDefinitionsForFile = parseSourceCodeDefinitionsForFile as jest.MockedFunction< typeof parseSourceCodeDefinitionsForFile > @@ -125,6 +127,7 @@ describe("read_file tool with maxReadFileLine setting", () => { mockCline.getFileContextTracker = jest.fn().mockReturnValue({ trackFileContext: jest.fn().mockResolvedValue(undefined), }) + mockCline.recordToolUsage = jest.fn().mockReturnValue({} as ToolUsage) // Reset tool result toolResult = undefined diff --git a/src/core/__tests__/read-file-tool.test.ts b/src/core/__tests__/read-file-tool.test.ts index c410159d4e..151b6df2bc 100644 --- a/src/core/__tests__/read-file-tool.test.ts +++ b/src/core/__tests__/read-file-tool.test.ts @@ -1,3 +1,5 @@ +// npx jest src/core/__tests__/read-file-tool.test.ts + import * as path from "path" import { countFileLines } from "../../integrations/misc/line-counter" import { readLines } from "../../integrations/misc/read-lines" diff --git a/src/core/__tests__/read-file-xml.test.ts b/src/core/__tests__/read-file-xml.test.ts index 46ca065514..c995003a1a 100644 --- a/src/core/__tests__/read-file-xml.test.ts +++ b/src/core/__tests__/read-file-xml.test.ts @@ -1,3 +1,5 @@ +// npx jest src/core/__tests__/read-file-xml.test.ts + import * as path from "path" import { countFileLines } from "../../integrations/misc/line-counter" @@ -6,6 +8,7 @@ import { extractTextFromFile } from "../../integrations/misc/extract-text" import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter" import { isBinaryFile } from "isbinaryfile" import { ReadFileToolUse } from "../../shared/tools" +import { ToolUsage } from "../../schemas" // Mock dependencies jest.mock("../../integrations/misc/line-counter") @@ -118,6 +121,7 @@ describe("read_file tool XML output structure", () => { mockCline.getFileContextTracker = jest.fn().mockReturnValue({ trackFileContext: jest.fn().mockResolvedValue(undefined), }) + mockCline.recordToolUsage = jest.fn().mockReturnValue({} as ToolUsage) // Reset tool result toolResult = undefined diff --git a/src/core/assistant-message/parse-assistant-message.ts b/src/core/assistant-message/parse-assistant-message.ts index aa97873701..0cac4dfb98 100644 --- a/src/core/assistant-message/parse-assistant-message.ts +++ b/src/core/assistant-message/parse-assistant-message.ts @@ -1,4 +1,5 @@ -import { TextContent, ToolUse, ToolParamName, toolParamNames, toolNames, ToolName } from "../../shared/tools" +import { TextContent, ToolUse, ToolParamName, toolParamNames } from "../../shared/tools" +import { toolNames, ToolName } from "../../schemas" export type AssistantMessageContent = TextContent | ToolUse diff --git a/src/core/mode-validator.ts b/src/core/mode-validator.ts index 8a9ac881c7..4c5e8fbf7f 100644 --- a/src/core/mode-validator.ts +++ b/src/core/mode-validator.ts @@ -1,4 +1,4 @@ -import { ToolName } from "../shared/tools" +import { ToolName } from "../schemas" import { Mode, isToolAllowedForMode, ModeConfig } from "../shared/modes" export function validateToolUse( diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index d462eace48..031196b002 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -1,3 +1,10 @@ +import { ToolName } from "../../../schemas" +import { TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS } from "../../../shared/tools" +import { DiffStrategy } from "../../diff/DiffStrategy" +import { McpHub } from "../../../services/mcp/McpHub" +import { Mode, ModeConfig, getModeConfig, isToolAllowedForMode, getGroupName } from "../../../shared/modes" + +import { ToolArgs } from "./types" import { getExecuteCommandDescription } from "./execute-command" import { getReadFileDescription } from "./read-file" import { getFetchInstructionsDescription } from "./fetch-instructions" @@ -15,11 +22,6 @@ import { getUseMcpToolDescription } from "./use-mcp-tool" import { getAccessMcpResourceDescription } from "./access-mcp-resource" import { getSwitchModeDescription } from "./switch-mode" import { getNewTaskDescription } from "./new-task" -import { DiffStrategy } from "../../diff/DiffStrategy" -import { McpHub } from "../../../services/mcp/McpHub" -import { Mode, ModeConfig, getModeConfig, isToolAllowedForMode, getGroupName } from "../../../shared/modes" -import { ToolName, TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS } from "../../../shared/tools" -import { ToolArgs } from "./types" // Map of tool names to their description functions const toolDescriptionMap: Record string | undefined> = { diff --git a/src/core/tools/__tests__/executeCommandTool.test.ts b/src/core/tools/__tests__/executeCommandTool.test.ts index 859d79ad7f..408c45f994 100644 --- a/src/core/tools/__tests__/executeCommandTool.test.ts +++ b/src/core/tools/__tests__/executeCommandTool.test.ts @@ -6,6 +6,7 @@ import { executeCommandTool } from "../executeCommandTool" import { Cline } from "../../Cline" import { formatResponse } from "../../prompts/responses" import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../../shared/tools" +import { ToolUsage } from "../../../schemas" // Mock dependencies jest.mock("../../Cline") @@ -40,6 +41,7 @@ describe("executeCommandTool", () => { // @ts-expect-error - Jest mock function type issues validateCommand: jest.fn().mockReturnValue(null), }, + recordToolUsage: jest.fn().mockReturnValue({} as ToolUsage), } // @ts-expect-error - Jest mock function type issues diff --git a/src/core/tools/accessMcpResourceTool.ts b/src/core/tools/accessMcpResourceTool.ts index ced110f8b6..0832d8ddac 100644 --- a/src/core/tools/accessMcpResourceTool.ts +++ b/src/core/tools/accessMcpResourceTool.ts @@ -13,6 +13,7 @@ export async function accessMcpResourceTool( ) { const server_name: string | undefined = block.params.server_name const uri: string | undefined = block.params.uri + try { if (block.partial) { const partialMessage = JSON.stringify({ @@ -20,32 +21,42 @@ export async function accessMcpResourceTool( serverName: removeClosingTag("server_name", server_name), uri: removeClosingTag("uri", uri), } satisfies ClineAskUseMcpServer) + await cline.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) return } else { if (!server_name) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "access_mcp_resource", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("access_mcp_resource", "server_name")) return } + if (!uri) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "access_mcp_resource", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("access_mcp_resource", "uri")) return } + cline.consecutiveMistakeCount = 0 + const completeMessage = JSON.stringify({ type: "access_mcp_resource", serverName: server_name, uri, } satisfies ClineAskUseMcpServer) + const didApprove = await askApproval("use_mcp_server", completeMessage) + if (!didApprove) { return } - // now execute the tool + + // Now execute the tool await cline.say("mcp_server_request_started") const resourceResult = await cline.providerRef.deref()?.getMcpHub()?.readResource(server_name, uri) + const resourceResultPretty = resourceResult?.contents .map((item) => { @@ -57,15 +68,19 @@ export async function accessMcpResourceTool( .filter(Boolean) .join("\n\n") || "(Empty response)" - // handle images (image must contain mimetype and blob) + // Handle images (image must contain mimetype and blob) let images: string[] = [] + resourceResult?.contents.forEach((item) => { if (item.mimeType?.startsWith("image") && item.blob) { images.push(item.blob) } }) + await cline.say("mcp_server_response", resourceResultPretty, images) pushToolResult(formatResponse.toolResult(resourceResultPretty, images)) + cline.recordToolUsage({ toolName: "access_mcp_resource" }) + return } } catch (error) { diff --git a/src/core/tools/appendToFileTool.ts b/src/core/tools/appendToFileTool.ts index a812677ae8..882d6401c6 100644 --- a/src/core/tools/appendToFileTool.ts +++ b/src/core/tools/appendToFileTool.ts @@ -23,11 +23,13 @@ export async function appendToFileTool( ) { const relPath: string | undefined = block.params.path let newContent: string | undefined = block.params.content + if (!relPath || !newContent) { return } const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) + if (!accessAllowed) { await cline.say("rooignore_error", relPath) pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) @@ -48,6 +50,7 @@ export async function appendToFileTool( if (newContent.startsWith("```")) { newContent = newContent.split("\n").slice(1).join("\n").trim() } + if (newContent.endsWith("```")) { newContent = newContent.split("\n").slice(0, -1).join("\n").trim() } @@ -68,36 +71,44 @@ export async function appendToFileTool( try { if (block.partial) { - // update gui message + // Update GUI message const partialMessage = JSON.stringify(sharedMessageProps) await cline.ask("tool", partialMessage, block.partial).catch(() => {}) - // update editor + + // Update editor if (!cline.diffViewProvider.isEditing) { await cline.diffViewProvider.open(relPath) } + // If file exists, append newContent to existing content if (fileExists && cline.diffViewProvider.originalContent) { newContent = cline.diffViewProvider.originalContent + "\n" + newContent } - // editor is open, stream content in + + // Editor is open, stream content in await cline.diffViewProvider.update( everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, false, ) + return } else { if (!relPath) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "append_to_file", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("append_to_file", "path")) await cline.diffViewProvider.reset() return } + if (!newContent) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "append_to_file", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("append_to_file", "content")) await cline.diffViewProvider.reset() return } + cline.consecutiveMistakeCount = 0 if (!cline.diffViewProvider.isEditing) { @@ -125,17 +136,21 @@ export async function appendToFileTool( ? formatResponse.createPrettyPatch(relPath, cline.diffViewProvider.originalContent, newContent) : undefined, } satisfies ClineSayTool) + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { await cline.diffViewProvider.revertChanges() return } + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() // Track file edit operation if (relPath) { await cline.getFileContextTracker().trackFileContext(relPath, "roo_edited" as RecordSource) } + cline.didEditFile = true if (userEdits) { @@ -147,6 +162,7 @@ export async function appendToFileTool( diff: userEdits, } satisfies ClineSayTool), ) + pushToolResult( `The user made the following updates to your content:\n\n${userEdits}\n\n` + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + @@ -162,7 +178,10 @@ export async function appendToFileTool( } else { pushToolResult(`The content was successfully appended to ${relPath.toPosix()}.${newProblemsMessage}`) } + + cline.recordToolUsage({ toolName: "append_to_file" }) await cline.diffViewProvider.reset() + return } } catch (error) { diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts index 433d23a42b..ca0adb9e33 100644 --- a/src/core/tools/applyDiffTool.ts +++ b/src/core/tools/applyDiffTool.ts @@ -35,33 +35,36 @@ export async function applyDiffTool( try { if (block.partial) { - // update gui message + // Update GUI message let toolProgressStatus + if (cline.diffStrategy && cline.diffStrategy.getProgressStatus) { toolProgressStatus = cline.diffStrategy.getProgressStatus(block) } const partialMessage = JSON.stringify(sharedMessageProps) - await cline.ask("tool", partialMessage, block.partial, toolProgressStatus).catch(() => {}) return } else { if (!relPath) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "apply_diff", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("apply_diff", "path")) return } + if (!diffContent) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "apply_diff", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("apply_diff", "diff")) return } const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) + if (!accessAllowed) { await cline.say("rooignore_error", relPath) pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) - return } @@ -70,6 +73,7 @@ export async function applyDiffTool( if (!fileExists) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "apply_diff", success: false }) const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` await cline.say("error", formattedError) pushToolResult(formattedError) @@ -87,14 +91,15 @@ export async function applyDiffTool( success: false, error: "No diff strategy available", } + let partResults = "" if (!diffResult.success) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "apply_diff", success: false }) const currentCount = (cline.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1 cline.consecutiveMistakeCountForApplyDiff.set(relPath, currentCount) let formattedError = "" - telemetryService.captureDiffApplicationError(cline.taskId, currentCount) if (diffResult.failParts && diffResult.failParts.length > 0) { @@ -102,14 +107,18 @@ export async function applyDiffTool( if (failPart.success) { continue } + const errorDetails = failPart.details ? JSON.stringify(failPart.details, null, 2) : "" + formattedError = `\n${ failPart.error }${errorDetails ? `\n\nDetails:\n${errorDetails}` : ""}\n` + partResults += formattedError } } else { const errorDetails = diffResult.details ? JSON.stringify(diffResult.details, null, 2) : "" + formattedError = `Unable to apply diff to file: ${absolutePath}\n\n\n${ diffResult.error }${errorDetails ? `\n\nDetails:\n${errorDetails}` : ""}\n` @@ -118,12 +127,14 @@ export async function applyDiffTool( if (currentCount >= 2) { await cline.say("diff_error", formattedError) } + pushToolResult(formattedError) return } cline.consecutiveMistakeCount = 0 cline.consecutiveMistakeCountForApplyDiff.delete(relPath) + // Show diff view before asking for approval cline.diffViewProvider.editType = "modify" await cline.diffViewProvider.open(relPath) @@ -136,26 +147,33 @@ export async function applyDiffTool( } satisfies ClineSayTool) let toolProgressStatus + if (cline.diffStrategy && cline.diffStrategy.getProgressStatus) { toolProgressStatus = cline.diffStrategy.getProgressStatus(block, diffResult) } const didApprove = await askApproval("tool", completeMessage, toolProgressStatus) + if (!didApprove) { - await cline.diffViewProvider.revertChanges() // cline likely handles closing the diff view + await cline.diffViewProvider.revertChanges() // Cline likely handles closing the diff view return } const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() + // Track file edit operation if (relPath) { await cline.getFileContextTracker().trackFileContext(relPath, "roo_edited" as RecordSource) } - cline.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request + + // Used to determine if we should wait for busy terminal to update before sending api request + cline.didEditFile = true let partFailHint = "" + if (diffResult.failParts && diffResult.failParts.length > 0) { partFailHint = `But unable to apply all diff parts to file: ${absolutePath}, silently use tool to check newest file version and re-apply diffs\n` } + if (userEdits) { await cline.say( "user_feedback_diff", @@ -165,6 +183,7 @@ export async function applyDiffTool( diff: userEdits, } satisfies ClineSayTool), ) + pushToolResult( `The user made the following updates to your content:\n\n${userEdits}\n\n` + partFailHint + @@ -183,7 +202,10 @@ export async function applyDiffTool( `Changes successfully applied to ${relPath.toPosix()}:\n\n${newProblemsMessage}\n` + partFailHint, ) } + + cline.recordToolUsage({ toolName: "apply_diff" }) await cline.diffViewProvider.reset() + return } } catch (error) { diff --git a/src/core/tools/askFollowupQuestionTool.ts b/src/core/tools/askFollowupQuestionTool.ts index 2e7263ad58..4bfc641137 100644 --- a/src/core/tools/askFollowupQuestionTool.ts +++ b/src/core/tools/askFollowupQuestionTool.ts @@ -13,6 +13,7 @@ export async function askFollowupQuestionTool( ) { const question: string | undefined = block.params.question const follow_up: string | undefined = block.params.follow_up + try { if (block.partial) { await cline.ask("followup", removeClosingTag("question", question), block.partial).catch(() => {}) @@ -20,13 +21,12 @@ export async function askFollowupQuestionTool( } else { if (!question) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "ask_followup_question", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("ask_followup_question", "question")) return } - type Suggest = { - answer: string - } + type Suggest = { answer: string } let follow_up_json = { question, @@ -39,11 +39,10 @@ export async function askFollowupQuestionTool( } try { - parsedSuggest = parseXml(follow_up, ["suggest"]) as { - suggest: Suggest[] | Suggest - } + parsedSuggest = parseXml(follow_up, ["suggest"]) as { suggest: Suggest[] | Suggest } } catch (error) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "ask_followup_question", success: false }) await cline.say("error", `Failed to parse operations: ${error.message}`) pushToolResult(formatResponse.toolError("Invalid operations xml format")) return @@ -57,10 +56,11 @@ export async function askFollowupQuestionTool( } cline.consecutiveMistakeCount = 0 - const { text, images } = await cline.ask("followup", JSON.stringify(follow_up_json), false) await cline.say("user_feedback", text ?? "", images) pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) + cline.recordToolUsage({ toolName: "ask_followup_question" }) + return } } catch (error) { diff --git a/src/core/tools/attemptCompletionTool.ts b/src/core/tools/attemptCompletionTool.ts index 891673969e..ac2051cf9c 100644 --- a/src/core/tools/attemptCompletionTool.ts +++ b/src/core/tools/attemptCompletionTool.ts @@ -26,8 +26,10 @@ export async function attemptCompletionTool( ) { const result: string | undefined = block.params.result const command: string | undefined = block.params.command + try { const lastMessage = cline.clineMessages.at(-1) + if (block.partial) { if (command) { // the attempt_completion text is done, now we're getting command @@ -43,7 +45,7 @@ export async function attemptCompletionTool( await cline.say("completion_result", removeClosingTag("result", result), undefined, false) telemetryService.captureTaskCompleted(cline.taskId) - cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage()) + cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.getToolUsage()) await cline.ask("command", removeClosingTag("command", command), block.partial).catch(() => {}) } @@ -55,6 +57,7 @@ export async function attemptCompletionTool( } else { if (!result) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "attempt_completion", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("attempt_completion", "result")) return } @@ -68,7 +71,7 @@ export async function attemptCompletionTool( // Haven't sent a command message yet so first send completion_result then command. await cline.say("completion_result", result, undefined, false) telemetryService.captureTaskCompleted(cline.taskId) - cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage()) + cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.getToolUsage()) } // Complete command message. @@ -91,7 +94,7 @@ export async function attemptCompletionTool( } else { await cline.say("completion_result", result, undefined, false) telemetryService.captureTaskCompleted(cline.taskId) - cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage()) + cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage(), cline.getToolUsage()) } if (cline.parentTask) { @@ -136,13 +139,10 @@ export async function attemptCompletionTool( }) toolResults.push(...formatResponse.imageBlocks(images)) - - cline.userMessageContent.push({ - type: "text", - text: `${toolDescription()} Result:`, - }) - + cline.userMessageContent.push({ type: "text", text: `${toolDescription()} Result:` }) cline.userMessageContent.push(...toolResults) + cline.recordToolUsage({ toolName: "attempt_completion" }) + return } } catch (error) { diff --git a/src/core/tools/browserActionTool.ts b/src/core/tools/browserActionTool.ts index c3f02821c1..bdc15b9c41 100644 --- a/src/core/tools/browserActionTool.ts +++ b/src/core/tools/browserActionTool.ts @@ -21,14 +21,17 @@ export async function browserActionTool( const coordinate: string | undefined = block.params.coordinate const text: string | undefined = block.params.text const size: string | undefined = block.params.size + if (!action || !browserActions.includes(action)) { // checking for action to ensure it is complete and valid if (!block.partial) { // if the block is complete and we don't have a valid action cline is a mistake cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "browser_action", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "action")) await cline.browserSession.closeBrowser() } + return } @@ -52,51 +55,63 @@ export async function browserActionTool( } else { // Initialize with empty object to avoid "used before assigned" errors let browserActionResult: BrowserActionResult = {} + if (action === "launch") { if (!url) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "browser_action", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "url")) await cline.browserSession.closeBrowser() return } + cline.consecutiveMistakeCount = 0 const didApprove = await askApproval("browser_action_launch", url) + if (!didApprove) { return } - // NOTE: it's okay that we call cline message since the partial inspect_site is finished streaming. The only scenario we have to avoid is sending messages WHILE a partial message exists at the end of the messages array. For example the api_req_finished message would interfere with the partial message, so we needed to remove that. - // await cline.say("inspect_site_result", "") // no result, starts the loading spinner waiting for result - await cline.say("browser_action_result", "") // starts loading spinner - + // NOTE: It's okay that we call cline message since the partial inspect_site is finished streaming. + // The only scenario we have to avoid is sending messages WHILE a partial message exists at the end of the messages array. + // For example the api_req_finished message would interfere with the partial message, so we needed to remove that. + // await cline.say("inspect_site_result", "") // No result, starts the loading spinner waiting for result + await cline.say("browser_action_result", "") // Starts loading spinner await cline.browserSession.launchBrowser() browserActionResult = await cline.browserSession.navigateToUrl(url) } else { if (action === "click" || action === "hover") { if (!coordinate) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "browser_action", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "coordinate")) await cline.browserSession.closeBrowser() return // can't be within an inner switch } } + if (action === "type") { if (!text) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "browser_action", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "text")) await cline.browserSession.closeBrowser() return } } + if (action === "resize") { if (!size) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "browser_action", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "size")) await cline.browserSession.closeBrowser() return } } + cline.consecutiveMistakeCount = 0 + await cline.say( "browser_action", JSON.stringify({ @@ -107,6 +122,7 @@ export async function browserActionTool( undefined, false, ) + switch (action) { case "click": browserActionResult = await cline.browserSession.click(coordinate!) @@ -141,6 +157,7 @@ export async function browserActionTool( case "scroll_up": case "resize": await cline.say("browser_action_result", JSON.stringify(browserActionResult)) + pushToolResult( formatResponse.toolResult( `The browser action has been executed. The console logs and screenshot have been captured for your analysis.\n\nConsole logs:\n${ @@ -149,6 +166,7 @@ export async function browserActionTool( browserActionResult?.screenshot ? [browserActionResult.screenshot] : [], ), ) + break case "close": pushToolResult( @@ -156,8 +174,12 @@ export async function browserActionTool( `The browser has been closed. You may now proceed to using other tools.`, ), ) + break } + + cline.recordToolUsage({ toolName: "browser_action" }) + return } } catch (error) { diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts index 8c54200bd7..592ab25787 100644 --- a/src/core/tools/executeCommandTool.ts +++ b/src/core/tools/executeCommandTool.ts @@ -13,6 +13,7 @@ export async function executeCommandTool( ) { let command: string | undefined = block.params.command const customCwd: string | undefined = block.params.cwd + try { if (block.partial) { await cline.ask("command", removeClosingTag("command", command), block.partial).catch(() => {}) @@ -20,6 +21,7 @@ export async function executeCommandTool( } else { if (!command) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "execute_command", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("execute_command", "command")) return } @@ -28,7 +30,6 @@ export async function executeCommandTool( if (ignoredFileAttemptedToAccess) { await cline.say("rooignore_error", ignoredFileAttemptedToAccess) pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(ignoredFileAttemptedToAccess))) - return } @@ -38,14 +39,20 @@ export async function executeCommandTool( cline.consecutiveMistakeCount = 0 const didApprove = await askApproval("command", command) + if (!didApprove) { return } + const [userRejected, result] = await cline.executeCommandTool(command, customCwd) + if (userRejected) { cline.didRejectTool = true } + pushToolResult(result) + cline.recordToolUsage({ toolName: "execute_command" }) + return } } catch (error) { diff --git a/src/core/tools/fetchInstructionsTool.ts b/src/core/tools/fetchInstructionsTool.ts index eaa27737e9..5bdefdd316 100644 --- a/src/core/tools/fetchInstructionsTool.ts +++ b/src/core/tools/fetchInstructionsTool.ts @@ -12,50 +12,51 @@ export async function fetchInstructionsTool( pushToolResult: PushToolResult, ) { const task: string | undefined = block.params.task - const sharedMessageProps: ClineSayTool = { - tool: "fetchInstructions", - content: task, - } + const sharedMessageProps: ClineSayTool = { tool: "fetchInstructions", content: task } + try { if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: undefined, - } satisfies ClineSayTool) + const partialMessage = JSON.stringify({ ...sharedMessageProps, content: undefined } satisfies ClineSayTool) await cline.ask("tool", partialMessage, block.partial).catch(() => {}) return } else { if (!task) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "fetch_instructions", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("fetch_instructions", "task")) return } cline.consecutiveMistakeCount = 0 - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: task, - } satisfies ClineSayTool) + const completeMessage = JSON.stringify({ ...sharedMessageProps, content: task } satisfies ClineSayTool) const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { return } - // now fetch the content and provide it to the agent. + // Bow fetch the content and provide it to the agent. const provider = cline.providerRef.deref() const mcpHub = provider?.getMcpHub() + if (!mcpHub) { throw new Error("MCP hub not available") } + const diffStrategy = cline.diffStrategy const context = provider?.context const content = await fetchInstructions(task, { mcpHub, diffStrategy, context }) + if (!content) { pushToolResult(formatResponse.toolError(`Invalid instructions request: ${task}`)) return } + pushToolResult(content) + cline.recordToolUsage({ toolName: "fetch_instructions" }) + + return } } catch (error) { await handleError("fetch instructions", error) diff --git a/src/core/tools/insertContentTool.ts b/src/core/tools/insertContentTool.ts index f05407f502..e55155aeab 100644 --- a/src/core/tools/insertContentTool.ts +++ b/src/core/tools/insertContentTool.ts @@ -37,12 +37,14 @@ export async function insertContentTool( // Validate required parameters if (!relPath) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "insert_content", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("insert_content", "path")) return } if (!operations) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "insert_content", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("insert_content", "operations")) return } @@ -52,6 +54,7 @@ export async function insertContentTool( if (!fileExists) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "insert_content", success: false }) const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` await cline.say("error", formattedError) pushToolResult(formattedError) @@ -70,6 +73,7 @@ export async function insertContentTool( } } catch (error) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "insert_content", success: false }) await cline.say("error", `Failed to parse operations JSON: ${error.message}`) pushToolResult(formatResponse.toolError("Invalid operations JSON format")) return @@ -112,10 +116,7 @@ export async function insertContentTool( await cline.diffViewProvider.update(updatedContent, true) - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - diff, - } satisfies ClineSayTool) + const completeMessage = JSON.stringify({ ...sharedMessageProps, diff } satisfies ClineSayTool) const didApprove = await cline .ask("tool", completeMessage, false) @@ -133,6 +134,7 @@ export async function insertContentTool( if (relPath) { await cline.getFileContextTracker().trackFileContext(relPath, "roo_edited" as RecordSource) } + cline.didEditFile = true if (!userEdits) { @@ -149,6 +151,7 @@ export async function insertContentTool( console.debug("[DEBUG] User made edits, sending feedback diff:", userFeedbackDiff) await cline.say("user_feedback_diff", userFeedbackDiff) + pushToolResult( `The user made the following updates to your content:\n\n${userEdits}\n\n` + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file:\n\n` + @@ -159,6 +162,8 @@ export async function insertContentTool( `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + `${newProblemsMessage}`, ) + + cline.recordToolUsage({ toolName: "insert_content" }) await cline.diffViewProvider.reset() } catch (error) { handleError("insert content", error) diff --git a/src/core/tools/listCodeDefinitionNamesTool.ts b/src/core/tools/listCodeDefinitionNamesTool.ts index 8487367e2b..7e4fad5bf8 100644 --- a/src/core/tools/listCodeDefinitionNamesTool.ts +++ b/src/core/tools/listCodeDefinitionNamesTool.ts @@ -17,29 +17,33 @@ export async function listCodeDefinitionNamesTool( removeClosingTag: RemoveClosingTag, ) { const relPath: string | undefined = block.params.path + const sharedMessageProps: ClineSayTool = { tool: "listCodeDefinitionNames", path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), } + try { if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: "", - } satisfies ClineSayTool) + const partialMessage = JSON.stringify({ ...sharedMessageProps, content: "" } satisfies ClineSayTool) await cline.ask("tool", partialMessage, block.partial).catch(() => {}) return } else { if (!relPath) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "list_code_definition_names", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("list_code_definition_names", "path")) return } + cline.consecutiveMistakeCount = 0 + const absolutePath = path.resolve(cline.cwd, relPath) let result: string + try { const stats = await fs.stat(absolutePath) + if (stats.isFile()) { const fileResult = await parseSourceCodeDefinitionsForFile(absolutePath, cline.rooIgnoreController) result = fileResult ?? "No source code definitions found in cline file." @@ -51,18 +55,20 @@ export async function listCodeDefinitionNamesTool( } catch { result = `${absolutePath}: does not exist or cannot be accessed.` } - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: result, - } satisfies ClineSayTool) + + const completeMessage = JSON.stringify({ ...sharedMessageProps, content: result } satisfies ClineSayTool) const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { return } + if (relPath) { await cline.getFileContextTracker().trackFileContext(relPath, "read_tool" as RecordSource) } + pushToolResult(result) + cline.recordToolUsage({ toolName: "list_code_definition_names" }) return } } catch (error) { diff --git a/src/core/tools/listFilesTool.ts b/src/core/tools/listFilesTool.ts index a010191f75..b9e1592ec0 100644 --- a/src/core/tools/listFilesTool.ts +++ b/src/core/tools/listFilesTool.ts @@ -21,6 +21,7 @@ import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } f * conversation. * @param removeClosingTag - A function that removes a closing tag from a string. */ + export async function listFilesTool( cline: Cline, block: ToolUse, @@ -32,28 +33,31 @@ export async function listFilesTool( const relDirPath: string | undefined = block.params.path const recursiveRaw: string | undefined = block.params.recursive const recursive = recursiveRaw?.toLowerCase() === "true" + const sharedMessageProps: ClineSayTool = { tool: !recursive ? "listFilesTopLevel" : "listFilesRecursive", path: getReadablePath(cline.cwd, removeClosingTag("path", relDirPath)), } + try { if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: "", - } satisfies ClineSayTool) + const partialMessage = JSON.stringify({ ...sharedMessageProps, content: "" } satisfies ClineSayTool) await cline.ask("tool", partialMessage, block.partial).catch(() => {}) return } else { if (!relDirPath) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "list_files", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("list_files", "path")) return } + cline.consecutiveMistakeCount = 0 + const absolutePath = path.resolve(cline.cwd, relDirPath) const [files, didHitLimit] = await listFiles(absolutePath, recursive, 200) const { showRooIgnoredFiles = true } = (await cline.providerRef.deref()?.getState()) ?? {} + const result = formatResponse.formatFilesList( absolutePath, files, @@ -61,15 +65,16 @@ export async function listFilesTool( cline.rooIgnoreController, showRooIgnoredFiles, ) - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: result, - } satisfies ClineSayTool) + + const completeMessage = JSON.stringify({ ...sharedMessageProps, content: result } satisfies ClineSayTool) const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { return } + pushToolResult(result) + cline.recordToolUsage({ toolName: "list_files" }) } } catch (error) { await handleError("listing files", error) diff --git a/src/core/tools/newTaskTool.ts b/src/core/tools/newTaskTool.ts index d6c94dd838..e299f09737 100644 --- a/src/core/tools/newTaskTool.ts +++ b/src/core/tools/newTaskTool.ts @@ -15,6 +15,7 @@ export async function newTaskTool( ) { const mode: string | undefined = block.params.mode const message: string | undefined = block.params.message + try { if (block.partial) { const partialMessage = JSON.stringify({ @@ -22,23 +23,29 @@ export async function newTaskTool( mode: removeClosingTag("mode", mode), message: removeClosingTag("message", message), }) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) return } else { if (!mode) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "new_task", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("new_task", "mode")) return } + if (!message) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "new_task", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("new_task", "message")) return } + cline.consecutiveMistakeCount = 0 // Verify the mode exists const targetMode = getModeBySlug(mode, (await cline.providerRef.deref()?.getState())?.customModes) + if (!targetMode) { pushToolResult(formatResponse.toolError(`Invalid mode: ${mode}`)) return @@ -49,6 +56,7 @@ export async function newTaskTool( mode: targetMode.name, content: message, }) + const didApprove = await askApproval("tool", toolMessage) if (!didApprove) { @@ -74,6 +82,7 @@ export async function newTaskTool( cline.emit("taskSpawned", newCline.taskId) pushToolResult(`Successfully created new task in ${targetMode.name} mode with message: ${message}`) + cline.recordToolUsage({ toolName: "new_task" }) // Set the isPaused flag to true so the parent // task can wait for the sub-task to finish. diff --git a/src/core/tools/readFileTool.ts b/src/core/tools/readFileTool.ts index 022ec4321c..ca84c0876e 100644 --- a/src/core/tools/readFileTool.ts +++ b/src/core/tools/readFileTool.ts @@ -37,15 +37,13 @@ export async function readFileTool( } try { if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: undefined, - } satisfies ClineSayTool) + const partialMessage = JSON.stringify({ ...sharedMessageProps, content: undefined } satisfies ClineSayTool) await cline.ask("tool", partialMessage, block.partial).catch(() => {}) return } else { if (!relPath) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "read_file", success: false }) const errorMsg = await cline.sayAndCreateMissingParamError("read_file", "path") pushToolResult(`${errorMsg}`) return @@ -67,13 +65,16 @@ export async function readFileTool( // Parse start_line if provided if (startLineStr) { startLine = parseInt(startLineStr) + if (isNaN(startLine)) { // Invalid start_line cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "read_file", success: false }) await cline.say("error", `Failed to parse start_line: ${startLineStr}`) pushToolResult(`${relPath}Invalid start_line value`) return } + startLine -= 1 // Convert to 0-based index } @@ -84,6 +85,7 @@ export async function readFileTool( if (isNaN(endLine)) { // Invalid end_line cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "read_file", success: false }) await cline.say("error", `Failed to parse end_line: ${endLineStr}`) pushToolResult(`${relPath}Invalid end_line value`) return @@ -94,6 +96,7 @@ export async function readFileTool( } const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) + if (!accessAllowed) { await cline.say("rooignore_error", relPath) const errorMsg = formatResponse.rooIgnoreError(relPath) @@ -103,6 +106,7 @@ export async function readFileTool( // Create line snippet description for approval message let lineSnippet = "" + if (isFullRead) { // No snippet for full read } else if (startLine !== undefined && endLine !== undefined) { @@ -127,12 +131,14 @@ export async function readFileTool( } satisfies ClineSayTool) const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { return } // Count total lines in the file let totalLines = 0 + try { totalLines = await countFileLines(absolutePath) } catch (error) { @@ -163,6 +169,7 @@ export async function readFileTool( content = res[0].length > 0 ? addLineNumbers(res[0]) : "" const result = res[1] + if (result) { sourceCodeDef = `${result}` } @@ -211,9 +218,11 @@ export async function readFileTool( else { // For non-range reads, always show line range let lines = totalLines + if (maxReadFileLine >= 0 && totalLines > maxReadFileLine) { lines = maxReadFileLine } + const lineRangeAttr = ` lines="1-${lines}"` // Maintain exact format expected by tests @@ -228,6 +237,7 @@ export async function readFileTool( // Format the result into the required XML structure const xmlResult = `${relPath}\n${contentTag}${xmlInfo}` pushToolResult(xmlResult) + cline.recordToolUsage({ toolName: "read_file" }) } } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error) diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts index 7b88405e37..7443974144 100644 --- a/src/core/tools/searchAndReplaceTool.ts +++ b/src/core/tools/searchAndReplaceTool.ts @@ -32,16 +32,20 @@ export async function searchAndReplaceTool( path: removeClosingTag("path", relPath), operations: removeClosingTag("operations", operations), }) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) return } else { if (!relPath) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "search_and_replace", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("search_and_replace", "path")) return } + if (!operations) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "search_and_replace", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("search_and_replace", "operations")) return } @@ -51,6 +55,7 @@ export async function searchAndReplaceTool( if (!fileExists) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "search_and_replace", success: false }) const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` await cline.say("error", formattedError) pushToolResult(formattedError) @@ -69,11 +74,13 @@ export async function searchAndReplaceTool( try { parsedOperations = JSON.parse(operations) + if (!Array.isArray(parsedOperations)) { throw new Error("Operations must be an array") } } catch (error) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "search_and_replace", success: false }) await cline.say("error", `Failed to parse operations JSON: ${error.message}`) pushToolResult(formatResponse.toolError("Invalid operations JSON format")) return @@ -132,18 +139,16 @@ export async function searchAndReplaceTool( await cline.diffViewProvider.update(newContent, true) cline.diffViewProvider.scrollToFirstDiff() - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - diff: diff, - } satisfies ClineSayTool) - + const completeMessage = JSON.stringify({ ...sharedMessageProps, diff: diff } satisfies ClineSayTool) const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { await cline.diffViewProvider.revertChanges() // cline likely handles closing the diff view return } const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() + if (relPath) { await cline.getFileContextTracker().trackFileContext(relPath, "roo_edited" as RecordSource) } @@ -158,6 +163,7 @@ export async function searchAndReplaceTool( diff: userEdits, } satisfies ClineSayTool), ) + pushToolResult( `The user made the following updates to your content:\n\n${userEdits}\n\n` + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + @@ -171,7 +177,10 @@ export async function searchAndReplaceTool( } else { pushToolResult(`Changes successfully applied to ${relPath.toPosix()}:\n\n${newProblemsMessage}`) } + + cline.recordToolUsage({ toolName: "search_and_replace" }) await cline.diffViewProvider.reset() + return } } catch (error) { diff --git a/src/core/tools/searchFilesTool.ts b/src/core/tools/searchFilesTool.ts index 3cf651a0db..3c1b09b6a4 100644 --- a/src/core/tools/searchFilesTool.ts +++ b/src/core/tools/searchFilesTool.ts @@ -17,33 +17,38 @@ export async function searchFilesTool( const relDirPath: string | undefined = block.params.path const regex: string | undefined = block.params.regex const filePattern: string | undefined = block.params.file_pattern + const sharedMessageProps: ClineSayTool = { tool: "searchFiles", path: getReadablePath(cline.cwd, removeClosingTag("path", relDirPath)), regex: removeClosingTag("regex", regex), filePattern: removeClosingTag("file_pattern", filePattern), } + try { if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: "", - } satisfies ClineSayTool) + const partialMessage = JSON.stringify({ ...sharedMessageProps, content: "" } satisfies ClineSayTool) await cline.ask("tool", partialMessage, block.partial).catch(() => {}) return } else { if (!relDirPath) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "search_files", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("search_files", "path")) return } + if (!regex) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "search_files", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("search_files", "regex")) return } + cline.consecutiveMistakeCount = 0 + const absolutePath = path.resolve(cline.cwd, relDirPath) + const results = await regexSearchFiles( cline.cwd, absolutePath, @@ -51,15 +56,17 @@ export async function searchFilesTool( filePattern, cline.rooIgnoreController, ) - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: results, - } satisfies ClineSayTool) + + const completeMessage = JSON.stringify({ ...sharedMessageProps, content: results } satisfies ClineSayTool) const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { return } + pushToolResult(results) + cline.recordToolUsage({ toolName: "search_files" }) + return } } catch (error) { diff --git a/src/core/tools/switchModeTool.ts b/src/core/tools/switchModeTool.ts index 595eb04290..0d0da1de39 100644 --- a/src/core/tools/switchModeTool.ts +++ b/src/core/tools/switchModeTool.ts @@ -15,6 +15,7 @@ export async function switchModeTool( ) { const mode_slug: string | undefined = block.params.mode_slug const reason: string | undefined = block.params.reason + try { if (block.partial) { const partialMessage = JSON.stringify({ @@ -22,49 +23,57 @@ export async function switchModeTool( mode: removeClosingTag("mode_slug", mode_slug), reason: removeClosingTag("reason", reason), }) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) return } else { if (!mode_slug) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "switch_mode", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("switch_mode", "mode_slug")) return } + cline.consecutiveMistakeCount = 0 // Verify the mode exists const targetMode = getModeBySlug(mode_slug, (await cline.providerRef.deref()?.getState())?.customModes) + if (!targetMode) { + cline.recordToolUsage({ toolName: "switch_mode", success: false }) pushToolResult(formatResponse.toolError(`Invalid mode: ${mode_slug}`)) return } // Check if already in requested mode const currentMode = (await cline.providerRef.deref()?.getState())?.mode ?? defaultModeSlug + if (currentMode === mode_slug) { + cline.recordToolUsage({ toolName: "switch_mode", success: false }) pushToolResult(`Already in ${targetMode.name} mode.`) return } - const completeMessage = JSON.stringify({ - tool: "switchMode", - mode: mode_slug, - reason, - }) - + const completeMessage = JSON.stringify({ tool: "switchMode", mode: mode_slug, reason }) const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { return } // Switch the mode using shared handler await cline.providerRef.deref()?.handleModeSwitch(mode_slug) + pushToolResult( `Successfully switched from ${getModeBySlug(currentMode)?.name ?? currentMode} mode to ${ targetMode.name } mode${reason ? ` because: ${reason}` : ""}.`, ) - await delay(500) // delay to allow mode change to take effect before next tool is executed + + cline.recordToolUsage({ toolName: "switch_mode" }) + + await delay(500) // Delay to allow mode change to take effect before next tool is executed + return } } catch (error) { diff --git a/src/core/tools/useMcpToolTool.ts b/src/core/tools/useMcpToolTool.ts index f89a2938b7..04a400371c 100644 --- a/src/core/tools/useMcpToolTool.ts +++ b/src/core/tools/useMcpToolTool.ts @@ -22,51 +22,60 @@ export async function useMcpToolTool( toolName: removeClosingTag("tool_name", tool_name), arguments: removeClosingTag("arguments", mcp_arguments), } satisfies ClineAskUseMcpServer) + await cline.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) return } else { if (!server_name) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "switch_mode", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "server_name")) return } + if (!tool_name) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "use_mcp_tool", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "tool_name")) return } - // arguments are optional, but if they are provided they must be valid JSON - // if (!mcp_arguments) { - // cline.consecutiveMistakeCount++ - // pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "arguments")) - // return - // } + let parsedArguments: Record | undefined + if (mcp_arguments) { try { parsedArguments = JSON.parse(mcp_arguments) } catch (error) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "use_mcp_tool", success: false }) await cline.say("error", `Roo tried to use ${tool_name} with an invalid JSON argument. Retrying...`) + pushToolResult( formatResponse.toolError(formatResponse.invalidMcpToolArgumentError(server_name, tool_name)), ) + return } } + cline.consecutiveMistakeCount = 0 + const completeMessage = JSON.stringify({ type: "use_mcp_tool", serverName: server_name, toolName: tool_name, arguments: mcp_arguments, } satisfies ClineAskUseMcpServer) + const didApprove = await askApproval("use_mcp_server", completeMessage) + if (!didApprove) { return } - // now execute the tool + + // Now execute the tool await cline.say("mcp_server_request_started") // same as browser_action_result + const toolResult = await cline.providerRef .deref() ?.getMcpHub() @@ -88,8 +97,11 @@ export async function useMcpToolTool( }) .filter(Boolean) .join("\n\n") || "(No response)" + await cline.say("mcp_server_response", toolResultPretty) pushToolResult(formatResponse.toolResult(toolResultPretty)) + cline.recordToolUsage({ toolName: "use_mcp_tool" }) + return } } catch (error) { diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts index 89dd010254..cf2ced16b5 100644 --- a/src/core/tools/writeToFileTool.ts +++ b/src/core/tools/writeToFileTool.ts @@ -25,6 +25,7 @@ export async function writeToFileTool( const relPath: string | undefined = block.params.path let newContent: string | undefined = block.params.content let predictedLineCount: number | undefined = parseInt(block.params.line_count ?? "0") + if (!relPath || !newContent) { // checking for newContent ensure relPath is complete // wait so we can determine if it's a new file or editing an existing file @@ -32,15 +33,16 @@ export async function writeToFileTool( } const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) + if (!accessAllowed) { await cline.say("rooignore_error", relPath) pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) - return } // Check if file exists using cached map or fs.access let fileExists: boolean + if (cline.diffViewProvider.editType !== undefined) { fileExists = cline.diffViewProvider.editType === "modify" } else { @@ -54,6 +56,7 @@ export async function writeToFileTool( // cline handles cases where it includes language specifiers like ```python ```js newContent = newContent.split("\n").slice(1).join("\n").trim() } + if (newContent.endsWith("```")) { newContent = newContent.split("\n").slice(0, -1).join("\n").trim() } @@ -71,41 +74,51 @@ export async function writeToFileTool( path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), isOutsideWorkspace, } + try { if (block.partial) { // update gui message const partialMessage = JSON.stringify(sharedMessageProps) await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + // update editor if (!cline.diffViewProvider.isEditing) { // open the editor and prepare to stream content in await cline.diffViewProvider.open(relPath) } + // editor is open, stream content in await cline.diffViewProvider.update( everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, false, ) + return } else { if (!relPath) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "write_to_file", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("write_to_file", "path")) await cline.diffViewProvider.reset() return } + if (!newContent) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "write_to_file", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("write_to_file", "content")) await cline.diffViewProvider.reset() return } + if (!predictedLineCount) { cline.consecutiveMistakeCount++ + cline.recordToolUsage({ toolName: "write_to_file", success: false }) pushToolResult(await cline.sayAndCreateMissingParamError("write_to_file", "line_count")) await cline.diffViewProvider.reset() return } + cline.consecutiveMistakeCount = 0 // if isEditingFile false, that means we have the full contents of the file already. @@ -117,10 +130,12 @@ export async function writeToFileTool( await cline.ask("tool", partialMessage, true).catch(() => {}) // sending true for partial even though it's not a partial, cline shows the edit row before the content is streamed into the editor await cline.diffViewProvider.open(relPath) } + await cline.diffViewProvider.update( everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, true, ) + await delay(300) // wait for diff view to update cline.diffViewProvider.scrollToFirstDiff() @@ -128,6 +143,7 @@ export async function writeToFileTool( if (detectCodeOmission(cline.diffViewProvider.originalContent || "", newContent, predictedLineCount)) { if (cline.diffStrategy) { await cline.diffViewProvider.revertChanges() + pushToolResult( formatResponse.toolError( `Content appears to be truncated (file has ${ @@ -161,18 +177,23 @@ export async function writeToFileTool( ? formatResponse.createPrettyPatch(relPath, cline.diffViewProvider.originalContent, newContent) : undefined, } satisfies ClineSayTool) + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { await cline.diffViewProvider.revertChanges() return } + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() // Track file edit operation if (relPath) { await cline.getFileContextTracker().trackFileContext(relPath, "roo_edited" as RecordSource) } + cline.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request + if (userEdits) { await cline.say( "user_feedback_diff", @@ -182,6 +203,7 @@ export async function writeToFileTool( diff: userEdits, } satisfies ClineSayTool), ) + pushToolResult( `The user made the following updates to your content:\n\n${userEdits}\n\n` + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + @@ -197,7 +219,10 @@ export async function writeToFileTool( } else { pushToolResult(`The content was successfully saved to ${relPath.toPosix()}.${newProblemsMessage}`) } + + cline.recordToolUsage({ toolName: "write_to_file" }) await cline.diffViewProvider.reset() + return } } catch (error) { diff --git a/src/exports/api.ts b/src/exports/api.ts index 2da90a84a5..47464ff00f 100644 --- a/src/exports/api.ts +++ b/src/exports/api.ts @@ -296,12 +296,12 @@ export class API extends EventEmitter implements RooCodeAPI { this.taskMap.delete(cline.taskId) }) - cline.on("taskCompleted", async (_, usage) => { - this.emit(RooCodeEventName.TaskCompleted, cline.taskId, usage) + cline.on("taskCompleted", async (_, tokenUsage, toolUsage) => { + this.emit(RooCodeEventName.TaskCompleted, cline.taskId, tokenUsage, toolUsage) this.taskMap.delete(cline.taskId) await this.fileLog( - `[${new Date().toISOString()}] taskCompleted -> ${cline.taskId} | ${JSON.stringify(usage, null, 2)}\n`, + `[${new Date().toISOString()}] taskCompleted -> ${cline.taskId} | ${JSON.stringify(tokenUsage, null, 2)} | ${JSON.stringify(toolUsage, null, 2)}\n`, ) }) diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 70ae8b08b4..422297ddd3 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -520,6 +520,12 @@ type RooCodeEvents = { totalCost: number contextTokens: number }, + { + [x: string]: { + attempts: number + failures: number + } + }, ] taskTokenUsageUpdated: [ string, diff --git a/src/exports/types.ts b/src/exports/types.ts index 878f0b95b2..df38b929ed 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -529,6 +529,12 @@ type RooCodeEvents = { totalCost: number contextTokens: number }, + { + [x: string]: { + attempts: number + failures: number + } + }, ] taskTokenUsageUpdated: [ string, diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 48f5b2989e..db9feb54b4 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -817,6 +817,45 @@ export const tokenUsageSchema = z.object({ export type TokenUsage = z.infer +export const toolNames = [ + "execute_command", + "read_file", + "write_to_file", + "append_to_file", + "apply_diff", + "insert_content", + "search_and_replace", + "search_files", + "list_files", + "list_code_definition_names", + "browser_action", + "use_mcp_tool", + "access_mcp_resource", + "ask_followup_question", + "attempt_completion", + "switch_mode", + "new_task", + "fetch_instructions", +] as const + +export const toolNamesSchema = z.enum(toolNames) + +export type ToolName = z.infer + +/** + * ToolUsage + */ + +export const toolUsageSchema = z.record( + toolNamesSchema, + z.object({ + attempts: z.number(), + failures: z.number(), + }), +) + +export type ToolUsage = z.infer + /** * RooCodeEvent */ @@ -851,7 +890,7 @@ export const rooCodeEventsSchema = z.object({ [RooCodeEventName.TaskAskResponded]: z.tuple([z.string()]), [RooCodeEventName.TaskAborted]: z.tuple([z.string()]), [RooCodeEventName.TaskSpawned]: z.tuple([z.string(), z.string()]), - [RooCodeEventName.TaskCompleted]: z.tuple([z.string(), tokenUsageSchema]), + [RooCodeEventName.TaskCompleted]: z.tuple([z.string(), tokenUsageSchema, toolUsageSchema]), [RooCodeEventName.TaskTokenUsageUpdated]: z.tuple([z.string(), tokenUsageSchema]), }) diff --git a/src/shared/tools.ts b/src/shared/tools.ts index 7dd12893a3..858bf591d3 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -1,6 +1,6 @@ import { Anthropic } from "@anthropic-ai/sdk" -import { ClineAsk, ToolProgressStatus, ToolGroup } from "../schemas" +import { ClineAsk, ToolProgressStatus, ToolGroup, ToolName } from "../schemas" export type ToolResponse = string | Array @@ -26,29 +26,6 @@ export interface TextContent { partial: boolean } -export const toolNames = [ - "execute_command", - "read_file", - "write_to_file", - "append_to_file", - "apply_diff", - "insert_content", - "search_and_replace", - "search_files", - "list_files", - "list_code_definition_names", - "browser_action", - "use_mcp_tool", - "access_mcp_resource", - "ask_followup_question", - "attempt_completion", - "switch_mode", - "new_task", - "fetch_instructions", -] as const - -export type ToolName = (typeof toolNames)[number] - export const toolParamNames = [ "command", "path", @@ -167,14 +144,6 @@ export interface NewTaskToolUse extends ToolUse { params: Partial, "mode" | "message">> } -export type ToolUsage = Record< - ToolName, - { - attempts: number - failures: number - } -> - // Define tool group configuration export type ToolGroupConfig = { tools: readonly string[] From 06882f56872729bc5cb09862e2b3fb548e491f04 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 18 Apr 2025 02:23:55 -0400 Subject: [PATCH 07/15] Don't break if an end_line is passed into a diff (#2743) --- .../__tests__/multi-search-replace.test.ts | 19 ++++++++++++++++++ .../diff/strategies/multi-search-replace.ts | 20 +++++++++++-------- 2 files changed, 31 insertions(+), 8 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 63111ba9aa..365a36bc7d 100644 --- a/src/core/diff/strategies/__tests__/multi-search-replace.test.ts +++ b/src/core/diff/strategies/__tests__/multi-search-replace.test.ts @@ -159,6 +159,25 @@ function helloWorld() { } }) + it("should replace matching content when end_line is passed in", async () => { + const originalContent = 'function hello() {\n console.log("hello")\n}\n' + const diffContent = `test.ts +<<<<<<< SEARCH +:start_line:1 +:end_line:1 +------- +function hello() { +======= +function helloWorld() { +>>>>>>> REPLACE` + + const result = await strategy.applyDiff(originalContent, diffContent) + expect(result.success).toBe(true) + if (result.success) { + expect(result.content).toBe('function helloWorld() {\n console.log("hello")\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 diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index a9d4ba6560..075a768fba 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -186,6 +186,7 @@ Only use a single line of '=======' between search and replacement content, beca .replace(/^\\=======/gm, "=======") .replace(/^\\>>>>>>>/gm, ">>>>>>>") .replace(/^\\-------/gm, "-------") + .replace(/^\\:end_line:/gm, ":end_line:") .replace(/^\\:start_line:/gm, ":start_line:") } @@ -322,25 +323,28 @@ Only use a single line of '=======' between search and replacement content, beca 3. ((?:\:start_line:\s*(\d+)\s*\n))?   Optionally matches a “:start_line:” line. The outer capturing group is group 1 and the inner (\d+) is group 2. - 4. ((?>>>>>> REPLACE)(?=\n|$) + 9. (?:(?<=\n)(?>>>>>> 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( - /(?:^|\n)(?>>>>>> REPLACE)(?=\n|$)/g, + /(?:^|\n)(?>>>>>> REPLACE)(?=\n|$)/g, ), ] @@ -359,8 +363,8 @@ Only use a single line of '=======' between search and replacement content, beca const replacements = matches .map((match) => ({ startLine: Number(match[2] ?? 0), - searchContent: match[4], - replaceContent: match[5], + searchContent: match[6], + replaceContent: match[7], })) .sort((a, b) => a.startLine - b.startLine) From c329b4509f3d1c98baf13ee89102a8df10aa90ff Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 18 Apr 2025 02:29:13 -0400 Subject: [PATCH 08/15] v3.12.4 (#2745) --- .changeset/tall-bottles-sin.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tall-bottles-sin.md diff --git a/.changeset/tall-bottles-sin.md b/.changeset/tall-bottles-sin.md new file mode 100644 index 0000000000..07d46d35a4 --- /dev/null +++ b/.changeset/tall-bottles-sin.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +v3.12.4 From 31656d9b16c3d0d2dd3789cb93f039b37a26b277 Mon Sep 17 00:00:00 2001 From: R00-B0T <110429663+R00-B0T@users.noreply.github.com> Date: Thu, 17 Apr 2025 23:47:41 -0700 Subject: [PATCH 09/15] Changeset version bump (#2716) * changeset version bump * Update CHANGELOG.md * Update package.json * Update package-lock.json --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matt Rubens --- .changeset/tall-bottles-sin.md | 5 ----- .changeset/ten-grapes-punch.md | 5 ----- .changeset/wise-spies-type.md | 5 ----- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 11 insertions(+), 18 deletions(-) delete mode 100644 .changeset/tall-bottles-sin.md delete mode 100644 .changeset/ten-grapes-punch.md delete mode 100644 .changeset/wise-spies-type.md diff --git a/.changeset/tall-bottles-sin.md b/.changeset/tall-bottles-sin.md deleted file mode 100644 index 07d46d35a4..0000000000 --- a/.changeset/tall-bottles-sin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -v3.12.4 diff --git a/.changeset/ten-grapes-punch.md b/.changeset/ten-grapes-punch.md deleted file mode 100644 index d9fef3e018..0000000000 --- a/.changeset/ten-grapes-punch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Add missing translation diff --git a/.changeset/wise-spies-type.md b/.changeset/wise-spies-type.md deleted file mode 100644 index 8c1e83fc18..0000000000 --- a/.changeset/wise-spies-type.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Clean up types related to tools diff --git a/CHANGELOG.md b/CHANGELOG.md index f5dd214ef8..bc344d0b44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Roo Code Changelog +## [3.13.0] - 2025-04-17 + +- UI improvements to task header, chat view, history preview, and welcome view (thanks @sachasayan!) +- Add append_to_file tool for appending content to files (thanks @samhvw8!) +- Add Gemini 2.5 Flash Preview to Gemini and Vertex providers (thanks @nbihan-mediware!) +- Fix image support in Bedrock (thanks @Smartsheet-JB-Brown!) +- Make diff edits more resilient to models passing in incorrect parameters + ## [3.12.3] - 2025-04-17 - Fix character escaping issues in Gemini diff edits diff --git a/package-lock.json b/package-lock.json index 91f5affa09..cb574238fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "roo-cline", - "version": "3.12.3", + "version": "3.13.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roo-cline", - "version": "3.12.3", + "version": "3.13.0", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.37.0", diff --git a/package.json b/package.json index 4804150ffd..fa5387a7c1 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "RooVeterinaryInc", - "version": "3.12.3", + "version": "3.13.0", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", From b3065d2ab3fef48b8ba6d663aba1d3159b140b54 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 18 Apr 2025 03:09:14 -0400 Subject: [PATCH 10/15] Update contributors list (#2715) docs: update contributors list [skip ci] Co-authored-by: mrubens --- README.md | 48 ++++++++++++++++++++--------------------- locales/ca/README.md | 34 ++++++++++++++--------------- locales/de/README.md | 34 ++++++++++++++--------------- locales/es/README.md | 34 ++++++++++++++--------------- locales/fr/README.md | 34 ++++++++++++++--------------- locales/hi/README.md | 34 ++++++++++++++--------------- locales/it/README.md | 34 ++++++++++++++--------------- locales/ja/README.md | 34 ++++++++++++++--------------- locales/ko/README.md | 34 ++++++++++++++--------------- locales/pl/README.md | 34 ++++++++++++++--------------- locales/pt-BR/README.md | 34 ++++++++++++++--------------- locales/tr/README.md | 34 ++++++++++++++--------------- locales/vi/README.md | 34 ++++++++++++++--------------- locales/zh-CN/README.md | 34 ++++++++++++++--------------- locales/zh-TW/README.md | 34 ++++++++++++++--------------- 15 files changed, 262 insertions(+), 262 deletions(-) diff --git a/README.md b/README.md index 9acf339410..71bc66bdf8 100644 --- a/README.md +++ b/README.md @@ -181,30 +181,30 @@ 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
| hannesrudolph
hannesrudolph
| nissa-seru
nissa-seru
| -| jquanton
jquanton
| KJ7LNW
KJ7LNW
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| -| monotykamary
monotykamary
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| feifei325
feifei325
| wkordalski
wkordalski
| cannuri
cannuri
| lloydchang
lloydchang
| -| vigneshsubbiah16
vigneshsubbiah16
| Szpadel
Szpadel
| lupuletic
lupuletic
| qdaxb
qdaxb
| Premshay
Premshay
| psv2522
psv2522
| -| diarmidmackenzie
diarmidmackenzie
| olweraltuve
olweraltuve
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| aheizi
aheizi
| afshawnlotfi
afshawnlotfi
| -| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| kyle-apex
kyle-apex
| emshvac
emshvac
| Lunchb0ne
Lunchb0ne
| arthurauffray
arthurauffray
| -| zhangtony239
zhangtony239
| upamune
upamune
| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| -| dtrugman
dtrugman
| aitoroses
aitoroses
| yt3trees
yt3trees
| franekp
franekp
| yongjer
yongjer
| vincentsong
vincentsong
| -| vagadiya
vagadiya
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| taisukeoe
taisukeoe
| heyseth
heyseth
| ross
ross
| -| philfung
philfung
| nbihan-mediware
nbihan-mediware
| napter
napter
| mdp
mdp
| SplittyDev
SplittyDev
| Chenjiayuan195
Chenjiayuan195
| -| jcbdev
jcbdev
| GitlyHallows
GitlyHallows
| bramburn
bramburn
| benzntech
benzntech
| axkirillov
axkirillov
| anton-otee
anton-otee
| -| shoopapa
shoopapa
| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| -| mecab
mecab
| im47cn
im47cn
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| -| ashktn
ashktn
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| nobu007
nobu007
| -| oprstchn
oprstchn
| philipnext
philipnext
| pokutuna
pokutuna
| refactorthis
refactorthis
| ronyblum
ronyblum
| samir-nimbly
samir-nimbly
| -| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| -| AMHesch
AMHesch
| adamwlarson
adamwlarson
| alarno
alarno
| andreastempsch
andreastempsch
| atlasgong
atlasgong
| Atlogit
Atlogit
| -| bogdan0083
bogdan0083
| chadgauth
chadgauth
| dleen
dleen
| elianiva
elianiva
| dbasclpy
dbasclpy
| snoyiatk
snoyiatk
| -| linegel
linegel
| celestial-vault
celestial-vault
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| shtse8
shtse8
| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| marvijo-code
marvijo-code
| kvokka
kvokka
| Sarke
Sarke
| -| 01Rian
01Rian
| sachasayan
sachasayan
| samsilveira
samsilveira
| maekawataiki
maekawataiki
| tgfjt
tgfjt
| tmsjngx0
tmsjngx0
| -| vladstudio
vladstudio
| | | | | | +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| hannesrudolph
hannesrudolph
| nissa-seru
nissa-seru
| +| jquanton
jquanton
| KJ7LNW
KJ7LNW
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| punkpeye
punkpeye
| +| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| feifei325
feifei325
| cannuri
cannuri
| lloydchang
lloydchang
| vigneshsubbiah16
vigneshsubbiah16
| +| wkordalski
wkordalski
| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| qdaxb
qdaxb
| +| lupuletic
lupuletic
| olweraltuve
olweraltuve
| afshawnlotfi
afshawnlotfi
| aheizi
aheizi
| RaySinner
RaySinner
| PeterDaveHello
PeterDaveHello
| +| emshvac
emshvac
| kyle-apex
kyle-apex
| nbihan-mediware
nbihan-mediware
| pdecat
pdecat
| pugazhendhi-m
pugazhendhi-m
| Lunchb0ne
Lunchb0ne
| +| arthurauffray
arthurauffray
| zhangtony239
zhangtony239
| upamune
upamune
| StevenTCramer
StevenTCramer
| sammcj
sammcj
| p12tic
p12tic
| +| gtaylor
gtaylor
| dtrugman
dtrugman
| aitoroses
aitoroses
| yt3trees
yt3trees
| franekp
franekp
| yongjer
yongjer
| +| vincentsong
vincentsong
| vagadiya
vagadiya
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| taisukeoe
taisukeoe
| heyseth
heyseth
| +| sachasayan
sachasayan
| ross
ross
| philfung
philfung
| napter
napter
| mdp
mdp
| SplittyDev
SplittyDev
| +| Chenjiayuan195
Chenjiayuan195
| jcbdev
jcbdev
| GitlyHallows
GitlyHallows
| bramburn
bramburn
| benzntech
benzntech
| axkirillov
axkirillov
| +| anton-otee
anton-otee
| shoopapa
shoopapa
| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| +| olup
olup
| mecab
mecab
| im47cn
im47cn
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| +| axmo
axmo
| ashktn
ashktn
| amittell
amittell
| AMHesch
AMHesch
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| +| nobu007
nobu007
| oprstchn
oprstchn
| philipnext
philipnext
| pokutuna
pokutuna
| refactorthis
refactorthis
| ronyblum
ronyblum
| +| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| +| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| andreastempsch
andreastempsch
| atlasgong
atlasgong
| Atlogit
Atlogit
| +| bogdan0083
bogdan0083
| chadgauth
chadgauth
| dleen
dleen
| elianiva
elianiva
| dbasclpy
dbasclpy
| snoyiatk
snoyiatk
| +| linegel
linegel
| celestial-vault
celestial-vault
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| +| shtse8
shtse8
| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| marvijo-code
marvijo-code
| kvokka
kvokka
| Sarke
Sarke
| +| 01Rian
01Rian
| samsilveira
samsilveira
| maekawataiki
maekawataiki
| tgfjt
tgfjt
| tmsjngx0
tmsjngx0
| vladstudio
vladstudio
| +| Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | diff --git a/locales/ca/README.md b/locales/ca/README.md index b43c627902..82c36ad8ea 100644 --- a/locales/ca/README.md +++ b/locales/ca/README.md @@ -182,26 +182,26 @@ Gràcies a tots els nostres col·laboradors que han ajudat a millorar Roo Code! |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## Llicència diff --git a/locales/de/README.md b/locales/de/README.md index e621914fb6..3cb474f80f 100644 --- a/locales/de/README.md +++ b/locales/de/README.md @@ -182,26 +182,26 @@ Danke an alle unsere Mitwirkenden, die geholfen haben, Roo Code zu verbessern! |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## Lizenz diff --git a/locales/es/README.md b/locales/es/README.md index 63699cd4a9..5f3d825e05 100644 --- a/locales/es/README.md +++ b/locales/es/README.md @@ -182,26 +182,26 @@ Usamos [changesets](https://github.com/changesets/changesets) para versionar y p |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## Licencia diff --git a/locales/fr/README.md b/locales/fr/README.md index 8c54b1f257..56cd0b4d93 100644 --- a/locales/fr/README.md +++ b/locales/fr/README.md @@ -182,26 +182,26 @@ Merci à tous nos contributeurs qui ont aidé à améliorer Roo Code ! |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## Licence diff --git a/locales/hi/README.md b/locales/hi/README.md index 64c24c391d..6f0512179e 100644 --- a/locales/hi/README.md +++ b/locales/hi/README.md @@ -182,26 +182,26 @@ Roo Code को बेहतर बनाने में मदद करने |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## लाइसेंस diff --git a/locales/it/README.md b/locales/it/README.md index f77b05ba2f..c5f925180d 100644 --- a/locales/it/README.md +++ b/locales/it/README.md @@ -182,26 +182,26 @@ Grazie a tutti i nostri contributori che hanno aiutato a migliorare Roo Code! |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## Licenza diff --git a/locales/ja/README.md b/locales/ja/README.md index 490496bfff..b83c23f91b 100644 --- a/locales/ja/README.md +++ b/locales/ja/README.md @@ -182,26 +182,26 @@ Roo Codeの改善に貢献してくれたすべての貢献者に感謝します |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## ライセンス diff --git a/locales/ko/README.md b/locales/ko/README.md index 9cca1246c7..fde14a4f91 100644 --- a/locales/ko/README.md +++ b/locales/ko/README.md @@ -182,26 +182,26 @@ Roo Code를 더 좋게 만드는 데 도움을 준 모든 기여자에게 감사 |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## 라이선스 diff --git a/locales/pl/README.md b/locales/pl/README.md index ce13b9374b..de7aea7c03 100644 --- a/locales/pl/README.md +++ b/locales/pl/README.md @@ -182,26 +182,26 @@ Dziękujemy wszystkim naszym współtwórcom, którzy pomogli ulepszyć Roo Code |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## Licencja diff --git a/locales/pt-BR/README.md b/locales/pt-BR/README.md index e99d72d9ab..d71df8eff8 100644 --- a/locales/pt-BR/README.md +++ b/locales/pt-BR/README.md @@ -182,26 +182,26 @@ Obrigado a todos os nossos contribuidores que ajudaram a tornar o Roo Code melho |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## Licença diff --git a/locales/tr/README.md b/locales/tr/README.md index cddd257c9d..0ec8114ffc 100644 --- a/locales/tr/README.md +++ b/locales/tr/README.md @@ -182,26 +182,26 @@ Roo Code'u daha iyi hale getirmeye yardımcı olan tüm katkıda bulunanlara te |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## Lisans diff --git a/locales/vi/README.md b/locales/vi/README.md index a6eef078e9..070eaeb563 100644 --- a/locales/vi/README.md +++ b/locales/vi/README.md @@ -182,26 +182,26 @@ Cảm ơn tất cả những người đóng góp đã giúp cải thiện Roo C |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## Giấy Phép diff --git a/locales/zh-CN/README.md b/locales/zh-CN/README.md index 91afd06cb3..a7e716bcc5 100644 --- a/locales/zh-CN/README.md +++ b/locales/zh-CN/README.md @@ -182,26 +182,26 @@ code --install-extension bin/roo-cline-.vsix |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## 许可证 diff --git a/locales/zh-TW/README.md b/locales/zh-TW/README.md index 6395dc0d0b..8336eeb139 100644 --- a/locales/zh-TW/README.md +++ b/locales/zh-TW/README.md @@ -183,26 +183,26 @@ code --install-extension bin/roo-cline-.vsix |:---:|:---:|:---:|:---:|:---:|:---:| |ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|hannesrudolph
hannesrudolph
|nissa-seru
nissa-seru
| |jquanton
jquanton
|KJ7LNW
KJ7LNW
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
|punkpeye
punkpeye
| -|monotykamary
monotykamary
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|feifei325
feifei325
|wkordalski
wkordalski
|cannuri
cannuri
|lloydchang
lloydchang
| -|vigneshsubbiah16
vigneshsubbiah16
|Szpadel
Szpadel
|lupuletic
lupuletic
|qdaxb
qdaxb
|Premshay
Premshay
|psv2522
psv2522
| -|diarmidmackenzie
diarmidmackenzie
|olweraltuve
olweraltuve
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
| -|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|kyle-apex
kyle-apex
|emshvac
emshvac
|Lunchb0ne
Lunchb0ne
|arthurauffray
arthurauffray
| -|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
| -|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
|vincentsong
vincentsong
| -|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
|ross
ross
| -|philfung
philfung
|nbihan-mediware
nbihan-mediware
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
|Chenjiayuan195
Chenjiayuan195
| -|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
|anton-otee
anton-otee
| -|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
| -|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
| -|ashktn
ashktn
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
|nobu007
nobu007
| -|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
|samir-nimbly
samir-nimbly
| -|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
| -|AMHesch
AMHesch
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| +|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|feifei325
feifei325
|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
| +|wkordalski
wkordalski
|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|qdaxb
qdaxb
| +|lupuletic
lupuletic
|olweraltuve
olweraltuve
|afshawnlotfi
afshawnlotfi
|aheizi
aheizi
|RaySinner
RaySinner
|PeterDaveHello
PeterDaveHello
| +|emshvac
emshvac
|kyle-apex
kyle-apex
|nbihan-mediware
nbihan-mediware
|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
| +|arthurauffray
arthurauffray
|zhangtony239
zhangtony239
|upamune
upamune
|StevenTCramer
StevenTCramer
|sammcj
sammcj
|p12tic
p12tic
| +|gtaylor
gtaylor
|dtrugman
dtrugman
|aitoroses
aitoroses
|yt3trees
yt3trees
|franekp
franekp
|yongjer
yongjer
| +|vincentsong
vincentsong
|vagadiya
vagadiya
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|taisukeoe
taisukeoe
|heyseth
heyseth
| +|sachasayan
sachasayan
|ross
ross
|philfung
philfung
|napter
napter
|mdp
mdp
|SplittyDev
SplittyDev
| +|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
|GitlyHallows
GitlyHallows
|bramburn
bramburn
|benzntech
benzntech
|axkirillov
axkirillov
| +|anton-otee
anton-otee
|shoopapa
shoopapa
|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
| +|olup
olup
|mecab
mecab
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
| +|axmo
axmo
|ashktn
ashktn
|amittell
amittell
|AMHesch
AMHesch
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| +|nobu007
nobu007
|oprstchn
oprstchn
|philipnext
philipnext
|pokutuna
pokutuna
|refactorthis
refactorthis
|ronyblum
ronyblum
| +|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
| +|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
|andreastempsch
andreastempsch
|atlasgong
atlasgong
|Atlogit
Atlogit
| |bogdan0083
bogdan0083
|chadgauth
chadgauth
|dleen
dleen
|elianiva
elianiva
|dbasclpy
dbasclpy
|snoyiatk
snoyiatk
| |linegel
linegel
|celestial-vault
celestial-vault
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| |shtse8
shtse8
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
|Sarke
Sarke
| -|01Rian
01Rian
|sachasayan
sachasayan
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
| -|vladstudio
vladstudio
| | | | | | +|01Rian
01Rian
|samsilveira
samsilveira
|maekawataiki
maekawataiki
|tgfjt
tgfjt
|tmsjngx0
tmsjngx0
|vladstudio
vladstudio
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
| | | | | | ## 授權 From 6772306ade0aa93d6905667a0bf92c57525f9f2c Mon Sep 17 00:00:00 2001 From: Felix NyxJae <52313587+NyxJae@users.noreply.github.com> Date: Fri, 18 Apr 2025 22:29:32 +0800 Subject: [PATCH 11/15] Fix: Correct path handling for dragged files on Windows (#2753) --- webview-ui/src/utils/path-mentions.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/webview-ui/src/utils/path-mentions.ts b/webview-ui/src/utils/path-mentions.ts index 3021fe5069..1afa76156a 100644 --- a/webview-ui/src/utils/path-mentions.ts +++ b/webview-ui/src/utils/path-mentions.ts @@ -13,7 +13,18 @@ */ export function convertToMentionPath(path: string, cwd?: string): string { // Strip file:// protocol if present - const pathWithoutProtocol = path.startsWith("file://") ? path.substring(7) : path + let pathWithoutProtocol = path.startsWith("file://") ? path.substring(7) : path + + try { + pathWithoutProtocol = decodeURIComponent(pathWithoutProtocol) + // Fix: Remove leading slash for Windows paths like /d:/... + if (pathWithoutProtocol.startsWith("/") && pathWithoutProtocol[2] === ":") { + pathWithoutProtocol = pathWithoutProtocol.substring(1) + } + } catch (e) { + // Log error if decoding fails, but continue with the potentially problematic path + console.error("Error decoding URI component in convertToMentionPath:", e, pathWithoutProtocol) + } const normalizedPath = pathWithoutProtocol.replace(/\\/g, "/") let normalizedCwd = cwd ? cwd.replace(/\\/g, "/") : "" From 5abea50cf1ba25a1984aa643c0ebbc269a2df7d2 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Fri, 18 Apr 2025 12:26:17 -0700 Subject: [PATCH 12/15] Support Gemini 2.5 Flash thinking (#2752) --- .changeset/shiny-poems-search.md | 5 + package-lock.json | 35 +++- package.json | 2 +- src/api/providers/__tests__/gemini.test.ts | 150 ++++++----------- src/api/providers/anthropic.ts | 16 +- src/api/providers/gemini.ts | 157 ++++++++++++------ src/api/transform/gemini-format.ts | 95 +++++------ src/exports/roo-code.d.ts | 5 + src/exports/types.ts | 5 + src/schemas/index.ts | 1 + src/shared/api.ts | 22 +++ .../components/settings/ThinkingBudget.tsx | 57 +++---- 12 files changed, 306 insertions(+), 244 deletions(-) create mode 100644 .changeset/shiny-poems-search.md diff --git a/.changeset/shiny-poems-search.md b/.changeset/shiny-poems-search.md new file mode 100644 index 0000000000..699843060e --- /dev/null +++ b/.changeset/shiny-poems-search.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Support Gemini 2.5 Flash thinking mode diff --git a/package-lock.json b/package-lock.json index cb574238fc..fa1a5a7c13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@anthropic-ai/vertex-sdk": "^0.7.0", "@aws-sdk/client-bedrock-runtime": "^3.779.0", "@google-cloud/vertexai": "^1.9.3", - "@google/generative-ai": "^0.18.0", + "@google/genai": "^0.9.0", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.7.0", "@types/clone-deep": "^4.0.4", @@ -5781,14 +5781,39 @@ "node": ">=18.0.0" } }, - "node_modules/@google/generative-ai": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.18.0.tgz", - "integrity": "sha512-AhaIWSpk2tuhYHrBhUqC0xrWWznmYEja1/TRDIb+5kruBU5kUzMlFsXCQNO9PzyTZ4clUJ3CX/Rvy+Xm9x+w3g==", + "node_modules/@google/genai": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-0.9.0.tgz", + "integrity": "sha512-FD2RizYGInsvfjeaN6O+wQGpRnGVglS1XWrGQr8K7D04AfMmvPodDSw94U9KyFtsVLzWH9kmlPyFM+G4jbmkqg==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.14.2", + "ws": "^8.18.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.4" + }, "engines": { "node": ">=18.0.0" } }, + "node_modules/@google/genai/node_modules/zod": { + "version": "3.24.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", + "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@google/genai/node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", diff --git a/package.json b/package.json index fa5387a7c1..ff026fa5cc 100644 --- a/package.json +++ b/package.json @@ -405,7 +405,7 @@ "@anthropic-ai/vertex-sdk": "^0.7.0", "@aws-sdk/client-bedrock-runtime": "^3.779.0", "@google-cloud/vertexai": "^1.9.3", - "@google/generative-ai": "^0.18.0", + "@google/genai": "^0.9.0", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.7.0", "@types/clone-deep": "^4.0.4", diff --git a/src/api/providers/__tests__/gemini.test.ts b/src/api/providers/__tests__/gemini.test.ts index d12c261b79..897ece3ed3 100644 --- a/src/api/providers/__tests__/gemini.test.ts +++ b/src/api/providers/__tests__/gemini.test.ts @@ -1,45 +1,41 @@ -import { GeminiHandler } from "../gemini" -import { Anthropic } from "@anthropic-ai/sdk" -import { GoogleGenerativeAI } from "@google/generative-ai" +// npx jest src/api/providers/__tests__/gemini.test.ts -// Mock the Google Generative AI SDK -jest.mock("@google/generative-ai", () => ({ - GoogleGenerativeAI: jest.fn().mockImplementation(() => ({ - getGenerativeModel: jest.fn().mockReturnValue({ - generateContentStream: jest.fn(), - generateContent: jest.fn().mockResolvedValue({ - response: { - text: () => "Test response", - }, - }), - }), - })), -})) +import { Anthropic } from "@anthropic-ai/sdk" + +import { GeminiHandler } from "../gemini" +import { geminiDefaultModelId } from "../../../shared/api" + +const GEMINI_20_FLASH_THINKING_NAME = "gemini-2.0-flash-thinking-exp-1219" describe("GeminiHandler", () => { let handler: GeminiHandler beforeEach(() => { + // Create mock functions + const mockGenerateContentStream = jest.fn() + const mockGenerateContent = jest.fn() + const mockGetGenerativeModel = jest.fn() + handler = new GeminiHandler({ apiKey: "test-key", - apiModelId: "gemini-2.0-flash-thinking-exp-1219", + apiModelId: GEMINI_20_FLASH_THINKING_NAME, geminiApiKey: "test-key", }) + + // Replace the client with our mock + handler["client"] = { + models: { + generateContentStream: mockGenerateContentStream, + generateContent: mockGenerateContent, + getGenerativeModel: mockGetGenerativeModel, + }, + } as any }) describe("constructor", () => { it("should initialize with provided config", () => { expect(handler["options"].geminiApiKey).toBe("test-key") - expect(handler["options"].apiModelId).toBe("gemini-2.0-flash-thinking-exp-1219") - }) - - it.skip("should throw if API key is missing", () => { - expect(() => { - new GeminiHandler({ - apiModelId: "gemini-2.0-flash-thinking-exp-1219", - geminiApiKey: "", - }) - }).toThrow("API key is required for Google Gemini") + expect(handler["options"].apiModelId).toBe(GEMINI_20_FLASH_THINKING_NAME) }) }) @@ -58,25 +54,15 @@ describe("GeminiHandler", () => { const systemPrompt = "You are a helpful assistant" it("should handle text messages correctly", async () => { - // Mock the stream response - const mockStream = { - stream: [{ text: () => "Hello" }, { text: () => " world!" }], - response: { - usageMetadata: { - promptTokenCount: 10, - candidatesTokenCount: 5, - }, + // Setup the mock implementation to return an async generator + ;(handler["client"].models.generateContentStream as jest.Mock).mockResolvedValue({ + [Symbol.asyncIterator]: async function* () { + yield { text: "Hello" } + yield { text: " world!" } + yield { usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 5 } } }, - } - - // Setup the mock implementation - const mockGenerateContentStream = jest.fn().mockResolvedValue(mockStream) - const mockGetGenerativeModel = jest.fn().mockReturnValue({ - generateContentStream: mockGenerateContentStream, }) - ;(handler["client"] as any).getGenerativeModel = mockGetGenerativeModel - const stream = handler.createMessage(systemPrompt, mockMessages) const chunks = [] @@ -100,35 +86,21 @@ describe("GeminiHandler", () => { outputTokens: 5, }) - // Verify the model configuration - expect(mockGetGenerativeModel).toHaveBeenCalledWith( - { - model: "gemini-2.0-flash-thinking-exp-1219", - systemInstruction: systemPrompt, - }, - { - baseUrl: undefined, - }, - ) - - // Verify generation config - expect(mockGenerateContentStream).toHaveBeenCalledWith( + // Verify the call to generateContentStream + expect(handler["client"].models.generateContentStream).toHaveBeenCalledWith( expect.objectContaining({ - generationConfig: { + model: GEMINI_20_FLASH_THINKING_NAME, + config: expect.objectContaining({ temperature: 0, - }, + systemInstruction: systemPrompt, + }), }), ) }) it("should handle API errors", async () => { const mockError = new Error("Gemini API error") - const mockGenerateContentStream = jest.fn().mockRejectedValue(mockError) - const mockGetGenerativeModel = jest.fn().mockReturnValue({ - generateContentStream: mockGenerateContentStream, - }) - - ;(handler["client"] as any).getGenerativeModel = mockGetGenerativeModel + ;(handler["client"].models.generateContentStream as jest.Mock).mockRejectedValue(mockError) const stream = handler.createMessage(systemPrompt, mockMessages) @@ -136,35 +108,26 @@ describe("GeminiHandler", () => { for await (const chunk of stream) { // Should throw before yielding any chunks } - }).rejects.toThrow("Gemini API error") + }).rejects.toThrow() }) }) describe("completePrompt", () => { it("should complete prompt successfully", async () => { - const mockGenerateContent = jest.fn().mockResolvedValue({ - response: { - text: () => "Test response", - }, + // Mock the response with text property + ;(handler["client"].models.generateContent as jest.Mock).mockResolvedValue({ + text: "Test response", }) - const mockGetGenerativeModel = jest.fn().mockReturnValue({ - generateContent: mockGenerateContent, - }) - ;(handler["client"] as any).getGenerativeModel = mockGetGenerativeModel const result = await handler.completePrompt("Test prompt") expect(result).toBe("Test response") - expect(mockGetGenerativeModel).toHaveBeenCalledWith( - { - model: "gemini-2.0-flash-thinking-exp-1219", - }, - { - baseUrl: undefined, - }, - ) - expect(mockGenerateContent).toHaveBeenCalledWith({ + + // Verify the call to generateContent + expect(handler["client"].models.generateContent).toHaveBeenCalledWith({ + model: GEMINI_20_FLASH_THINKING_NAME, contents: [{ role: "user", parts: [{ text: "Test prompt" }] }], - generationConfig: { + config: { + httpOptions: undefined, temperature: 0, }, }) @@ -172,11 +135,7 @@ describe("GeminiHandler", () => { it("should handle API errors", async () => { const mockError = new Error("Gemini API error") - const mockGenerateContent = jest.fn().mockRejectedValue(mockError) - const mockGetGenerativeModel = jest.fn().mockReturnValue({ - generateContent: mockGenerateContent, - }) - ;(handler["client"] as any).getGenerativeModel = mockGetGenerativeModel + ;(handler["client"].models.generateContent as jest.Mock).mockRejectedValue(mockError) await expect(handler.completePrompt("Test prompt")).rejects.toThrow( "Gemini completion error: Gemini API error", @@ -184,15 +143,10 @@ describe("GeminiHandler", () => { }) it("should handle empty response", async () => { - const mockGenerateContent = jest.fn().mockResolvedValue({ - response: { - text: () => "", - }, + // Mock the response with empty text + ;(handler["client"].models.generateContent as jest.Mock).mockResolvedValue({ + text: "", }) - const mockGetGenerativeModel = jest.fn().mockReturnValue({ - generateContent: mockGenerateContent, - }) - ;(handler["client"] as any).getGenerativeModel = mockGetGenerativeModel const result = await handler.completePrompt("Test prompt") expect(result).toBe("") @@ -202,7 +156,7 @@ describe("GeminiHandler", () => { describe("getModel", () => { it("should return correct model info", () => { const modelInfo = handler.getModel() - expect(modelInfo.id).toBe("gemini-2.0-flash-thinking-exp-1219") + expect(modelInfo.id).toBe(GEMINI_20_FLASH_THINKING_NAME) expect(modelInfo.info).toBeDefined() expect(modelInfo.info.maxTokens).toBe(8192) expect(modelInfo.info.contextWindow).toBe(32_767) @@ -214,7 +168,7 @@ describe("GeminiHandler", () => { geminiApiKey: "test-key", }) const modelInfo = invalidHandler.getModel() - expect(modelInfo.id).toBe("gemini-2.0-flash-001") // Default model + expect(modelInfo.id).toBe(geminiDefaultModelId) // Default model }) }) }) diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index a906ad6e7e..9032754ac6 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -23,6 +23,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa const apiKeyFieldName = this.options.anthropicBaseUrl && this.options.anthropicUseAuthToken ? "authToken" : "apiKey" + this.client = new Anthropic({ baseURL: this.options.anthropicBaseUrl || undefined, [apiKeyFieldName]: this.options.apiKey, @@ -217,10 +218,10 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa } async completePrompt(prompt: string) { - let { id: modelId, temperature } = this.getModel() + let { id: model, temperature } = this.getModel() const message = await this.client.messages.create({ - model: modelId, + model, max_tokens: ANTHROPIC_DEFAULT_MAX_TOKENS, thinking: undefined, temperature, @@ -241,16 +242,11 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa override async countTokens(content: Array): Promise { try { // Use the current model - const actualModelId = this.getModel().id + const { id: model } = this.getModel() const response = await this.client.messages.countTokens({ - model: actualModelId, - messages: [ - { - role: "user", - content: content, - }, - ], + model, + messages: [{ role: "user", content: content }], }) return response.input_tokens diff --git a/src/api/providers/gemini.ts b/src/api/providers/gemini.ts index 98117e99a9..7389611300 100644 --- a/src/api/providers/gemini.ts +++ b/src/api/providers/gemini.ts @@ -1,89 +1,142 @@ -import { Anthropic } from "@anthropic-ai/sdk" -import { GoogleGenerativeAI } from "@google/generative-ai" -import { SingleCompletionHandler } from "../" -import { ApiHandlerOptions, geminiDefaultModelId, GeminiModelId, geminiModels, ModelInfo } from "../../shared/api" -import { convertAnthropicMessageToGemini } from "../transform/gemini-format" -import { ApiStream } from "../transform/stream" -import { BaseProvider } from "./base-provider" +import type { Anthropic } from "@anthropic-ai/sdk" +import { + GoogleGenAI, + ThinkingConfig, + type GenerateContentResponseUsageMetadata, + type GenerateContentParameters, +} from "@google/genai" -const GEMINI_DEFAULT_TEMPERATURE = 0 +import { SingleCompletionHandler } from "../" +import type { ApiHandlerOptions, GeminiModelId, ModelInfo } from "../../shared/api" +import { geminiDefaultModelId, geminiModels } from "../../shared/api" +import { convertAnthropicContentToGemini, convertAnthropicMessageToGemini } from "../transform/gemini-format" +import type { ApiStream } from "../transform/stream" +import { BaseProvider } from "./base-provider" export class GeminiHandler extends BaseProvider implements SingleCompletionHandler { protected options: ApiHandlerOptions - private client: GoogleGenerativeAI + private client: GoogleGenAI constructor(options: ApiHandlerOptions) { super() this.options = options - this.client = new GoogleGenerativeAI(options.geminiApiKey ?? "not-provided") + this.client = new GoogleGenAI({ apiKey: options.geminiApiKey ?? "not-provided" }) } - override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { - const model = this.client.getGenerativeModel( - { - model: this.getModel().id, + async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const { id: model, thinkingConfig, maxOutputTokens } = this.getModel() + + const params: GenerateContentParameters = { + model, + contents: messages.map(convertAnthropicMessageToGemini), + config: { + thinkingConfig, + maxOutputTokens, + temperature: this.options.modelTemperature ?? 0, systemInstruction: systemPrompt, }, - { - baseUrl: this.options.googleGeminiBaseUrl || undefined, - }, - ) - const result = await model.generateContentStream({ - contents: messages.map(convertAnthropicMessageToGemini), - generationConfig: { - // maxOutputTokens: this.getModel().info.maxTokens, - temperature: this.options.modelTemperature ?? GEMINI_DEFAULT_TEMPERATURE, - }, - }) + } - for await (const chunk of result.stream) { - yield { - type: "text", - text: chunk.text(), + const result = await this.client.models.generateContentStream(params) + + let lastUsageMetadata: GenerateContentResponseUsageMetadata | undefined + + for await (const chunk of result) { + if (chunk.text) { + yield { type: "text", text: chunk.text } + } + + if (chunk.usageMetadata) { + lastUsageMetadata = chunk.usageMetadata } } - const response = await result.response - yield { - type: "usage", - inputTokens: response.usageMetadata?.promptTokenCount ?? 0, - outputTokens: response.usageMetadata?.candidatesTokenCount ?? 0, + if (lastUsageMetadata) { + yield { + type: "usage", + inputTokens: lastUsageMetadata.promptTokenCount ?? 0, + outputTokens: lastUsageMetadata.candidatesTokenCount ?? 0, + } } } - override getModel(): { id: GeminiModelId; info: ModelInfo } { - const modelId = this.options.apiModelId - if (modelId && modelId in geminiModels) { - const id = modelId as GeminiModelId - return { id, info: geminiModels[id] } + override getModel(): { + id: GeminiModelId + info: ModelInfo + thinkingConfig?: ThinkingConfig + maxOutputTokens?: number + } { + let id = this.options.apiModelId ? (this.options.apiModelId as GeminiModelId) : geminiDefaultModelId + let info: ModelInfo = geminiModels[id] + let thinkingConfig: ThinkingConfig | undefined = undefined + let maxOutputTokens: number | undefined = undefined + + const thinkingSuffix = ":thinking" + + if (id?.endsWith(thinkingSuffix)) { + id = id.slice(0, -thinkingSuffix.length) as GeminiModelId + info = geminiModels[id] + + thinkingConfig = this.options.modelMaxThinkingTokens + ? { thinkingBudget: this.options.modelMaxThinkingTokens } + : undefined + + maxOutputTokens = this.options.modelMaxTokens ?? info.maxTokens ?? undefined } - return { id: geminiDefaultModelId, info: geminiModels[geminiDefaultModelId] } + + if (!info) { + id = geminiDefaultModelId + info = geminiModels[geminiDefaultModelId] + thinkingConfig = undefined + maxOutputTokens = undefined + } + + return { id, info, thinkingConfig, maxOutputTokens } } async completePrompt(prompt: string): Promise { try { - const model = this.client.getGenerativeModel( - { - model: this.getModel().id, - }, - { - baseUrl: this.options.googleGeminiBaseUrl || undefined, - }, - ) + const { id: model } = this.getModel() - const result = await model.generateContent({ + const result = await this.client.models.generateContent({ + model, contents: [{ role: "user", parts: [{ text: prompt }] }], - generationConfig: { - temperature: this.options.modelTemperature ?? GEMINI_DEFAULT_TEMPERATURE, + config: { + httpOptions: this.options.googleGeminiBaseUrl + ? { baseUrl: this.options.googleGeminiBaseUrl } + : undefined, + temperature: this.options.modelTemperature ?? 0, }, }) - return result.response.text() + return result.text ?? "" } catch (error) { if (error instanceof Error) { throw new Error(`Gemini completion error: ${error.message}`) } + throw error } } + + override async countTokens(content: Array): Promise { + try { + const { id: model } = this.getModel() + + const response = await this.client.models.countTokens({ + model, + contents: convertAnthropicContentToGemini(content), + }) + + if (response.totalTokens === undefined) { + console.warn("Gemini token counting returned undefined, using fallback") + return super.countTokens(content) + } + + return response.totalTokens + } catch (error) { + console.warn("Gemini token counting failed, using fallback", error) + return super.countTokens(content) + } + } } diff --git a/src/api/transform/gemini-format.ts b/src/api/transform/gemini-format.ts index c8fc80d769..ee22cff32a 100644 --- a/src/api/transform/gemini-format.ts +++ b/src/api/transform/gemini-format.ts @@ -1,76 +1,71 @@ import { Anthropic } from "@anthropic-ai/sdk" -import { Content, FunctionCallPart, FunctionResponsePart, InlineDataPart, Part, TextPart } from "@google/generative-ai" +import { Content, Part } from "@google/genai" -function convertAnthropicContentToGemini(content: Anthropic.Messages.MessageParam["content"]): Part[] { +export function convertAnthropicContentToGemini(content: string | Anthropic.ContentBlockParam[]): Part[] { if (typeof content === "string") { - return [{ text: content } as TextPart] + return [{ text: content }] } - return content.flatMap((block) => { + return content.flatMap((block): Part | Part[] => { switch (block.type) { case "text": - return { text: block.text } as TextPart + return { text: block.text } case "image": if (block.source.type !== "base64") { throw new Error("Unsupported image source type") } - return { - inlineData: { - data: block.source.data, - mimeType: block.source.media_type, - }, - } as InlineDataPart + + return { inlineData: { data: block.source.data, mimeType: block.source.media_type } } case "tool_use": return { functionCall: { name: block.name, - args: block.input, + args: block.input as Record, }, - } as FunctionCallPart - case "tool_result": - const name = block.tool_use_id.split("-")[0] + } + case "tool_result": { if (!block.content) { return [] } + + // Extract tool name from tool_use_id (e.g., "calculator-123" -> "calculator") + const toolName = block.tool_use_id.split("-")[0] + if (typeof block.content === "string") { return { - functionResponse: { - name, - response: { - name, - content: block.content, - }, - }, - } as FunctionResponsePart - } else { - // The only case when tool_result could be array is when the tool failed and we're providing ie user feedback potentially with images - const textParts = block.content.filter((part) => part.type === "text") - const imageParts = block.content.filter((part) => part.type === "image") - const text = textParts.length > 0 ? textParts.map((part) => part.text).join("\n\n") : "" - const imageText = imageParts.length > 0 ? "\n\n(See next part for image)" : "" - return [ - { - functionResponse: { - name, - response: { - name, - content: text + imageText, - }, - }, - } as FunctionResponsePart, - ...imageParts.map( - (part) => - ({ - inlineData: { - data: part.source.data, - mimeType: part.source.media_type, - }, - }) as InlineDataPart, - ), - ] + functionResponse: { name: toolName, response: { name: toolName, content: block.content } }, + } } + + if (!Array.isArray(block.content)) { + return [] + } + + const textParts: string[] = [] + const imageParts: Part[] = [] + + for (const item of block.content) { + if (item.type === "text") { + textParts.push(item.text) + } else if (item.type === "image" && item.source.type === "base64") { + const { data, media_type } = item.source + imageParts.push({ inlineData: { data, mimeType: media_type } }) + } + } + + // Create content text with a note about images if present + const contentText = + textParts.join("\n\n") + (imageParts.length > 0 ? "\n\n(See next part for image)" : "") + + // Return function response followed by any images + return [ + { functionResponse: { name: toolName, response: { name: toolName, content: contentText } } }, + ...imageParts, + ] + } default: - throw new Error(`Unsupported content block type: ${(block as any).type}`) + // Currently unsupported: "thinking" | "redacted_thinking" | "document" + throw new Error(`Unsupported content block type: ${block.type}`) } }) } diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 422297ddd3..d2a28a8273 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -31,6 +31,7 @@ type ProviderSettings = { glamaModelInfo?: | ({ maxTokens?: (number | null) | undefined + maxThinkingTokens?: (number | null) | undefined contextWindow: number supportsImages?: boolean | undefined supportsComputerUse?: boolean | undefined @@ -53,6 +54,7 @@ type ProviderSettings = { openRouterModelInfo?: | ({ maxTokens?: (number | null) | undefined + maxThinkingTokens?: (number | null) | undefined contextWindow: number supportsImages?: boolean | undefined supportsComputerUse?: boolean | undefined @@ -95,6 +97,7 @@ type ProviderSettings = { openAiCustomModelInfo?: | ({ maxTokens?: (number | null) | undefined + maxThinkingTokens?: (number | null) | undefined contextWindow: number supportsImages?: boolean | undefined supportsComputerUse?: boolean | undefined @@ -140,6 +143,7 @@ type ProviderSettings = { unboundModelInfo?: | ({ maxTokens?: (number | null) | undefined + maxThinkingTokens?: (number | null) | undefined contextWindow: number supportsImages?: boolean | undefined supportsComputerUse?: boolean | undefined @@ -161,6 +165,7 @@ type ProviderSettings = { requestyModelInfo?: | ({ maxTokens?: (number | null) | undefined + maxThinkingTokens?: (number | null) | undefined contextWindow: number supportsImages?: boolean | undefined supportsComputerUse?: boolean | undefined diff --git a/src/exports/types.ts b/src/exports/types.ts index df38b929ed..4280511a8d 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -32,6 +32,7 @@ type ProviderSettings = { glamaModelInfo?: | ({ maxTokens?: (number | null) | undefined + maxThinkingTokens?: (number | null) | undefined contextWindow: number supportsImages?: boolean | undefined supportsComputerUse?: boolean | undefined @@ -54,6 +55,7 @@ type ProviderSettings = { openRouterModelInfo?: | ({ maxTokens?: (number | null) | undefined + maxThinkingTokens?: (number | null) | undefined contextWindow: number supportsImages?: boolean | undefined supportsComputerUse?: boolean | undefined @@ -96,6 +98,7 @@ type ProviderSettings = { openAiCustomModelInfo?: | ({ maxTokens?: (number | null) | undefined + maxThinkingTokens?: (number | null) | undefined contextWindow: number supportsImages?: boolean | undefined supportsComputerUse?: boolean | undefined @@ -141,6 +144,7 @@ type ProviderSettings = { unboundModelInfo?: | ({ maxTokens?: (number | null) | undefined + maxThinkingTokens?: (number | null) | undefined contextWindow: number supportsImages?: boolean | undefined supportsComputerUse?: boolean | undefined @@ -162,6 +166,7 @@ type ProviderSettings = { requestyModelInfo?: | ({ maxTokens?: (number | null) | undefined + maxThinkingTokens?: (number | null) | undefined contextWindow: number supportsImages?: boolean | undefined supportsComputerUse?: boolean | undefined diff --git a/src/schemas/index.ts b/src/schemas/index.ts index db9feb54b4..24b224c08b 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -99,6 +99,7 @@ export type ReasoningEffort = z.infer export const modelInfoSchema = z.object({ maxTokens: z.number().nullish(), + maxThinkingTokens: z.number().nullish(), contextWindow: z.number(), supportsImages: z.boolean().optional(), supportsComputerUse: z.boolean().optional(), diff --git a/src/shared/api.ts b/src/shared/api.ts index 4d71d947ba..ebc0b85c93 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -485,6 +485,16 @@ export const vertexModels = { inputPrice: 0.15, outputPrice: 0.6, }, + "gemini-2.5-flash-preview-04-17:thinking": { + maxTokens: 65_535, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0.15, + outputPrice: 0.6, + thinking: true, + maxThinkingTokens: 24_576, + }, "gemini-2.5-flash-preview-04-17": { maxTokens: 65_535, contextWindow: 1_048_576, @@ -492,6 +502,7 @@ export const vertexModels = { supportsPromptCache: false, inputPrice: 0.15, outputPrice: 0.6, + thinking: false, }, "gemini-2.5-pro-preview-03-25": { maxTokens: 65_535, @@ -640,6 +651,16 @@ export const openAiModelInfoSaneDefaults: ModelInfo = { export type GeminiModelId = keyof typeof geminiModels export const geminiDefaultModelId: GeminiModelId = "gemini-2.0-flash-001" export const geminiModels = { + "gemini-2.5-flash-preview-04-17:thinking": { + maxTokens: 65_535, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0.15, + outputPrice: 0.6, + thinking: true, + maxThinkingTokens: 24_576, + }, "gemini-2.5-flash-preview-04-17": { maxTokens: 65_535, contextWindow: 1_048_576, @@ -647,6 +668,7 @@ export const geminiModels = { supportsPromptCache: false, inputPrice: 0.15, outputPrice: 0.6, + thinking: false, }, "gemini-2.5-pro-exp-03-25": { maxTokens: 65_535, diff --git a/webview-ui/src/components/settings/ThinkingBudget.tsx b/webview-ui/src/components/settings/ThinkingBudget.tsx index e4cb4f0b9c..5123d571b3 100644 --- a/webview-ui/src/components/settings/ThinkingBudget.tsx +++ b/webview-ui/src/components/settings/ThinkingBudget.tsx @@ -1,10 +1,13 @@ -import { useEffect, useMemo } from "react" +import { useEffect } from "react" import { useAppTranslation } from "@/i18n/TranslationContext" import { Slider } from "@/components/ui" import { ApiConfiguration, ModelInfo } from "../../../../src/shared/api" +const DEFAULT_MAX_OUTPUT_TOKENS = 16_384 +const DEFAULT_MAX_THINKING_TOKENS = 8_192 + interface ThinkingBudgetProps { apiConfiguration: ApiConfiguration setApiConfigurationField: (field: K, value: ApiConfiguration[K]) => void @@ -13,57 +16,55 @@ interface ThinkingBudgetProps { export const ThinkingBudget = ({ apiConfiguration, setApiConfigurationField, modelInfo }: ThinkingBudgetProps) => { const { t } = useAppTranslation() - const tokens = apiConfiguration?.modelMaxTokens || 16_384 - const tokensMin = 8192 - const tokensMax = modelInfo?.maxTokens || 64_000 - // Get the appropriate thinking tokens based on provider - const thinkingTokens = useMemo(() => { - const value = apiConfiguration?.modelMaxThinkingTokens - return value || Math.min(Math.floor(0.8 * tokens), 8192) - }, [apiConfiguration, tokens]) + const isThinkingModel = modelInfo && modelInfo.thinking && modelInfo.maxTokens - const thinkingTokensMin = 1024 - const thinkingTokensMax = Math.floor(0.8 * tokens) + const customMaxOutputTokens = apiConfiguration.modelMaxTokens || DEFAULT_MAX_OUTPUT_TOKENS + const customMaxThinkingTokens = apiConfiguration.modelMaxThinkingTokens || DEFAULT_MAX_THINKING_TOKENS + // Dynamically expand or shrink the max thinking budget based on the custom + // max output tokens so that there's always a 20% buffer. + const modelMaxThinkingTokens = modelInfo?.maxThinkingTokens + ? Math.min(modelInfo.maxThinkingTokens, Math.floor(0.8 * customMaxOutputTokens)) + : Math.floor(0.8 * customMaxOutputTokens) + + // If the custom max thinking tokens are going to exceed it's limit due + // to the custom max output tokens being reduced then we need to shrink it + // appropriately. useEffect(() => { - if (thinkingTokens > thinkingTokensMax) { - setApiConfigurationField("modelMaxThinkingTokens", thinkingTokensMax) + if (isThinkingModel && customMaxThinkingTokens > modelMaxThinkingTokens) { + setApiConfigurationField("modelMaxThinkingTokens", modelMaxThinkingTokens) } - }, [thinkingTokens, thinkingTokensMax, setApiConfigurationField]) + }, [isThinkingModel, customMaxThinkingTokens, modelMaxThinkingTokens, setApiConfigurationField]) - if (!modelInfo?.thinking) { - return null - } - - return ( + return isThinkingModel ? ( <>
{t("settings:thinkingBudget.maxTokens")}
setApiConfigurationField("modelMaxTokens", value)} /> -
{tokens}
+
{customMaxOutputTokens}
{t("settings:thinkingBudget.maxThinkingTokens")}
setApiConfigurationField("modelMaxThinkingTokens", value)} /> -
{thinkingTokens}
+
{customMaxThinkingTokens}
- ) + ) : null } From 0bb5ec18c56b0f8bc28579c3b9c028372b6b23d5 Mon Sep 17 00:00:00 2001 From: Sacha Sayan Date: Fri, 18 Apr 2025 16:43:03 -0400 Subject: [PATCH 13/15] UI: Auto-approve toggle styling tweak. (#2769) --- .../components/settings/AutoApproveToggle.tsx | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/webview-ui/src/components/settings/AutoApproveToggle.tsx b/webview-ui/src/components/settings/AutoApproveToggle.tsx index 7d530b2beb..f46ed06838 100644 --- a/webview-ui/src/components/settings/AutoApproveToggle.tsx +++ b/webview-ui/src/components/settings/AutoApproveToggle.tsx @@ -95,27 +95,23 @@ export const AutoApproveToggle = ({ onToggle, ...props }: AutoApproveToggleProps return (
{Object.values(autoApproveSettingsConfig).map(({ key, descriptionKey, labelKey, icon, testId }) => ( -
- -
+ ))}
) From f6e4e3504f76f34c6381972500a4e64a83ac4780 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Fri, 18 Apr 2025 14:43:04 -0700 Subject: [PATCH 14/15] Move executeCommand out of Cline and add telemetry for shell integration errors (#2771) --- .changeset/four-trainers-move.md | 5 + src/core/Cline.ts | 192 ++---------------- src/core/__tests__/Cline.test.ts | 56 +---- .../read-file-maxReadFileLine.test.ts | 4 +- src/core/__tests__/read-file-xml.test.ts | 3 +- src/core/diff/DiffStrategy.ts | 22 -- .../diff/strategies/multi-search-replace.ts | 3 +- src/core/diff/types.ts | 47 ----- src/core/prompts/__tests__/sections.test.ts | 2 +- .../prompts/instructions/create-mcp-server.ts | 2 +- src/core/prompts/instructions/instructions.ts | 2 +- src/core/prompts/sections/capabilities.ts | 2 +- src/core/prompts/sections/mcp-servers.ts | 2 +- src/core/prompts/sections/rules.ts | 2 +- src/core/prompts/system.ts | 2 +- src/core/prompts/tools/index.ts | 3 +- src/core/prompts/tools/types.ts | 2 +- .../__tests__/executeCommandTool.test.ts | 166 +++++++-------- src/core/tools/accessMcpResourceTool.ts | 5 +- src/core/tools/appendToFileTool.ts | 5 +- src/core/tools/applyDiffTool.ts | 9 +- src/core/tools/askFollowupQuestionTool.ts | 5 +- src/core/tools/attemptCompletionTool.ts | 6 +- src/core/tools/browserActionTool.ts | 12 +- src/core/tools/executeCommandTool.ts | 181 ++++++++++++++++- src/core/tools/fetchInstructionsTool.ts | 3 +- src/core/tools/insertContentTool.ts | 9 +- src/core/tools/listCodeDefinitionNamesTool.ts | 3 +- src/core/tools/listFilesTool.ts | 3 +- src/core/tools/newTaskTool.ts | 5 +- src/core/tools/readFileTool.ts | 7 +- src/core/tools/searchAndReplaceTool.ts | 9 +- src/core/tools/searchFilesTool.ts | 5 +- src/core/tools/switchModeTool.ts | 8 +- src/core/tools/useMcpToolTool.ts | 7 +- src/core/tools/writeToFileTool.ts | 7 +- .../webview/__tests__/ClineProvider.test.ts | 7 - src/core/webview/webviewMessageHandler.ts | 9 +- src/services/telemetry/PostHogClient.ts | 1 + src/services/telemetry/TelemetryService.ts | 4 + src/shared/tools.ts | 42 ++++ 41 files changed, 393 insertions(+), 476 deletions(-) create mode 100644 .changeset/four-trainers-move.md delete mode 100644 src/core/diff/DiffStrategy.ts delete mode 100644 src/core/diff/types.ts diff --git a/.changeset/four-trainers-move.md b/.changeset/four-trainers-move.md new file mode 100644 index 0000000000..b6aaf84160 --- /dev/null +++ b/.changeset/four-trainers-move.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Move executeCommand out of Cline and add telemetry for shell integration errors diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 1b06517b79..cde87ebc85 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -39,7 +39,7 @@ import { GlobalFileNames } from "../shared/globalFileNames" import { defaultModeSlug, getModeBySlug, getFullModeDetails, isToolAllowedForMode } from "../shared/modes" import { EXPERIMENT_IDS, experiments as Experiments, ExperimentId } from "../shared/experiments" import { formatLanguage } from "../shared/language" -import { ToolParamName, ToolResponse } from "../shared/tools" +import { ToolParamName, ToolResponse, DiffStrategy } from "../shared/tools" // services import { UrlContentFetcher } from "../services/browser/UrlContentFetcher" @@ -52,7 +52,6 @@ import { CheckpointServiceOptions, RepoPerTaskCheckpointService } from "../servi // integrations import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "../integrations/editor/DiffViewProvider" import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown" -import { ExitCodeDetails, TerminalProcess } from "../integrations/terminal/TerminalProcess" import { Terminal } from "../integrations/terminal/Terminal" import { TerminalRegistry } from "../integrations/terminal/TerminalRegistry" @@ -92,8 +91,8 @@ import { RooIgnoreController } from "./ignore/RooIgnoreController" import { type AssistantMessageContent, parseAssistantMessage } from "./assistant-message" import { truncateConversationIfNeeded } from "./sliding-window" import { ClineProvider } from "./webview/ClineProvider" -import { DiffStrategy, getDiffStrategy } from "./diff/DiffStrategy" import { validateToolUse } from "./mode-validator" +import { MultiSearchReplaceDiffStrategy } from "./diff/strategies/multi-search-replace" type UserContent = Array @@ -247,8 +246,7 @@ export class Cline extends EventEmitter { telemetryService.captureTaskCreated(this.taskId) } - // Initialize diffStrategy based on current state. - this.updateDiffStrategy(experiments ?? {}) + this.diffStrategy = new MultiSearchReplaceDiffStrategy(this.fuzzyMatchThreshold) onCreated?.(this) @@ -283,15 +281,6 @@ export class Cline extends EventEmitter { return getWorkspacePath(path.join(os.homedir(), "Desktop")) } - // Add method to update diffStrategy. - async updateDiffStrategy(experiments: Partial>) { - this.diffStrategy = getDiffStrategy({ - model: this.api.getModel().id, - experiments, - fuzzyMatchThreshold: this.fuzzyMatchThreshold, - }) - } - // Storing task to disk for history private async ensureTaskDirectoryExists(): Promise { @@ -308,9 +297,11 @@ export class Cline extends EventEmitter { private async getSavedApiConversationHistory(): Promise { const filePath = path.join(await this.ensureTaskDirectoryExists(), GlobalFileNames.apiConversationHistory) const fileExists = await fileExistsAtPath(filePath) + if (fileExists) { return JSON.parse(await fs.readFile(filePath, "utf8")) } + return [] } @@ -378,7 +369,8 @@ export class Cline extends EventEmitter { const tokenUsage = this.getTokenUsage() this.emit("taskTokenUsageUpdated", this.taskId, tokenUsage) - const taskMessage = this.clineMessages[0] // first message is always the task say + const taskMessage = this.clineMessages[0] // First message is always the task say + const lastRelevantMessage = this.clineMessages[ findLastIndex( @@ -913,11 +905,6 @@ export class Cline extends EventEmitter { } async abortTask(isAbandoned = false) { - // if (this.abort) { - // console.log(`[subtasks] already aborted task ${this.taskId}.${this.instanceId}`) - // return - // } - console.log(`[subtasks] aborting task ${this.taskId}.${this.instanceId}`) // Will stop any autonomously running promises. @@ -951,159 +938,6 @@ export class Cline extends EventEmitter { // Tools - async executeCommandTool(command: string, customCwd?: string): Promise<[boolean, ToolResponse]> { - let workingDir: string - if (!customCwd) { - workingDir = this.cwd - } else if (path.isAbsolute(customCwd)) { - workingDir = customCwd - } else { - workingDir = path.resolve(this.cwd, customCwd) - } - - // Check if directory exists - try { - await fs.access(workingDir) - } catch (error) { - return [false, `Working directory '${workingDir}' does not exist.`] - } - - const terminalInfo = await TerminalRegistry.getOrCreateTerminal(workingDir, !!customCwd, this.taskId) - - // Update the working directory in case the terminal we asked for has - // a different working directory so that the model will know where the - // command actually executed: - workingDir = terminalInfo.getCurrentWorkingDirectory() - - const workingDirInfo = workingDir ? ` from '${workingDir.toPosix()}'` : "" - terminalInfo.terminal.show() // weird visual bug when creating new terminals (even manually) where there's an empty space at the top. - let userFeedback: { text?: string; images?: string[] } | undefined - let didContinue = false - let completed = false - let result: string = "" - let exitDetails: ExitCodeDetails | undefined - const { terminalOutputLineLimit = 500 } = (await this.providerRef.deref()?.getState()) ?? {} - - const sendCommandOutput = async (line: string, terminalProcess: TerminalProcess): Promise => { - try { - const { response, text, images } = await this.ask("command_output", line) - if (response === "yesButtonClicked") { - // proceed while running - } else { - userFeedback = { text, images } - } - didContinue = true - terminalProcess.continue() // continue past the await - } catch { - // This can only happen if this ask promise was ignored, so ignore this error - } - } - - const process = terminalInfo.runCommand(command, { - onLine: (line, process) => { - if (!didContinue) { - sendCommandOutput(Terminal.compressTerminalOutput(line, terminalOutputLineLimit), process) - } else { - this.say("command_output", Terminal.compressTerminalOutput(line, terminalOutputLineLimit)) - } - }, - onCompleted: (output) => { - result = output ?? "" - completed = true - }, - onShellExecutionComplete: (details) => { - exitDetails = details - }, - onNoShellIntegration: async (message) => { - await this.say("shell_integration_warning", message) - }, - }) - - await process - - // Wait for a short delay to ensure all messages are sent to the webview - // This delay allows time for non-awaited promises to be created and - // for their associated messages to be sent to the webview, maintaining - // the correct order of messages (although the webview is smart about - // grouping command_output messages despite any gaps anyways) - await delay(50) - - result = Terminal.compressTerminalOutput(result, terminalOutputLineLimit) - - // keep in case we need it to troubleshoot user issues, but this should be removed in the future - // if everything looks good: - console.debug( - "[execute_command status]", - JSON.stringify( - { - completed, - userFeedback, - hasResult: result.length > 0, - exitDetails, - terminalId: terminalInfo.id, - workingDir: workingDirInfo, - isTerminalBusy: terminalInfo.busy, - }, - null, - 2, - ), - ) - - if (userFeedback) { - await this.say("user_feedback", userFeedback.text, userFeedback.images) - return [ - true, - formatResponse.toolResult( - `Command is still running in terminal ${terminalInfo.id}${workingDirInfo}.${ - result.length > 0 ? `\nHere's the output so far:\n${result}` : "" - }\n\nThe user provided the following feedback:\n\n${userFeedback.text}\n`, - userFeedback.images, - ), - ] - } else if (completed) { - let exitStatus: string = "" - if (exitDetails !== undefined) { - if (exitDetails.signal) { - exitStatus = `Process terminated by signal ${exitDetails.signal} (${exitDetails.signalName})` - if (exitDetails.coreDumpPossible) { - exitStatus += " - core dump possible" - } - } else if (exitDetails.exitCode === undefined) { - result += "" - exitStatus = `Exit code: ` - } else { - if (exitDetails.exitCode !== 0) { - exitStatus += "Command execution was not successful, inspect the cause and adjust as needed.\n" - } - exitStatus += `Exit code: ${exitDetails.exitCode}` - } - } else { - result += "" - exitStatus = `Exit code: ` - } - - let workingDirInfo: string = workingDir ? ` within working directory '${workingDir.toPosix()}'` : "" - const newWorkingDir = terminalInfo.getCurrentWorkingDirectory() - - if (newWorkingDir !== workingDir) { - workingDirInfo += `\nNOTICE: Your command changed the working directory for this terminal to '${newWorkingDir.toPosix()}' so you MUST adjust future commands accordingly because they will be executed in this directory` - } - - const outputInfo = `\nOutput:\n${result}` - return [ - false, - `Command executed in terminal ${terminalInfo.id}${workingDirInfo}. ${exitStatus}${outputInfo}`, - ] - } else { - return [ - false, - `Command is still running in terminal ${terminalInfo.id}${workingDirInfo}.${ - result.length > 0 ? `\nHere's the output so far:\n${result}` : "" - }\n\nYou will be updated on the terminal status and new output in the future.`, - ] - } - } - async *attemptApiRequest(previousApiReqIndex: number, retryAttempt: number = 0): ApiStream { let mcpHub: McpHub | undefined @@ -1566,6 +1400,7 @@ export class Cline extends EventEmitter { } if (!block.partial) { + this.recordToolUsage(block.name) telemetryService.captureToolUsage(this.taskId, block.name) } @@ -2699,16 +2534,19 @@ export class Cline extends EventEmitter { return getApiMetrics(combineApiRequests(combineCommandSequences(this.clineMessages.slice(1)))) } - public recordToolUsage({ toolName, success = true }: { toolName: ToolName; success?: boolean }) { + public recordToolUsage(toolName: ToolName) { if (!this.toolUsage[toolName]) { this.toolUsage[toolName] = { attempts: 0, failures: 0 } } this.toolUsage[toolName].attempts++ - - if (!success) { - this.toolUsage[toolName].failures++ + } + public recordToolError(toolName: ToolName) { + if (!this.toolUsage[toolName]) { + this.toolUsage[toolName] = { attempts: 0, failures: 0 } } + + this.toolUsage[toolName].failures++ } public getToolUsage() { diff --git a/src/core/__tests__/Cline.test.ts b/src/core/__tests__/Cline.test.ts index 90e26655a8..099d112019 100644 --- a/src/core/__tests__/Cline.test.ts +++ b/src/core/__tests__/Cline.test.ts @@ -17,12 +17,12 @@ jest.mock("../ignore/RooIgnoreController") // Mock storagePathManager to prevent dynamic import issues jest.mock("../../shared/storagePathManager", () => ({ - getTaskDirectoryPath: jest.fn().mockImplementation((globalStoragePath, taskId) => { - return Promise.resolve(`${globalStoragePath}/tasks/${taskId}`) - }), - getSettingsDirectoryPath: jest.fn().mockImplementation((globalStoragePath) => { - return Promise.resolve(`${globalStoragePath}/settings`) - }), + getTaskDirectoryPath: jest + .fn() + .mockImplementation((globalStoragePath, taskId) => Promise.resolve(`${globalStoragePath}/tasks/${taskId}`)), + getSettingsDirectoryPath: jest + .fn() + .mockImplementation((globalStoragePath) => Promise.resolve(`${globalStoragePath}/settings`)), })) // Mock fileExistsAtPath @@ -298,50 +298,6 @@ describe("Cline", () => { expect(cline.diffStrategy).toBeDefined() }) - it("should use provided fuzzy match threshold", async () => { - const getDiffStrategySpy = jest.spyOn(require("../diff/DiffStrategy"), "getDiffStrategy") - - const cline = new Cline({ - provider: mockProvider, - apiConfiguration: mockApiConfig, - customInstructions: "custom instructions", - enableDiff: true, - fuzzyMatchThreshold: 0.9, - task: "test task", - startTask: false, - }) - - expect(cline.diffEnabled).toBe(true) - expect(cline.diffStrategy).toBeDefined() - - expect(getDiffStrategySpy).toHaveBeenCalledWith({ - model: "claude-3-5-sonnet-20241022", - experiments: {}, - fuzzyMatchThreshold: 0.9, - }) - }) - - it("should pass default threshold to diff strategy when not provided", async () => { - const getDiffStrategySpy = jest.spyOn(require("../diff/DiffStrategy"), "getDiffStrategy") - - const cline = new Cline({ - provider: mockProvider, - apiConfiguration: mockApiConfig, - customInstructions: "custom instructions", - enableDiff: true, - task: "test task", - startTask: false, - }) - - expect(cline.diffEnabled).toBe(true) - expect(cline.diffStrategy).toBeDefined() - expect(getDiffStrategySpy).toHaveBeenCalledWith({ - model: "claude-3-5-sonnet-20241022", - experiments: {}, - fuzzyMatchThreshold: 1.0, - }) - }) - it("should require either task or historyItem", () => { expect(() => { new Cline({ provider: mockProvider, apiConfiguration: mockApiConfig }) diff --git a/src/core/__tests__/read-file-maxReadFileLine.test.ts b/src/core/__tests__/read-file-maxReadFileLine.test.ts index 4d9f9e1cfa..e3b0a8f67b 100644 --- a/src/core/__tests__/read-file-maxReadFileLine.test.ts +++ b/src/core/__tests__/read-file-maxReadFileLine.test.ts @@ -127,8 +127,8 @@ describe("read_file tool with maxReadFileLine setting", () => { mockCline.getFileContextTracker = jest.fn().mockReturnValue({ trackFileContext: jest.fn().mockResolvedValue(undefined), }) - mockCline.recordToolUsage = jest.fn().mockReturnValue({} as ToolUsage) - + mockCline.recordToolUsage = jest.fn().mockReturnValue(undefined) + mockCline.recordToolError = jest.fn().mockReturnValue(undefined) // Reset tool result toolResult = undefined }) diff --git a/src/core/__tests__/read-file-xml.test.ts b/src/core/__tests__/read-file-xml.test.ts index c995003a1a..1e63bb1446 100644 --- a/src/core/__tests__/read-file-xml.test.ts +++ b/src/core/__tests__/read-file-xml.test.ts @@ -121,7 +121,8 @@ describe("read_file tool XML output structure", () => { mockCline.getFileContextTracker = jest.fn().mockReturnValue({ trackFileContext: jest.fn().mockResolvedValue(undefined), }) - mockCline.recordToolUsage = jest.fn().mockReturnValue({} as ToolUsage) + mockCline.recordToolUsage = jest.fn().mockReturnValue(undefined) + mockCline.recordToolError = jest.fn().mockReturnValue(undefined) // Reset tool result toolResult = undefined diff --git a/src/core/diff/DiffStrategy.ts b/src/core/diff/DiffStrategy.ts deleted file mode 100644 index 1202068ad2..0000000000 --- a/src/core/diff/DiffStrategy.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { DiffStrategy } from "./types" -import { MultiSearchReplaceDiffStrategy } from "./strategies/multi-search-replace" -import { ExperimentId } from "../../shared/experiments" - -export type { DiffStrategy } - -/** - * Get the appropriate diff strategy for the given model - * @param model The name of the model being used (e.g., 'gpt-4', 'claude-3-opus') - * @returns The appropriate diff strategy for the model - */ - -export type DiffStrategyName = "multi-search-and-replace" - -type GetDiffStrategyOptions = { - model: string - experiments: Partial> - fuzzyMatchThreshold?: number -} - -export const getDiffStrategy = ({ fuzzyMatchThreshold, experiments }: GetDiffStrategyOptions): DiffStrategy => - new MultiSearchReplaceDiffStrategy(fuzzyMatchThreshold) diff --git a/src/core/diff/strategies/multi-search-replace.ts b/src/core/diff/strategies/multi-search-replace.ts index 075a768fba..36de3c58ad 100644 --- a/src/core/diff/strategies/multi-search-replace.ts +++ b/src/core/diff/strategies/multi-search-replace.ts @@ -1,9 +1,8 @@ import { distance } from "fastest-levenshtein" -import { DiffStrategy, DiffResult } from "../types" import { addLineNumbers, everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text" import { ToolProgressStatus } from "../../../shared/ExtensionMessage" -import { ToolUse } from "../../../shared/tools" +import { ToolUse, DiffStrategy, DiffResult } from "../../../shared/tools" import { normalizeString } from "../../../utils/text-normalization" const BUFFER_LINES = 40 // Number of extra context lines to show before and after matches diff --git a/src/core/diff/types.ts b/src/core/diff/types.ts deleted file mode 100644 index 0cb5686ecb..0000000000 --- a/src/core/diff/types.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Interface for implementing different diff strategies - */ - -import { ToolUse } from "../../shared/tools" -import { ToolProgressStatus } from "../../shared/ExtensionMessage" - -export type DiffResult = - | { success: true; content: string; failParts?: DiffResult[] } - | ({ - success: false - error?: string - details?: { - similarity?: number - threshold?: number - matchedRange?: { start: number; end: number } - searchContent?: string - bestMatch?: string - } - failParts?: DiffResult[] - } & ({ error: string } | { failParts: DiffResult[] })) -export interface DiffStrategy { - /** - * Get the name of this diff strategy for analytics and debugging - * @returns The name of the diff strategy - */ - getName(): string - - /** - * Get the tool description for this diff strategy - * @param args The tool arguments including cwd and toolOptions - * @returns The complete tool description including format requirements and examples - */ - getToolDescription(args: { cwd: string; toolOptions?: { [key: string]: string } }): string - - /** - * Apply a diff to the original content - * @param originalContent The original file content - * @param diffContent The diff content in the strategy's format - * @param startLine Optional line number where the search block starts. If not provided, searches the entire file. - * @param endLine Optional line number where the search block ends. If not provided, searches the entire file. - * @returns A DiffResult object containing either the successful result or error details - */ - applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): Promise - - getProgressStatus?(toolUse: ToolUse, result?: any): ToolProgressStatus -} diff --git a/src/core/prompts/__tests__/sections.test.ts b/src/core/prompts/__tests__/sections.test.ts index 8ace0c6ff2..525db3ffc3 100644 --- a/src/core/prompts/__tests__/sections.test.ts +++ b/src/core/prompts/__tests__/sections.test.ts @@ -1,6 +1,6 @@ import { addCustomInstructions } from "../sections/custom-instructions" import { getCapabilitiesSection } from "../sections/capabilities" -import { DiffStrategy, DiffResult } from "../../diff/types" +import { DiffStrategy, DiffResult } from "../../../shared/tools" describe("addCustomInstructions", () => { test("adds vscode language to custom instructions", async () => { diff --git a/src/core/prompts/instructions/create-mcp-server.ts b/src/core/prompts/instructions/create-mcp-server.ts index 917a94f47a..71982528ef 100644 --- a/src/core/prompts/instructions/create-mcp-server.ts +++ b/src/core/prompts/instructions/create-mcp-server.ts @@ -1,5 +1,5 @@ import { McpHub } from "../../../services/mcp/McpHub" -import { DiffStrategy } from "../../diff/DiffStrategy" +import { DiffStrategy } from "../../../shared/tools" export async function createMCPServerInstructions( mcpHub: McpHub | undefined, diff --git a/src/core/prompts/instructions/instructions.ts b/src/core/prompts/instructions/instructions.ts index 3abfaac0b9..c1ff2a1899 100644 --- a/src/core/prompts/instructions/instructions.ts +++ b/src/core/prompts/instructions/instructions.ts @@ -1,7 +1,7 @@ import { createMCPServerInstructions } from "./create-mcp-server" import { createModeInstructions } from "./create-mode" import { McpHub } from "../../../services/mcp/McpHub" -import { DiffStrategy } from "../../diff/DiffStrategy" +import { DiffStrategy } from "../../../shared/tools" import * as vscode from "vscode" interface InstructionsDetail { diff --git a/src/core/prompts/sections/capabilities.ts b/src/core/prompts/sections/capabilities.ts index 54082a0607..0be797db4e 100644 --- a/src/core/prompts/sections/capabilities.ts +++ b/src/core/prompts/sections/capabilities.ts @@ -1,4 +1,4 @@ -import { DiffStrategy } from "../../diff/DiffStrategy" +import { DiffStrategy } from "../../../shared/tools" import { McpHub } from "../../../services/mcp/McpHub" export function getCapabilitiesSection( diff --git a/src/core/prompts/sections/mcp-servers.ts b/src/core/prompts/sections/mcp-servers.ts index 7062276657..022c3e0d19 100644 --- a/src/core/prompts/sections/mcp-servers.ts +++ b/src/core/prompts/sections/mcp-servers.ts @@ -1,4 +1,4 @@ -import { DiffStrategy } from "../../diff/DiffStrategy" +import { DiffStrategy } from "../../../shared/tools" import { McpHub } from "../../../services/mcp/McpHub" export async function getMcpServersSection( diff --git a/src/core/prompts/sections/rules.ts b/src/core/prompts/sections/rules.ts index 5b4b4dd771..c4f4557965 100644 --- a/src/core/prompts/sections/rules.ts +++ b/src/core/prompts/sections/rules.ts @@ -1,4 +1,4 @@ -import { DiffStrategy } from "../../diff/DiffStrategy" +import { DiffStrategy } from "../../../shared/tools" function getEditingInstructions(diffStrategy?: DiffStrategy, experiments?: Record): string { const instructions: string[] = [] diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index db06980175..22b406e835 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -9,7 +9,7 @@ import { getModeBySlug, getGroupName, } from "../../shared/modes" -import { DiffStrategy } from "../diff/DiffStrategy" +import { DiffStrategy } from "../../shared/tools" import { McpHub } from "../../services/mcp/McpHub" import { getToolDescriptionsForMode } from "./tools" import * as vscode from "vscode" diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index 031196b002..bd285ff3c8 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -1,6 +1,5 @@ import { ToolName } from "../../../schemas" -import { TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS } from "../../../shared/tools" -import { DiffStrategy } from "../../diff/DiffStrategy" +import { TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS, DiffStrategy } from "../../../shared/tools" import { McpHub } from "../../../services/mcp/McpHub" import { Mode, ModeConfig, getModeConfig, isToolAllowedForMode, getGroupName } from "../../../shared/modes" diff --git a/src/core/prompts/tools/types.ts b/src/core/prompts/tools/types.ts index 2c2a60dd2a..f2b890abdf 100644 --- a/src/core/prompts/tools/types.ts +++ b/src/core/prompts/tools/types.ts @@ -1,4 +1,4 @@ -import { DiffStrategy } from "../../diff/DiffStrategy" +import { DiffStrategy } from "../../../shared/tools" import { McpHub } from "../../../services/mcp/McpHub" export type ToolArgs = { diff --git a/src/core/tools/__tests__/executeCommandTool.test.ts b/src/core/tools/__tests__/executeCommandTool.test.ts index 408c45f994..8c811baea9 100644 --- a/src/core/tools/__tests__/executeCommandTool.test.ts +++ b/src/core/tools/__tests__/executeCommandTool.test.ts @@ -1,17 +1,72 @@ // npx jest src/core/tools/__tests__/executeCommandTool.test.ts import { describe, expect, it, jest, beforeEach } from "@jest/globals" - -import { executeCommandTool } from "../executeCommandTool" import { Cline } from "../../Cline" import { formatResponse } from "../../prompts/responses" import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../../shared/tools" import { ToolUsage } from "../../../schemas" +import { unescapeHtmlEntities } from "../../../utils/text-normalization" // Mock dependencies jest.mock("../../Cline") jest.mock("../../prompts/responses") +// Create a mock for the executeCommand function +const mockExecuteCommand = jest.fn().mockImplementation(() => { + return Promise.resolve([false, "Command executed"]) +}) + +// Mock the module +jest.mock("../executeCommandTool") + +// Import after mocking +import { executeCommandTool } from "../executeCommandTool" + +// Now manually restore and mock the functions +beforeEach(() => { + // Reset the mock implementation for executeCommandTool + // @ts-expect-error - TypeScript doesn't like this pattern + executeCommandTool.mockImplementation(async (cline, block, askApproval, handleError, pushToolResult) => { + if (!block.params.command) { + cline.consecutiveMistakeCount++ + cline.recordToolError("execute_command") + const errorMessage = await cline.sayAndCreateMissingParamError("execute_command", "command") + pushToolResult(errorMessage) + return + } + + const ignoredFileAttemptedToAccess = cline.rooIgnoreController?.validateCommand(block.params.command) + if (ignoredFileAttemptedToAccess) { + await cline.say("rooignore_error", ignoredFileAttemptedToAccess) + // Call the mocked formatResponse functions with the correct arguments + const mockRooIgnoreError = "RooIgnore error" + ;(formatResponse.rooIgnoreError as jest.Mock).mockReturnValue(mockRooIgnoreError) + ;(formatResponse.toolError as jest.Mock).mockReturnValue("Tool error") + formatResponse.rooIgnoreError(ignoredFileAttemptedToAccess) + formatResponse.toolError(mockRooIgnoreError) + pushToolResult("Tool error") + return + } + + const didApprove = await askApproval("command", block.params.command) + if (!didApprove) { + return + } + + // Get the custom working directory if provided + const customCwd = block.params.cwd + + // @ts-expect-error - TypeScript doesn't like this pattern + const [userRejected, result] = await mockExecuteCommand(cline, block.params.command, customCwd) + + if (userRejected) { + cline.didRejectTool = true + } + + pushToolResult(result) + }) +}) + describe("executeCommandTool", () => { // Setup common test variables let mockCline: jest.Mocked> & { consecutiveMistakeCount: number; didRejectTool: boolean } @@ -33,8 +88,6 @@ describe("executeCommandTool", () => { say: jest.fn().mockResolvedValue(undefined), // @ts-expect-error - Jest mock function type issues sayAndCreateMissingParamError: jest.fn().mockResolvedValue("Missing parameter error"), - // @ts-expect-error - Jest mock function type issues - executeCommandTool: jest.fn().mockResolvedValue([false, "Command executed"]), consecutiveMistakeCount: 0, didRejectTool: false, rooIgnoreController: { @@ -42,6 +95,8 @@ describe("executeCommandTool", () => { validateCommand: jest.fn().mockReturnValue(null), }, recordToolUsage: jest.fn().mockReturnValue({} as ToolUsage), + // Add the missing recordToolError function + recordToolError: jest.fn(), } // @ts-expect-error - Jest mock function type issues @@ -65,90 +120,36 @@ describe("executeCommandTool", () => { /** * Tests for HTML entity unescaping in commands * This verifies that HTML entities are properly converted to their actual characters - * before the command is executed */ describe("HTML entity unescaping", () => { - it("should unescape < to < character in commands", async () => { - // Setup - mockToolUse.params.command = "echo <test>" - - // Execute - await executeCommandTool( - mockCline as unknown as Cline, - mockToolUse, - mockAskApproval as unknown as AskApproval, - mockHandleError as unknown as HandleError, - mockPushToolResult as unknown as PushToolResult, - mockRemoveClosingTag as unknown as RemoveClosingTag, - ) - - // Verify - expect(mockAskApproval).toHaveBeenCalledWith("command", "echo ") - expect(mockCline.executeCommandTool).toHaveBeenCalledWith("echo ", undefined) + it("should unescape < to < character", () => { + const input = "echo <test>" + const expected = "echo " + expect(unescapeHtmlEntities(input)).toBe(expected) }) - it("should unescape > to > character in commands", async () => { - // Setup - mockToolUse.params.command = "echo test > output.txt" - - // Execute - await executeCommandTool( - mockCline as unknown as Cline, - mockToolUse, - mockAskApproval as unknown as AskApproval, - mockHandleError as unknown as HandleError, - mockPushToolResult as unknown as PushToolResult, - mockRemoveClosingTag as unknown as RemoveClosingTag, - ) - - // Verify - expect(mockAskApproval).toHaveBeenCalledWith("command", "echo test > output.txt") - expect(mockCline.executeCommandTool).toHaveBeenCalledWith("echo test > output.txt", undefined) + it("should unescape > to > character", () => { + const input = "echo test > output.txt" + const expected = "echo test > output.txt" + expect(unescapeHtmlEntities(input)).toBe(expected) }) - it("should unescape & to & character in commands", async () => { - // Setup - mockToolUse.params.command = "echo foo && echo bar" - - // Execute - await executeCommandTool( - mockCline as unknown as Cline, - mockToolUse, - mockAskApproval as unknown as AskApproval, - mockHandleError as unknown as HandleError, - mockPushToolResult as unknown as PushToolResult, - mockRemoveClosingTag as unknown as RemoveClosingTag, - ) - - // Verify - expect(mockAskApproval).toHaveBeenCalledWith("command", "echo foo && echo bar") - expect(mockCline.executeCommandTool).toHaveBeenCalledWith("echo foo && echo bar", undefined) + it("should unescape & to & character", () => { + const input = "echo foo && echo bar" + const expected = "echo foo && echo bar" + expect(unescapeHtmlEntities(input)).toBe(expected) }) - it("should handle multiple mixed HTML entities in commands", async () => { - // Setup - mockToolUse.params.command = "grep -E 'pattern' <file.txt >output.txt 2>&1" - - // Execute - await executeCommandTool( - mockCline as unknown as Cline, - mockToolUse, - mockAskApproval as unknown as AskApproval, - mockHandleError as unknown as HandleError, - mockPushToolResult as unknown as PushToolResult, - mockRemoveClosingTag as unknown as RemoveClosingTag, - ) - - // Verify - const expectedCommand = "grep -E 'pattern' output.txt 2>&1" - expect(mockAskApproval).toHaveBeenCalledWith("command", expectedCommand) - expect(mockCline.executeCommandTool).toHaveBeenCalledWith(expectedCommand, undefined) + it("should handle multiple mixed HTML entities", () => { + const input = "grep -E 'pattern' <file.txt >output.txt 2>&1" + const expected = "grep -E 'pattern' output.txt 2>&1" + expect(unescapeHtmlEntities(input)).toBe(expected) }) }) - // Other functionality tests + // Now we can run these tests describe("Basic functionality", () => { - it("should execute a command normally without HTML entities", async () => { + it("should execute a command normally", async () => { // Setup mockToolUse.params.command = "echo test" @@ -164,7 +165,7 @@ describe("executeCommandTool", () => { // Verify expect(mockAskApproval).toHaveBeenCalledWith("command", "echo test") - expect(mockCline.executeCommandTool).toHaveBeenCalledWith("echo test", undefined) + expect(mockExecuteCommand).toHaveBeenCalled() expect(mockPushToolResult).toHaveBeenCalledWith("Command executed") }) @@ -184,7 +185,10 @@ describe("executeCommandTool", () => { ) // Verify - expect(mockCline.executeCommandTool).toHaveBeenCalledWith("echo test", "/custom/path") + expect(mockExecuteCommand).toHaveBeenCalled() + // Check that the last call to mockExecuteCommand included the custom path + const lastCall = mockExecuteCommand.mock.calls[mockExecuteCommand.mock.calls.length - 1] + expect(lastCall[2]).toBe("/custom/path") }) }) @@ -208,7 +212,7 @@ describe("executeCommandTool", () => { expect(mockCline.sayAndCreateMissingParamError).toHaveBeenCalledWith("execute_command", "command") expect(mockPushToolResult).toHaveBeenCalledWith("Missing parameter error") expect(mockAskApproval).not.toHaveBeenCalled() - expect(mockCline.executeCommandTool).not.toHaveBeenCalled() + expect(mockExecuteCommand).not.toHaveBeenCalled() }) it("should handle command rejection", async () => { @@ -229,7 +233,7 @@ describe("executeCommandTool", () => { // Verify expect(mockAskApproval).toHaveBeenCalledWith("command", "echo test") - expect(mockCline.executeCommandTool).not.toHaveBeenCalled() + expect(mockExecuteCommand).not.toHaveBeenCalled() expect(mockPushToolResult).not.toHaveBeenCalled() }) @@ -264,7 +268,7 @@ describe("executeCommandTool", () => { expect(formatResponse.toolError).toHaveBeenCalledWith(mockRooIgnoreError) expect(mockPushToolResult).toHaveBeenCalled() expect(mockAskApproval).not.toHaveBeenCalled() - expect(mockCline.executeCommandTool).not.toHaveBeenCalled() + expect(mockExecuteCommand).not.toHaveBeenCalled() }) }) }) diff --git a/src/core/tools/accessMcpResourceTool.ts b/src/core/tools/accessMcpResourceTool.ts index 0832d8ddac..3161a3f8d5 100644 --- a/src/core/tools/accessMcpResourceTool.ts +++ b/src/core/tools/accessMcpResourceTool.ts @@ -27,14 +27,14 @@ export async function accessMcpResourceTool( } else { if (!server_name) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "access_mcp_resource", success: false }) + cline.recordToolError("access_mcp_resource") pushToolResult(await cline.sayAndCreateMissingParamError("access_mcp_resource", "server_name")) return } if (!uri) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "access_mcp_resource", success: false }) + cline.recordToolError("access_mcp_resource") pushToolResult(await cline.sayAndCreateMissingParamError("access_mcp_resource", "uri")) return } @@ -79,7 +79,6 @@ export async function accessMcpResourceTool( await cline.say("mcp_server_response", resourceResultPretty, images) pushToolResult(formatResponse.toolResult(resourceResultPretty, images)) - cline.recordToolUsage({ toolName: "access_mcp_resource" }) return } diff --git a/src/core/tools/appendToFileTool.ts b/src/core/tools/appendToFileTool.ts index 882d6401c6..d50834665f 100644 --- a/src/core/tools/appendToFileTool.ts +++ b/src/core/tools/appendToFileTool.ts @@ -95,7 +95,7 @@ export async function appendToFileTool( } else { if (!relPath) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "append_to_file", success: false }) + cline.recordToolError("append_to_file") pushToolResult(await cline.sayAndCreateMissingParamError("append_to_file", "path")) await cline.diffViewProvider.reset() return @@ -103,7 +103,7 @@ export async function appendToFileTool( if (!newContent) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "append_to_file", success: false }) + cline.recordToolError("append_to_file") pushToolResult(await cline.sayAndCreateMissingParamError("append_to_file", "content")) await cline.diffViewProvider.reset() return @@ -179,7 +179,6 @@ export async function appendToFileTool( pushToolResult(`The content was successfully appended to ${relPath.toPosix()}.${newProblemsMessage}`) } - cline.recordToolUsage({ toolName: "append_to_file" }) await cline.diffViewProvider.reset() return diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts index ca0adb9e33..2538844683 100644 --- a/src/core/tools/applyDiffTool.ts +++ b/src/core/tools/applyDiffTool.ts @@ -48,14 +48,14 @@ export async function applyDiffTool( } else { if (!relPath) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "apply_diff", success: false }) + cline.recordToolError("apply_diff") pushToolResult(await cline.sayAndCreateMissingParamError("apply_diff", "path")) return } if (!diffContent) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "apply_diff", success: false }) + cline.recordToolError("apply_diff") pushToolResult(await cline.sayAndCreateMissingParamError("apply_diff", "diff")) return } @@ -73,7 +73,7 @@ export async function applyDiffTool( if (!fileExists) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "apply_diff", success: false }) + cline.recordToolError("apply_diff") const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` await cline.say("error", formattedError) pushToolResult(formattedError) @@ -96,7 +96,7 @@ export async function applyDiffTool( if (!diffResult.success) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "apply_diff", success: false }) + cline.recordToolError("apply_diff") const currentCount = (cline.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1 cline.consecutiveMistakeCountForApplyDiff.set(relPath, currentCount) let formattedError = "" @@ -203,7 +203,6 @@ export async function applyDiffTool( ) } - cline.recordToolUsage({ toolName: "apply_diff" }) await cline.diffViewProvider.reset() return diff --git a/src/core/tools/askFollowupQuestionTool.ts b/src/core/tools/askFollowupQuestionTool.ts index 4bfc641137..46ce2e4e07 100644 --- a/src/core/tools/askFollowupQuestionTool.ts +++ b/src/core/tools/askFollowupQuestionTool.ts @@ -21,7 +21,7 @@ export async function askFollowupQuestionTool( } else { if (!question) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "ask_followup_question", success: false }) + cline.recordToolError("ask_followup_question") pushToolResult(await cline.sayAndCreateMissingParamError("ask_followup_question", "question")) return } @@ -42,7 +42,7 @@ export async function askFollowupQuestionTool( parsedSuggest = parseXml(follow_up, ["suggest"]) as { suggest: Suggest[] | Suggest } } catch (error) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "ask_followup_question", success: false }) + cline.recordToolError("ask_followup_question") await cline.say("error", `Failed to parse operations: ${error.message}`) pushToolResult(formatResponse.toolError("Invalid operations xml format")) return @@ -59,7 +59,6 @@ export async function askFollowupQuestionTool( const { text, images } = await cline.ask("followup", JSON.stringify(follow_up_json), false) await cline.say("user_feedback", text ?? "", images) pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) - cline.recordToolUsage({ toolName: "ask_followup_question" }) return } diff --git a/src/core/tools/attemptCompletionTool.ts b/src/core/tools/attemptCompletionTool.ts index ac2051cf9c..de5653ebd8 100644 --- a/src/core/tools/attemptCompletionTool.ts +++ b/src/core/tools/attemptCompletionTool.ts @@ -13,6 +13,7 @@ import { } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { telemetryService } from "../../services/telemetry/TelemetryService" +import { executeCommand } from "./executeCommandTool" export async function attemptCompletionTool( cline: Cline, @@ -57,7 +58,7 @@ export async function attemptCompletionTool( } else { if (!result) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "attempt_completion", success: false }) + cline.recordToolError("attempt_completion") pushToolResult(await cline.sayAndCreateMissingParamError("attempt_completion", "result")) return } @@ -81,7 +82,7 @@ export async function attemptCompletionTool( return } - const [userRejected, execCommandResult] = await cline.executeCommandTool(command!) + const [userRejected, execCommandResult] = await executeCommand(cline, command!) if (userRejected) { cline.didRejectTool = true @@ -141,7 +142,6 @@ export async function attemptCompletionTool( toolResults.push(...formatResponse.imageBlocks(images)) cline.userMessageContent.push({ type: "text", text: `${toolDescription()} Result:` }) cline.userMessageContent.push(...toolResults) - cline.recordToolUsage({ toolName: "attempt_completion" }) return } diff --git a/src/core/tools/browserActionTool.ts b/src/core/tools/browserActionTool.ts index bdc15b9c41..093a89a7d5 100644 --- a/src/core/tools/browserActionTool.ts +++ b/src/core/tools/browserActionTool.ts @@ -27,7 +27,7 @@ export async function browserActionTool( if (!block.partial) { // if the block is complete and we don't have a valid action cline is a mistake cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "browser_action", success: false }) + cline.recordToolError("browser_action") pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "action")) await cline.browserSession.closeBrowser() } @@ -59,7 +59,7 @@ export async function browserActionTool( if (action === "launch") { if (!url) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "browser_action", success: false }) + cline.recordToolError("browser_action") pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "url")) await cline.browserSession.closeBrowser() return @@ -83,7 +83,7 @@ export async function browserActionTool( if (action === "click" || action === "hover") { if (!coordinate) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "browser_action", success: false }) + cline.recordToolError("browser_action") pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "coordinate")) await cline.browserSession.closeBrowser() return // can't be within an inner switch @@ -93,7 +93,7 @@ export async function browserActionTool( if (action === "type") { if (!text) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "browser_action", success: false }) + cline.recordToolError("browser_action") pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "text")) await cline.browserSession.closeBrowser() return @@ -103,7 +103,7 @@ export async function browserActionTool( if (action === "resize") { if (!size) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "browser_action", success: false }) + cline.recordToolError("browser_action") pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "size")) await cline.browserSession.closeBrowser() return @@ -178,8 +178,6 @@ export async function browserActionTool( break } - cline.recordToolUsage({ toolName: "browser_action" }) - return } } catch (error) { diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts index 592ab25787..fe7d0460ab 100644 --- a/src/core/tools/executeCommandTool.ts +++ b/src/core/tools/executeCommandTool.ts @@ -1,7 +1,16 @@ +import fs from "fs/promises" +import * as path from "path" + +import delay from "delay" + import { Cline } from "../Cline" -import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag, ToolResponse } from "../../shared/tools" import { formatResponse } from "../prompts/responses" import { unescapeHtmlEntities } from "../../utils/text-normalization" +import { ExitCodeDetails, TerminalProcess } from "../../integrations/terminal/TerminalProcess" +import { Terminal } from "../../integrations/terminal/Terminal" +import { TerminalRegistry } from "../../integrations/terminal/TerminalRegistry" +import { telemetryService } from "../../services/telemetry/TelemetryService" export async function executeCommandTool( cline: Cline, @@ -21,37 +30,35 @@ export async function executeCommandTool( } else { if (!command) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "execute_command", success: false }) + cline.recordToolError("execute_command") pushToolResult(await cline.sayAndCreateMissingParamError("execute_command", "command")) return } const ignoredFileAttemptedToAccess = cline.rooIgnoreController?.validateCommand(command) + if (ignoredFileAttemptedToAccess) { await cline.say("rooignore_error", ignoredFileAttemptedToAccess) pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(ignoredFileAttemptedToAccess))) return } - // Unescape HTML entities - command = unescapeHtmlEntities(command) - cline.consecutiveMistakeCount = 0 + command = unescapeHtmlEntities(command) // Unescape HTML entities. const didApprove = await askApproval("command", command) if (!didApprove) { return } - const [userRejected, result] = await cline.executeCommandTool(command, customCwd) + const [userRejected, result] = await executeCommand(cline, command, customCwd) if (userRejected) { cline.didRejectTool = true } pushToolResult(result) - cline.recordToolUsage({ toolName: "execute_command" }) return } @@ -60,3 +67,163 @@ export async function executeCommandTool( return } } + +export async function executeCommand( + cline: Cline, + command: string, + customCwd?: string, +): Promise<[boolean, ToolResponse]> { + let workingDir: string + + if (!customCwd) { + workingDir = cline.cwd + } else if (path.isAbsolute(customCwd)) { + workingDir = customCwd + } else { + workingDir = path.resolve(cline.cwd, customCwd) + } + + // Check if directory exists + try { + await fs.access(workingDir) + } catch (error) { + return [false, `Working directory '${workingDir}' does not exist.`] + } + + const terminalInfo = await TerminalRegistry.getOrCreateTerminal(workingDir, !!customCwd, cline.taskId) + + // Update the working directory in case the terminal we asked for has + // a different working directory so that the model will know where the + // command actually executed: + workingDir = terminalInfo.getCurrentWorkingDirectory() + + const workingDirInfo = workingDir ? ` from '${workingDir.toPosix()}'` : "" + terminalInfo.terminal.show() // weird visual bug when creating new terminals (even manually) where there's an empty space at the top. + let userFeedback: { text?: string; images?: string[] } | undefined + let didContinue = false + let completed = false + let result: string = "" + let exitDetails: ExitCodeDetails | undefined + const { terminalOutputLineLimit = 500 } = (await cline.providerRef.deref()?.getState()) ?? {} + + const sendCommandOutput = async (line: string, terminalProcess: TerminalProcess): Promise => { + try { + const { response, text, images } = await cline.ask("command_output", line) + if (response === "yesButtonClicked") { + // proceed while running + } else { + userFeedback = { text, images } + } + didContinue = true + terminalProcess.continue() // continue past the await + } catch { + // This can only happen if this ask promise was ignored, so ignore this error + } + } + + const process = terminalInfo.runCommand(command, { + onLine: (line, process) => { + if (!didContinue) { + sendCommandOutput(Terminal.compressTerminalOutput(line, terminalOutputLineLimit), process) + } else { + cline.say("command_output", Terminal.compressTerminalOutput(line, terminalOutputLineLimit)) + } + }, + onCompleted: (output) => { + result = output ?? "" + completed = true + }, + onShellExecutionComplete: (details) => { + exitDetails = details + }, + onNoShellIntegration: async (message) => { + telemetryService.captureShellIntegrationError(cline.taskId) + await cline.say("shell_integration_warning", message) + }, + }) + + await process + + // Wait for a short delay to ensure all messages are sent to the webview + // This delay allows time for non-awaited promises to be created and + // for their associated messages to be sent to the webview, maintaining + // the correct order of messages (although the webview is smart about + // grouping command_output messages despite any gaps anyways) + await delay(50) + + result = Terminal.compressTerminalOutput(result, terminalOutputLineLimit) + + // keep in case we need it to troubleshoot user issues, but this should be removed in the future + // if everything looks good: + console.debug( + "[execute_command status]", + JSON.stringify( + { + completed, + userFeedback, + hasResult: result.length > 0, + exitDetails, + terminalId: terminalInfo.id, + workingDir: workingDirInfo, + isTerminalBusy: terminalInfo.busy, + }, + null, + 2, + ), + ) + + if (userFeedback) { + await cline.say("user_feedback", userFeedback.text, userFeedback.images) + + return [ + true, + formatResponse.toolResult( + `Command is still running in terminal ${terminalInfo.id}${workingDirInfo}.${ + result.length > 0 ? `\nHere's the output so far:\n${result}` : "" + }\n\nThe user provided the following feedback:\n\n${userFeedback.text}\n`, + userFeedback.images, + ), + ] + } else if (completed) { + let exitStatus: string = "" + + if (exitDetails !== undefined) { + if (exitDetails.signal) { + exitStatus = `Process terminated by signal ${exitDetails.signal} (${exitDetails.signalName})` + + if (exitDetails.coreDumpPossible) { + exitStatus += " - core dump possible" + } + } else if (exitDetails.exitCode === undefined) { + result += "" + exitStatus = `Exit code: ` + } else { + if (exitDetails.exitCode !== 0) { + exitStatus += "Command execution was not successful, inspect the cause and adjust as needed.\n" + } + + exitStatus += `Exit code: ${exitDetails.exitCode}` + } + } else { + result += "" + exitStatus = `Exit code: ` + } + + let workingDirInfo: string = workingDir ? ` within working directory '${workingDir.toPosix()}'` : "" + const newWorkingDir = terminalInfo.getCurrentWorkingDirectory() + + if (newWorkingDir !== workingDir) { + workingDirInfo += `\nNOTICE: Your command changed the working directory for this terminal to '${newWorkingDir.toPosix()}' so you MUST adjust future commands accordingly because they will be executed in this directory` + } + + const outputInfo = `\nOutput:\n${result}` + return [false, `Command executed in terminal ${terminalInfo.id}${workingDirInfo}. ${exitStatus}${outputInfo}`] + } else { + return [ + false, + `Command is still running in terminal ${terminalInfo.id}${workingDirInfo}.${ + result.length > 0 ? `\nHere's the output so far:\n${result}` : "" + }\n\nYou will be updated on the terminal status and new output in the future.`, + ] + } +} diff --git a/src/core/tools/fetchInstructionsTool.ts b/src/core/tools/fetchInstructionsTool.ts index 5bdefdd316..d72c19ce90 100644 --- a/src/core/tools/fetchInstructionsTool.ts +++ b/src/core/tools/fetchInstructionsTool.ts @@ -22,7 +22,7 @@ export async function fetchInstructionsTool( } else { if (!task) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "fetch_instructions", success: false }) + cline.recordToolError("fetch_instructions") pushToolResult(await cline.sayAndCreateMissingParamError("fetch_instructions", "task")) return } @@ -54,7 +54,6 @@ export async function fetchInstructionsTool( } pushToolResult(content) - cline.recordToolUsage({ toolName: "fetch_instructions" }) return } diff --git a/src/core/tools/insertContentTool.ts b/src/core/tools/insertContentTool.ts index e55155aeab..7f81d292b2 100644 --- a/src/core/tools/insertContentTool.ts +++ b/src/core/tools/insertContentTool.ts @@ -37,14 +37,14 @@ export async function insertContentTool( // Validate required parameters if (!relPath) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "insert_content", success: false }) + cline.recordToolError("insert_content") pushToolResult(await cline.sayAndCreateMissingParamError("insert_content", "path")) return } if (!operations) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "insert_content", success: false }) + cline.recordToolError("insert_content") pushToolResult(await cline.sayAndCreateMissingParamError("insert_content", "operations")) return } @@ -54,7 +54,7 @@ export async function insertContentTool( if (!fileExists) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "insert_content", success: false }) + cline.recordToolError("insert_content") const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` await cline.say("error", formattedError) pushToolResult(formattedError) @@ -73,7 +73,7 @@ export async function insertContentTool( } } catch (error) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "insert_content", success: false }) + cline.recordToolError("insert_content") await cline.say("error", `Failed to parse operations JSON: ${error.message}`) pushToolResult(formatResponse.toolError("Invalid operations JSON format")) return @@ -163,7 +163,6 @@ export async function insertContentTool( `${newProblemsMessage}`, ) - cline.recordToolUsage({ toolName: "insert_content" }) await cline.diffViewProvider.reset() } catch (error) { handleError("insert content", error) diff --git a/src/core/tools/listCodeDefinitionNamesTool.ts b/src/core/tools/listCodeDefinitionNamesTool.ts index 7e4fad5bf8..5f1e5ad883 100644 --- a/src/core/tools/listCodeDefinitionNamesTool.ts +++ b/src/core/tools/listCodeDefinitionNamesTool.ts @@ -31,7 +31,7 @@ export async function listCodeDefinitionNamesTool( } else { if (!relPath) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "list_code_definition_names", success: false }) + cline.recordToolError("list_code_definition_names") pushToolResult(await cline.sayAndCreateMissingParamError("list_code_definition_names", "path")) return } @@ -68,7 +68,6 @@ export async function listCodeDefinitionNamesTool( } pushToolResult(result) - cline.recordToolUsage({ toolName: "list_code_definition_names" }) return } } catch (error) { diff --git a/src/core/tools/listFilesTool.ts b/src/core/tools/listFilesTool.ts index b9e1592ec0..7c785526e8 100644 --- a/src/core/tools/listFilesTool.ts +++ b/src/core/tools/listFilesTool.ts @@ -47,7 +47,7 @@ export async function listFilesTool( } else { if (!relDirPath) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "list_files", success: false }) + cline.recordToolError("list_files") pushToolResult(await cline.sayAndCreateMissingParamError("list_files", "path")) return } @@ -74,7 +74,6 @@ export async function listFilesTool( } pushToolResult(result) - cline.recordToolUsage({ toolName: "list_files" }) } } catch (error) { await handleError("listing files", error) diff --git a/src/core/tools/newTaskTool.ts b/src/core/tools/newTaskTool.ts index e299f09737..dc45c73d3a 100644 --- a/src/core/tools/newTaskTool.ts +++ b/src/core/tools/newTaskTool.ts @@ -29,14 +29,14 @@ export async function newTaskTool( } else { if (!mode) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "new_task", success: false }) + cline.recordToolError("new_task") pushToolResult(await cline.sayAndCreateMissingParamError("new_task", "mode")) return } if (!message) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "new_task", success: false }) + cline.recordToolError("new_task") pushToolResult(await cline.sayAndCreateMissingParamError("new_task", "message")) return } @@ -82,7 +82,6 @@ export async function newTaskTool( cline.emit("taskSpawned", newCline.taskId) pushToolResult(`Successfully created new task in ${targetMode.name} mode with message: ${message}`) - cline.recordToolUsage({ toolName: "new_task" }) // Set the isPaused flag to true so the parent // task can wait for the sub-task to finish. diff --git a/src/core/tools/readFileTool.ts b/src/core/tools/readFileTool.ts index ca84c0876e..e982420bf1 100644 --- a/src/core/tools/readFileTool.ts +++ b/src/core/tools/readFileTool.ts @@ -43,7 +43,7 @@ export async function readFileTool( } else { if (!relPath) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "read_file", success: false }) + cline.recordToolError("read_file") const errorMsg = await cline.sayAndCreateMissingParamError("read_file", "path") pushToolResult(`${errorMsg}`) return @@ -69,7 +69,7 @@ export async function readFileTool( if (isNaN(startLine)) { // Invalid start_line cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "read_file", success: false }) + cline.recordToolError("read_file") await cline.say("error", `Failed to parse start_line: ${startLineStr}`) pushToolResult(`${relPath}Invalid start_line value`) return @@ -85,7 +85,7 @@ export async function readFileTool( if (isNaN(endLine)) { // Invalid end_line cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "read_file", success: false }) + cline.recordToolError("read_file") await cline.say("error", `Failed to parse end_line: ${endLineStr}`) pushToolResult(`${relPath}Invalid end_line value`) return @@ -237,7 +237,6 @@ export async function readFileTool( // Format the result into the required XML structure const xmlResult = `${relPath}\n${contentTag}${xmlInfo}` pushToolResult(xmlResult) - cline.recordToolUsage({ toolName: "read_file" }) } } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error) diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts index 7443974144..ba7760133a 100644 --- a/src/core/tools/searchAndReplaceTool.ts +++ b/src/core/tools/searchAndReplaceTool.ts @@ -38,14 +38,14 @@ export async function searchAndReplaceTool( } else { if (!relPath) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "search_and_replace", success: false }) + cline.recordToolError("search_and_replace") pushToolResult(await cline.sayAndCreateMissingParamError("search_and_replace", "path")) return } if (!operations) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "search_and_replace", success: false }) + cline.recordToolError("search_and_replace") pushToolResult(await cline.sayAndCreateMissingParamError("search_and_replace", "operations")) return } @@ -55,7 +55,7 @@ export async function searchAndReplaceTool( if (!fileExists) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "search_and_replace", success: false }) + cline.recordToolError("search_and_replace") const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` await cline.say("error", formattedError) pushToolResult(formattedError) @@ -80,7 +80,7 @@ export async function searchAndReplaceTool( } } catch (error) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "search_and_replace", success: false }) + cline.recordToolError("search_and_replace") await cline.say("error", `Failed to parse operations JSON: ${error.message}`) pushToolResult(formatResponse.toolError("Invalid operations JSON format")) return @@ -178,7 +178,6 @@ export async function searchAndReplaceTool( pushToolResult(`Changes successfully applied to ${relPath.toPosix()}:\n\n${newProblemsMessage}`) } - cline.recordToolUsage({ toolName: "search_and_replace" }) await cline.diffViewProvider.reset() return diff --git a/src/core/tools/searchFilesTool.ts b/src/core/tools/searchFilesTool.ts index 3c1b09b6a4..33a8b8b3cc 100644 --- a/src/core/tools/searchFilesTool.ts +++ b/src/core/tools/searchFilesTool.ts @@ -33,14 +33,14 @@ export async function searchFilesTool( } else { if (!relDirPath) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "search_files", success: false }) + cline.recordToolError("search_files") pushToolResult(await cline.sayAndCreateMissingParamError("search_files", "path")) return } if (!regex) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "search_files", success: false }) + cline.recordToolError("search_files") pushToolResult(await cline.sayAndCreateMissingParamError("search_files", "regex")) return } @@ -65,7 +65,6 @@ export async function searchFilesTool( } pushToolResult(results) - cline.recordToolUsage({ toolName: "search_files" }) return } diff --git a/src/core/tools/switchModeTool.ts b/src/core/tools/switchModeTool.ts index 0d0da1de39..28f719ff2d 100644 --- a/src/core/tools/switchModeTool.ts +++ b/src/core/tools/switchModeTool.ts @@ -29,7 +29,7 @@ export async function switchModeTool( } else { if (!mode_slug) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "switch_mode", success: false }) + cline.recordToolError("switch_mode") pushToolResult(await cline.sayAndCreateMissingParamError("switch_mode", "mode_slug")) return } @@ -40,7 +40,7 @@ export async function switchModeTool( const targetMode = getModeBySlug(mode_slug, (await cline.providerRef.deref()?.getState())?.customModes) if (!targetMode) { - cline.recordToolUsage({ toolName: "switch_mode", success: false }) + cline.recordToolError("switch_mode") pushToolResult(formatResponse.toolError(`Invalid mode: ${mode_slug}`)) return } @@ -49,7 +49,7 @@ export async function switchModeTool( const currentMode = (await cline.providerRef.deref()?.getState())?.mode ?? defaultModeSlug if (currentMode === mode_slug) { - cline.recordToolUsage({ toolName: "switch_mode", success: false }) + cline.recordToolError("switch_mode") pushToolResult(`Already in ${targetMode.name} mode.`) return } @@ -70,8 +70,6 @@ export async function switchModeTool( } mode${reason ? ` because: ${reason}` : ""}.`, ) - cline.recordToolUsage({ toolName: "switch_mode" }) - await delay(500) // Delay to allow mode change to take effect before next tool is executed return diff --git a/src/core/tools/useMcpToolTool.ts b/src/core/tools/useMcpToolTool.ts index 04a400371c..9a5463355c 100644 --- a/src/core/tools/useMcpToolTool.ts +++ b/src/core/tools/useMcpToolTool.ts @@ -28,14 +28,14 @@ export async function useMcpToolTool( } else { if (!server_name) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "switch_mode", success: false }) + cline.recordToolError("use_mcp_tool") pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "server_name")) return } if (!tool_name) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "use_mcp_tool", success: false }) + cline.recordToolError("use_mcp_tool") pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "tool_name")) return } @@ -47,7 +47,7 @@ export async function useMcpToolTool( parsedArguments = JSON.parse(mcp_arguments) } catch (error) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "use_mcp_tool", success: false }) + cline.recordToolError("use_mcp_tool") await cline.say("error", `Roo tried to use ${tool_name} with an invalid JSON argument. Retrying...`) pushToolResult( @@ -100,7 +100,6 @@ export async function useMcpToolTool( await cline.say("mcp_server_response", toolResultPretty) pushToolResult(formatResponse.toolResult(toolResultPretty)) - cline.recordToolUsage({ toolName: "use_mcp_tool" }) return } diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts index cf2ced16b5..2fe39c3511 100644 --- a/src/core/tools/writeToFileTool.ts +++ b/src/core/tools/writeToFileTool.ts @@ -97,7 +97,7 @@ export async function writeToFileTool( } else { if (!relPath) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "write_to_file", success: false }) + cline.recordToolError("write_to_file") pushToolResult(await cline.sayAndCreateMissingParamError("write_to_file", "path")) await cline.diffViewProvider.reset() return @@ -105,7 +105,7 @@ export async function writeToFileTool( if (!newContent) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "write_to_file", success: false }) + cline.recordToolError("write_to_file") pushToolResult(await cline.sayAndCreateMissingParamError("write_to_file", "content")) await cline.diffViewProvider.reset() return @@ -113,7 +113,7 @@ export async function writeToFileTool( if (!predictedLineCount) { cline.consecutiveMistakeCount++ - cline.recordToolUsage({ toolName: "write_to_file", success: false }) + cline.recordToolError("write_to_file") pushToolResult(await cline.sayAndCreateMissingParamError("write_to_file", "line_count")) await cline.diffViewProvider.reset() return @@ -220,7 +220,6 @@ export async function writeToFileTool( pushToolResult(`The content was successfully saved to ${relPath.toPosix()}.${newProblemsMessage}`) } - cline.recordToolUsage({ toolName: "write_to_file" }) await cline.diffViewProvider.reset() return diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index b6ad6864ec..5d6067bbf5 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -114,13 +114,6 @@ jest.mock( { virtual: true }, ) -// Mock DiffStrategy -jest.mock("../../diff/DiffStrategy", () => ({ - getDiffStrategy: jest.fn().mockImplementation(() => ({ - getToolDescription: jest.fn().mockReturnValue("apply_diff tool description"), - })), -})) - // Mock dependencies jest.mock("vscode", () => ({ ExtensionContext: jest.fn(), diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 863d0aed51..b542fdb166 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -38,10 +38,10 @@ import { telemetryService } from "../../services/telemetry/TelemetryService" import { TelemetrySetting } from "../../shared/TelemetrySetting" import { getWorkspacePath } from "../../utils/path" import { Mode, defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes" -import { getDiffStrategy } from "../diff/DiffStrategy" import { SYSTEM_PROMPT } from "../prompts/system" import { buildApiHandler } from "../../api" import { GlobalState } from "../../schemas" +import { MultiSearchReplaceDiffStrategy } from "../diff/strategies/multi-search-replace" export const webviewMessageHandler = async (provider: ClineProvider, message: WebviewMessage) => { // Utility functions provided for concise get/update of global state via contextProxy API. @@ -1372,12 +1372,7 @@ const generateSystemPrompt = async (provider: ClineProvider, message: WebviewMes language, } = await provider.getState() - // Create diffStrategy based on current model and settings. - const diffStrategy = getDiffStrategy({ - model: apiConfiguration.apiModelId || apiConfiguration.openRouterModelId || "", - experiments, - fuzzyMatchThreshold, - }) + const diffStrategy = new MultiSearchReplaceDiffStrategy(fuzzyMatchThreshold) const cwd = provider.cwd diff --git a/src/services/telemetry/PostHogClient.ts b/src/services/telemetry/PostHogClient.ts index c968d17d01..784c9476e8 100644 --- a/src/services/telemetry/PostHogClient.ts +++ b/src/services/telemetry/PostHogClient.ts @@ -32,6 +32,7 @@ export class PostHogClient { ERRORS: { SCHEMA_VALIDATION_ERROR: "Schema Validation Error", DIFF_APPLICATION_ERROR: "Diff Application Error", + SHELL_INTEGRATION_ERROR: "Shell Integration Error", CONSECUTIVE_MISTAKE_ERROR: "Consecutive Mistake Error", }, } diff --git a/src/services/telemetry/TelemetryService.ts b/src/services/telemetry/TelemetryService.ts index c37c9d8ee4..031456f62e 100644 --- a/src/services/telemetry/TelemetryService.ts +++ b/src/services/telemetry/TelemetryService.ts @@ -137,6 +137,10 @@ class TelemetryService { this.captureEvent(PostHogClient.EVENTS.ERRORS.DIFF_APPLICATION_ERROR, { taskId, consecutiveMistakeCount }) } + public captureShellIntegrationError(taskId: string): void { + this.captureEvent(PostHogClient.EVENTS.ERRORS.SHELL_INTEGRATION_ERROR, { taskId }) + } + public captureConsecutiveMistakeError(taskId: string): void { this.captureEvent(PostHogClient.EVENTS.ERRORS.CONSECUTIVE_MISTAKE_ERROR, { taskId }) } diff --git a/src/shared/tools.ts b/src/shared/tools.ts index 858bf591d3..ece22c7fed 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -203,3 +203,45 @@ export const ALWAYS_AVAILABLE_TOOLS: ToolName[] = [ "switch_mode", "new_task", ] as const + +export type DiffResult = + | { success: true; content: string; failParts?: DiffResult[] } + | ({ + success: false + error?: string + details?: { + similarity?: number + threshold?: number + matchedRange?: { start: number; end: number } + searchContent?: string + bestMatch?: string + } + failParts?: DiffResult[] + } & ({ error: string } | { failParts: DiffResult[] })) + +export interface DiffStrategy { + /** + * Get the name of this diff strategy for analytics and debugging + * @returns The name of the diff strategy + */ + getName(): string + + /** + * Get the tool description for this diff strategy + * @param args The tool arguments including cwd and toolOptions + * @returns The complete tool description including format requirements and examples + */ + getToolDescription(args: { cwd: string; toolOptions?: { [key: string]: string } }): string + + /** + * Apply a diff to the original content + * @param originalContent The original file content + * @param diffContent The diff content in the strategy's format + * @param startLine Optional line number where the search block starts. If not provided, searches the entire file. + * @param endLine Optional line number where the search block ends. If not provided, searches the entire file. + * @returns A DiffResult object containing either the successful result or error details + */ + applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): Promise + + getProgressStatus?(toolUse: ToolUse, result?: any): ToolProgressStatus +} From 96ff9fc380ee1f627ede849b8589a422f401fb3a Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Fri, 18 Apr 2025 14:43:27 -0700 Subject: [PATCH 15/15] Fix pricing for Gemini 2.5 Flash (Thinking) (#2773) * Fix pricing for Gemini 2.5 Flash (Thinking) * Looks like it's actually $3.50 * We aren't honoring custom thinking token budgets on Vertex yet --- src/shared/api.ts | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/shared/api.ts b/src/shared/api.ts index ebc0b85c93..346268de44 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -477,24 +477,6 @@ export const openRouterDefaultModelInfo: ModelInfo = { export type VertexModelId = keyof typeof vertexModels export const vertexDefaultModelId: VertexModelId = "claude-3-7-sonnet@20250219" export const vertexModels = { - "gemini-2.0-flash-001": { - maxTokens: 8192, - contextWindow: 1_048_576, - supportsImages: true, - supportsPromptCache: false, - inputPrice: 0.15, - outputPrice: 0.6, - }, - "gemini-2.5-flash-preview-04-17:thinking": { - maxTokens: 65_535, - contextWindow: 1_048_576, - supportsImages: true, - supportsPromptCache: false, - inputPrice: 0.15, - outputPrice: 0.6, - thinking: true, - maxThinkingTokens: 24_576, - }, "gemini-2.5-flash-preview-04-17": { maxTokens: 65_535, contextWindow: 1_048_576, @@ -502,7 +484,6 @@ export const vertexModels = { supportsPromptCache: false, inputPrice: 0.15, outputPrice: 0.6, - thinking: false, }, "gemini-2.5-pro-preview-03-25": { maxTokens: 65_535, @@ -528,6 +509,14 @@ export const vertexModels = { inputPrice: 0, outputPrice: 0, }, + "gemini-2.0-flash-001": { + maxTokens: 8192, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0.15, + outputPrice: 0.6, + }, "gemini-2.0-flash-lite-001": { maxTokens: 8192, contextWindow: 1_048_576, @@ -657,7 +646,7 @@ export const geminiModels = { supportsImages: true, supportsPromptCache: false, inputPrice: 0.15, - outputPrice: 0.6, + outputPrice: 3.5, thinking: true, maxThinkingTokens: 24_576, },