From 9a734d0cab7e453ed204b84d063d6deab5803946 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 15 Aug 2025 12:03:58 -0400 Subject: [PATCH 01/34] Add an API for resuming tasks by ID (#7122) --- packages/ipc/README.md | 90 ++++++++++++++++++++++++ packages/types/npm/package.metadata.json | 2 +- packages/types/src/__tests__/ipc.test.ts | 75 ++++++++++++++++++++ packages/types/src/ipc.ts | 5 ++ src/extension/api.ts | 11 +++ 5 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 packages/ipc/README.md create mode 100644 packages/types/src/__tests__/ipc.test.ts diff --git a/packages/ipc/README.md b/packages/ipc/README.md new file mode 100644 index 0000000000..7642af4958 --- /dev/null +++ b/packages/ipc/README.md @@ -0,0 +1,90 @@ +# IPC (Inter-Process Communication) + +This package provides IPC functionality for Roo Code, allowing external applications to communicate with the extension through a socket-based interface. + +## Available Commands + +The IPC interface supports the following task commands: + +### StartNewTask + +Starts a new task with optional configuration and initial message. + +**Parameters:** + +- `configuration`: RooCode settings object +- `text`: Initial task message (string) +- `images`: Array of image data URIs (optional) +- `newTab`: Whether to open in a new tab (boolean, optional) + +### CancelTask + +Cancels a running task. + +**Parameters:** + +- `data`: Task ID to cancel (string) + +### CloseTask + +Closes a task and performs cleanup. + +**Parameters:** + +- `data`: Task ID to close (string) + +### ResumeTask + +Resumes a task from history. + +**Parameters:** + +- `data`: Task ID to resume (string) + +**Error Handling:** + +- If the task ID is not found in history, the command will fail gracefully without crashing the IPC server +- Errors are logged for debugging purposes but do not propagate to the client + +## Usage Example + +```typescript +import { IpcClient } from "@roo-code/ipc" + +const client = new IpcClient("/path/to/socket") + +// Resume a task +client.sendCommand({ + commandName: "ResumeTask", + data: "task-123", +}) + +// Start a new task +client.sendCommand({ + commandName: "StartNewTask", + data: { + configuration: { + /* RooCode settings */ + }, + text: "Hello, world!", + images: [], + newTab: false, + }, +}) +``` + +## Events + +The IPC interface also emits task events that clients can listen to: + +- `TaskStarted`: When a task begins +- `TaskCompleted`: When a task finishes +- `TaskAborted`: When a task is cancelled +- `Message`: When a task sends a message + +## Socket Path + +The socket path is typically located in the system's temporary directory and follows the pattern: + +- Unix/Linux/macOS: `/tmp/roo-code-{id}.sock` +- Windows: `\\.\pipe\roo-code-{id}` diff --git a/packages/types/npm/package.metadata.json b/packages/types/npm/package.metadata.json index e33d5530af..3de8670447 100644 --- a/packages/types/npm/package.metadata.json +++ b/packages/types/npm/package.metadata.json @@ -1,6 +1,6 @@ { "name": "@roo-code/types", - "version": "1.49.0", + "version": "1.50.0", "description": "TypeScript type definitions for Roo Code.", "publishConfig": { "access": "public", diff --git a/packages/types/src/__tests__/ipc.test.ts b/packages/types/src/__tests__/ipc.test.ts new file mode 100644 index 0000000000..a2b4429356 --- /dev/null +++ b/packages/types/src/__tests__/ipc.test.ts @@ -0,0 +1,75 @@ +import { describe, it, expect } from "vitest" +import { TaskCommandName, taskCommandSchema } from "../ipc.js" + +describe("IPC Types", () => { + describe("TaskCommandName", () => { + it("should include ResumeTask command", () => { + expect(TaskCommandName.ResumeTask).toBe("ResumeTask") + }) + + it("should have all expected task commands", () => { + const expectedCommands = ["StartNewTask", "CancelTask", "CloseTask", "ResumeTask"] + const actualCommands = Object.values(TaskCommandName) + + expectedCommands.forEach((command) => { + expect(actualCommands).toContain(command) + }) + }) + + describe("Error Handling", () => { + it("should handle ResumeTask command gracefully when task not found", () => { + // This test verifies the schema validation - the actual error handling + // for invalid task IDs is tested at the API level, not the schema level + const resumeTaskCommand = { + commandName: TaskCommandName.ResumeTask, + data: "non-existent-task-id", + } + + const result = taskCommandSchema.safeParse(resumeTaskCommand) + expect(result.success).toBe(true) + + if (result.success) { + expect(result.data.commandName).toBe("ResumeTask") + expect(result.data.data).toBe("non-existent-task-id") + } + }) + }) + }) + + describe("taskCommandSchema", () => { + it("should validate ResumeTask command with taskId", () => { + const resumeTaskCommand = { + commandName: TaskCommandName.ResumeTask, + data: "task-123", + } + + const result = taskCommandSchema.safeParse(resumeTaskCommand) + expect(result.success).toBe(true) + + if (result.success) { + expect(result.data.commandName).toBe("ResumeTask") + expect(result.data.data).toBe("task-123") + } + }) + + it("should reject ResumeTask command with invalid data", () => { + const invalidCommand = { + commandName: TaskCommandName.ResumeTask, + data: 123, // Should be string + } + + const result = taskCommandSchema.safeParse(invalidCommand) + expect(result.success).toBe(false) + }) + + it("should reject ResumeTask command without data", () => { + const invalidCommand = { + commandName: TaskCommandName.ResumeTask, + // Missing data field + } + + const result = taskCommandSchema.safeParse(invalidCommand) + expect(result.success).toBe(false) + }) + }) +}) diff --git a/packages/types/src/ipc.ts b/packages/types/src/ipc.ts index 22cba1dea8..ace39c3f2b 100644 --- a/packages/types/src/ipc.ts +++ b/packages/types/src/ipc.ts @@ -44,6 +44,7 @@ export enum TaskCommandName { StartNewTask = "StartNewTask", CancelTask = "CancelTask", CloseTask = "CloseTask", + ResumeTask = "ResumeTask", } /** @@ -68,6 +69,10 @@ export const taskCommandSchema = z.discriminatedUnion("commandName", [ commandName: z.literal(TaskCommandName.CloseTask), data: z.string(), }), + z.object({ + commandName: z.literal(TaskCommandName.ResumeTask), + data: z.string(), + }), ]) export type TaskCommand = z.infer diff --git a/src/extension/api.ts b/src/extension/api.ts index f419613b8e..2fc51e7afb 100644 --- a/src/extension/api.ts +++ b/src/extension/api.ts @@ -78,6 +78,17 @@ export class API extends EventEmitter implements RooCodeAPI { await vscode.commands.executeCommand("workbench.action.files.saveFiles") await vscode.commands.executeCommand("workbench.action.closeWindow") break + case TaskCommandName.ResumeTask: + this.log(`[API] ResumeTask -> ${data}`) + try { + await this.resumeTask(data) + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + this.log(`[API] ResumeTask failed for taskId ${data}: ${errorMessage}`) + // Don't rethrow - we want to prevent IPC server crashes + // The error is logged for debugging purposes + } + break } }) } From 6274273982edb552106ceb068e6d3761e3ad6fdf Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 15 Aug 2025 12:42:53 -0400 Subject: [PATCH 02/34] Release: v1.51.0 (#7130) --- packages/types/npm/package.metadata.json | 2 +- packages/types/src/task.ts | 1 + src/core/webview/ClineProvider.ts | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/types/npm/package.metadata.json b/packages/types/npm/package.metadata.json index 3de8670447..f9e3c71ead 100644 --- a/packages/types/npm/package.metadata.json +++ b/packages/types/npm/package.metadata.json @@ -1,6 +1,6 @@ { "name": "@roo-code/types", - "version": "1.50.0", + "version": "1.51.0", "description": "TypeScript type definitions for Roo Code.", "publishConfig": { "access": "public", diff --git a/packages/types/src/task.ts b/packages/types/src/task.ts index 1a5a5039bb..07789c88de 100644 --- a/packages/types/src/task.ts +++ b/packages/types/src/task.ts @@ -25,6 +25,7 @@ export interface TaskProviderLike { createTask(text?: string, images?: string[], parentTask?: TaskLike): Promise cancelTask(): Promise clearTask(): Promise + resumeTask(taskId: string): void getState(): Promise postStateToWebview(): Promise diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 2270d5aee5..87c92448e8 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -403,6 +403,13 @@ export class ClineProvider await this.removeClineFromStack() } + resumeTask(taskId: string): void { + // Use the existing showTaskWithId method which handles both current and historical tasks + this.showTaskWithId(taskId).catch((error) => { + this.log(`Failed to resume task ${taskId}: ${error.message}`) + }) + } + getRecentTasks(): string[] { if (this.recentTasksCache) { return this.recentTasksCache From 52c58ea66646300e3856402a91cdfd2ed9def736 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 15 Aug 2025 14:25:32 -0400 Subject: [PATCH 03/34] Bump cloud version to 0.16.0 (#7135) --- pnpm-lock.yaml | 20 ++++++++++---------- src/package.json | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0bdcf9ab8..3faeb9f219 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -584,8 +584,8 @@ importers: specifier: ^1.14.0 version: 1.14.0(typescript@5.8.3) '@roo-code/cloud': - specifier: ^0.15.0 - version: 0.15.0 + specifier: ^0.16.0 + version: 0.16.0 '@roo-code/ipc': specifier: workspace:^ version: link:../packages/ipc @@ -3103,11 +3103,11 @@ packages: cpu: [x64] os: [win32] - '@roo-code/cloud@0.15.0': - resolution: {integrity: sha512-0DivOP5uUS9U6UKSxzoxZ4NxMCYUxA7wG72y3PBP91JhGzHaTqxi9WrvF61bo134dCnCktU9oTKIAD9AvFigzg==} + '@roo-code/cloud@0.16.0': + resolution: {integrity: sha512-AMHjPFK6lSZeutELzdYgxs4r7tUW8NEffRkM3NagtGKK5KY/pCRwctdP0TDCJGwLCRu5JI21Ww9uYCgAQ4MM3Q==} - '@roo-code/types@1.49.0': - resolution: {integrity: sha512-h7gbjfIxBN+fgFecQiZs3W+vjdUhZOvtjh4OqcpPGG1w8B1DlXPDV4L/+ARUeNzZxSOK5S5rNPy57jC35EaD/w==} + '@roo-code/types@1.51.0': + resolution: {integrity: sha512-h+wihwF9iuKfb7xycS5yXgDzGGypjiZF4Sy4tu6vdkhzVcE8ExFtCwGn1w535p9KaLE1QCV/G5NddgajqRyPAQ==} '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -12305,9 +12305,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.40.2': optional: true - '@roo-code/cloud@0.15.0': + '@roo-code/cloud@0.16.0': dependencies: - '@roo-code/types': 1.49.0 + '@roo-code/types': 1.51.0 ioredis: 5.6.1 p-wait-for: 5.0.2 socket.io-client: 4.8.1 @@ -12317,7 +12317,7 @@ snapshots: - supports-color - utf-8-validate - '@roo-code/types@1.49.0': + '@roo-code/types@1.51.0': dependencies: zod: 3.25.76 @@ -14176,7 +14176,7 @@ snapshots: dependencies: devtools-protocol: 0.0.1452169 mitt: 3.0.1 - zod: 3.25.61 + zod: 3.25.76 ci-info@2.0.0: {} diff --git a/src/package.json b/src/package.json index 296e54abc7..604354a31c 100644 --- a/src/package.json +++ b/src/package.json @@ -427,7 +427,7 @@ "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.9.0", "@qdrant/js-client-rest": "^1.14.0", - "@roo-code/cloud": "^0.15.0", + "@roo-code/cloud": "^0.16.0", "@roo-code/ipc": "workspace:^", "@roo-code/telemetry": "workspace:^", "@roo-code/types": "workspace:^", From 438c8e377adc5637d69201455c4a75d4f27a86d8 Mon Sep 17 00:00:00 2001 From: Will Li Date: Fri, 15 Aug 2025 19:40:46 -0700 Subject: [PATCH 04/34] Make enhance with task history default to true (#7140) --- src/core/webview/ClineProvider.ts | 4 ++-- src/core/webview/webviewMessageHandler.ts | 2 +- webview-ui/src/components/settings/PromptsSettings.tsx | 4 ++-- webview-ui/src/components/settings/SettingsView.tsx | 2 +- webview-ui/src/context/ExtensionStateContext.tsx | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 87c92448e8..8c4a32c3c8 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1873,7 +1873,7 @@ export class ClineProvider followupAutoApproveTimeoutMs: followupAutoApproveTimeoutMs ?? 60000, includeDiagnosticMessages: includeDiagnosticMessages ?? true, maxDiagnosticMessages: maxDiagnosticMessages ?? 50, - includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? false, + includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true, remoteControlEnabled: remoteControlEnabled ?? false, } } @@ -2061,7 +2061,7 @@ export class ClineProvider includeDiagnosticMessages: stateValues.includeDiagnosticMessages ?? true, maxDiagnosticMessages: stateValues.maxDiagnosticMessages ?? 50, // Add includeTaskHistoryInEnhance setting - includeTaskHistoryInEnhance: stateValues.includeTaskHistoryInEnhance ?? false, + includeTaskHistoryInEnhance: stateValues.includeTaskHistoryInEnhance ?? true, // Add remoteControlEnabled setting remoteControlEnabled: stateValues.remoteControlEnabled ?? false, } diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index d16ed06132..4dd0fee75e 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1343,7 +1343,7 @@ export const webviewMessageHandler = async ( await provider.postStateToWebview() break case "includeTaskHistoryInEnhance": - await updateGlobalState("includeTaskHistoryInEnhance", message.bool ?? false) + await updateGlobalState("includeTaskHistoryInEnhance", message.bool ?? true) await provider.postStateToWebview() break case "condensingApiConfigId": diff --git a/webview-ui/src/components/settings/PromptsSettings.tsx b/webview-ui/src/components/settings/PromptsSettings.tsx index ee112b54dd..d09b53fe47 100644 --- a/webview-ui/src/components/settings/PromptsSettings.tsx +++ b/webview-ui/src/components/settings/PromptsSettings.tsx @@ -46,7 +46,7 @@ const PromptsSettings = ({ } = useExtensionState() // Use props if provided, otherwise fall back to context - const includeTaskHistoryInEnhance = propsIncludeTaskHistoryInEnhance ?? contextIncludeTaskHistoryInEnhance + const includeTaskHistoryInEnhance = propsIncludeTaskHistoryInEnhance ?? contextIncludeTaskHistoryInEnhance ?? true const setIncludeTaskHistoryInEnhance = propsSetIncludeTaskHistoryInEnhance ?? contextSetIncludeTaskHistoryInEnhance const [testPrompt, setTestPrompt] = useState("") @@ -235,7 +235,7 @@ const PromptsSettings = ({ <>
{ const value = e.target.checked setIncludeTaskHistoryInEnhance(value) diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 9866c1e235..2738b82632 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -341,7 +341,7 @@ const SettingsView = forwardRef(({ onDone, t vscode.postMessage({ type: "condensingApiConfigId", text: condensingApiConfigId || "" }) vscode.postMessage({ type: "updateCondensingPrompt", text: customCondensingPrompt || "" }) vscode.postMessage({ type: "updateSupportPrompt", values: customSupportPrompts || {} }) - vscode.postMessage({ type: "includeTaskHistoryInEnhance", bool: includeTaskHistoryInEnhance ?? false }) + vscode.postMessage({ type: "includeTaskHistoryInEnhance", bool: includeTaskHistoryInEnhance ?? true }) vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration }) vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting }) vscode.postMessage({ type: "profileThresholds", values: profileThresholds }) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index ad35452079..b0045977c3 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -268,7 +268,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode project: {}, global: {}, }) - const [includeTaskHistoryInEnhance, setIncludeTaskHistoryInEnhance] = useState(false) + const [includeTaskHistoryInEnhance, setIncludeTaskHistoryInEnhance] = useState(true) const setListApiConfigMeta = useCallback( (value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })), From 45ac7ee6cffac626cb585634829ba69845e54e7d Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 23:15:55 -0400 Subject: [PATCH 05/34] feat: add support for OpenAI gpt-5-chat-latest model (#7058) * feat: add support for OpenAI gpt-5-chat-latest model - Added gpt-5-chat-latest model configuration to openAiNativeModels - Updated OpenAiNativeHandler to recognize gpt-5-chat-latest as a Responses API model - Added comprehensive tests for the new model - Model is optimized for conversational AI and non-reasoning tasks Fixes #7057 * fix: remove redundant condition and unnecessary test file - Remove redundant gpt-5-chat-latest check in isResponsesApiModel since startsWith('gpt-5') already covers it - Remove unnecessary dedicated test file for gpt-5-chat-latest --------- Co-authored-by: Roo Code Co-authored-by: daniel-lxs --- packages/types/src/providers/openai.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/types/src/providers/openai.ts b/packages/types/src/providers/openai.ts index ff79824984..6409e67586 100644 --- a/packages/types/src/providers/openai.ts +++ b/packages/types/src/providers/openai.ts @@ -6,6 +6,18 @@ export type OpenAiNativeModelId = keyof typeof openAiNativeModels export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-5-2025-08-07" export const openAiNativeModels = { + "gpt-5-chat-latest": { + maxTokens: 128000, + contextWindow: 400000, + supportsImages: true, + supportsPromptCache: true, + supportsReasoningEffort: false, + inputPrice: 1.25, + outputPrice: 10.0, + cacheReadsPrice: 0.13, + description: "GPT-5 Chat Latest: Optimized for conversational AI and non-reasoning tasks", + supportsVerbosity: true, + }, "gpt-5-2025-08-07": { maxTokens: 128000, contextWindow: 400000, From f3864ffebba8ddd82831cfa42436251c38168416 Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Fri, 15 Aug 2025 22:31:58 -0500 Subject: [PATCH 06/34] fix: use native Ollama API instead of OpenAI compatibility layer (#7137) --- pnpm-lock.yaml | 17 +- src/api/index.ts | 4 +- .../providers/__tests__/native-ollama.spec.ts | 162 ++++++++++ src/api/providers/native-ollama.ts | 285 ++++++++++++++++++ src/package.json | 1 + 5 files changed, 466 insertions(+), 3 deletions(-) create mode 100644 src/api/providers/__tests__/native-ollama.spec.ts create mode 100644 src/api/providers/native-ollama.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3faeb9f219..d514ddf028 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -676,6 +676,9 @@ importers: node-ipc: specifier: ^12.0.0 version: 12.0.0 + ollama: + specifier: ^0.5.17 + version: 0.5.17 openai: specifier: ^5.0.0 version: 5.5.1(ws@8.18.3)(zod@3.25.61) @@ -7645,6 +7648,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + ollama@0.5.17: + resolution: {integrity: sha512-q5LmPtk6GLFouS+3aURIVl+qcAOPC4+Msmx7uBb3pd+fxI55WnGjmLZ0yijI/CYy79x0QPGx3BwC3u5zv9fBvQ==} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -9655,6 +9661,9 @@ packages: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} @@ -13546,7 +13555,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.50)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) '@vitest/utils@3.2.4': dependencies: @@ -17683,6 +17692,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + ollama@0.5.17: + dependencies: + whatwg-fetch: 3.6.20 + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -20155,6 +20168,8 @@ snapshots: dependencies: iconv-lite: 0.6.3 + whatwg-fetch@3.6.20: {} + whatwg-mimetype@4.0.0: {} whatwg-url@14.2.0: diff --git a/src/api/index.ts b/src/api/index.ts index c29c230b06..92a5c95770 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -13,7 +13,6 @@ import { VertexHandler, AnthropicVertexHandler, OpenAiHandler, - OllamaHandler, LmStudioHandler, GeminiHandler, OpenAiNativeHandler, @@ -37,6 +36,7 @@ import { ZAiHandler, FireworksHandler, } from "./providers" +import { NativeOllamaHandler } from "./providers/native-ollama" export interface SingleCompletionHandler { completePrompt(prompt: string): Promise @@ -95,7 +95,7 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler { case "openai": return new OpenAiHandler(options) case "ollama": - return new OllamaHandler(options) + return new NativeOllamaHandler(options) case "lmstudio": return new LmStudioHandler(options) case "gemini": diff --git a/src/api/providers/__tests__/native-ollama.spec.ts b/src/api/providers/__tests__/native-ollama.spec.ts new file mode 100644 index 0000000000..f8792937db --- /dev/null +++ b/src/api/providers/__tests__/native-ollama.spec.ts @@ -0,0 +1,162 @@ +// npx vitest run api/providers/__tests__/native-ollama.spec.ts + +import { NativeOllamaHandler } from "../native-ollama" +import { ApiHandlerOptions } from "../../../shared/api" + +// Mock the ollama package +const mockChat = vitest.fn() +vitest.mock("ollama", () => { + return { + Ollama: vitest.fn().mockImplementation(() => ({ + chat: mockChat, + })), + Message: vitest.fn(), + } +}) + +// Mock the getOllamaModels function +vitest.mock("../fetchers/ollama", () => ({ + getOllamaModels: vitest.fn().mockResolvedValue({ + llama2: { + contextWindow: 4096, + maxTokens: 4096, + supportsImages: false, + supportsPromptCache: false, + }, + }), +})) + +describe("NativeOllamaHandler", () => { + let handler: NativeOllamaHandler + + beforeEach(() => { + vitest.clearAllMocks() + + const options: ApiHandlerOptions = { + apiModelId: "llama2", + ollamaModelId: "llama2", + ollamaBaseUrl: "http://localhost:11434", + } + + handler = new NativeOllamaHandler(options) + }) + + describe("createMessage", () => { + it("should stream messages from Ollama", async () => { + // Mock the chat response as an async generator + mockChat.mockImplementation(async function* () { + yield { + message: { content: "Hello" }, + eval_count: undefined, + prompt_eval_count: undefined, + } + yield { + message: { content: " world" }, + eval_count: 2, + prompt_eval_count: 10, + } + }) + + const systemPrompt = "You are a helpful assistant" + const messages = [{ role: "user" as const, content: "Hi there" }] + + const stream = handler.createMessage(systemPrompt, messages) + const results = [] + + for await (const chunk of stream) { + results.push(chunk) + } + + expect(results).toHaveLength(3) + expect(results[0]).toEqual({ type: "text", text: "Hello" }) + expect(results[1]).toEqual({ type: "text", text: " world" }) + expect(results[2]).toEqual({ type: "usage", inputTokens: 10, outputTokens: 2 }) + }) + + it("should handle DeepSeek R1 models with reasoning detection", async () => { + const options: ApiHandlerOptions = { + apiModelId: "deepseek-r1", + ollamaModelId: "deepseek-r1", + ollamaBaseUrl: "http://localhost:11434", + } + + handler = new NativeOllamaHandler(options) + + // Mock response with thinking tags + mockChat.mockImplementation(async function* () { + yield { message: { content: "Let me think" } } + yield { message: { content: " about this" } } + yield { message: { content: "The answer is 42" } } + }) + + const stream = handler.createMessage("System", [{ role: "user" as const, content: "Question?" }]) + const results = [] + + for await (const chunk of stream) { + results.push(chunk) + } + + // Should detect reasoning vs regular text + expect(results.some((r) => r.type === "reasoning")).toBe(true) + expect(results.some((r) => r.type === "text")).toBe(true) + }) + }) + + describe("completePrompt", () => { + it("should complete a prompt without streaming", async () => { + mockChat.mockResolvedValue({ + message: { content: "This is the response" }, + }) + + const result = await handler.completePrompt("Tell me a joke") + + expect(mockChat).toHaveBeenCalledWith({ + model: "llama2", + messages: [{ role: "user", content: "Tell me a joke" }], + stream: false, + options: { + temperature: 0, + }, + }) + expect(result).toBe("This is the response") + }) + }) + + describe("error handling", () => { + it("should handle connection refused errors", async () => { + const error = new Error("ECONNREFUSED") as any + error.code = "ECONNREFUSED" + mockChat.mockRejectedValue(error) + + const stream = handler.createMessage("System", [{ role: "user" as const, content: "Test" }]) + + await expect(async () => { + for await (const _ of stream) { + // consume stream + } + }).rejects.toThrow("Ollama service is not running") + }) + + it("should handle model not found errors", async () => { + const error = new Error("Not found") as any + error.status = 404 + mockChat.mockRejectedValue(error) + + const stream = handler.createMessage("System", [{ role: "user" as const, content: "Test" }]) + + await expect(async () => { + for await (const _ of stream) { + // consume stream + } + }).rejects.toThrow("Model llama2 not found in Ollama") + }) + }) + + describe("getModel", () => { + it("should return the configured model", () => { + const model = handler.getModel() + expect(model.id).toBe("llama2") + expect(model.info).toBeDefined() + }) + }) +}) diff --git a/src/api/providers/native-ollama.ts b/src/api/providers/native-ollama.ts new file mode 100644 index 0000000000..8ab4ebe2e1 --- /dev/null +++ b/src/api/providers/native-ollama.ts @@ -0,0 +1,285 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import { Message, Ollama } from "ollama" +import { ModelInfo, openAiModelInfoSaneDefaults, DEEP_SEEK_DEFAULT_TEMPERATURE } from "@roo-code/types" +import { ApiStream } from "../transform/stream" +import { BaseProvider } from "./base-provider" +import type { ApiHandlerOptions } from "../../shared/api" +import { getOllamaModels } from "./fetchers/ollama" +import { XmlMatcher } from "../../utils/xml-matcher" +import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index" + +function convertToOllamaMessages(anthropicMessages: Anthropic.Messages.MessageParam[]): Message[] { + const ollamaMessages: Message[] = [] + + for (const anthropicMessage of anthropicMessages) { + if (typeof anthropicMessage.content === "string") { + ollamaMessages.push({ + role: anthropicMessage.role, + content: anthropicMessage.content, + }) + } else { + if (anthropicMessage.role === "user") { + const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{ + nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] + toolMessages: Anthropic.ToolResultBlockParam[] + }>( + (acc, part) => { + if (part.type === "tool_result") { + acc.toolMessages.push(part) + } else if (part.type === "text" || part.type === "image") { + acc.nonToolMessages.push(part) + } + return acc + }, + { nonToolMessages: [], toolMessages: [] }, + ) + + // Process tool result messages FIRST since they must follow the tool use messages + const toolResultImages: string[] = [] + toolMessages.forEach((toolMessage) => { + // The Anthropic SDK allows tool results to be a string or an array of text and image blocks, enabling rich and structured content. In contrast, the Ollama SDK only supports tool results as a single string, so we map the Anthropic tool result parts into one concatenated string to maintain compatibility. + let content: string + + if (typeof toolMessage.content === "string") { + content = toolMessage.content + } else { + content = + toolMessage.content + ?.map((part) => { + if (part.type === "image") { + // Handle base64 images only (Anthropic SDK uses base64) + // Ollama expects raw base64 strings, not data URLs + if ("source" in part && part.source.type === "base64") { + toolResultImages.push(part.source.data) + } + return "(see following user message for image)" + } + return part.text + }) + .join("\n") ?? "" + } + ollamaMessages.push({ + role: "user", + images: toolResultImages.length > 0 ? toolResultImages : undefined, + content: content, + }) + }) + + // Process non-tool messages + if (nonToolMessages.length > 0) { + // Separate text and images for Ollama + const textContent = nonToolMessages + .filter((part) => part.type === "text") + .map((part) => part.text) + .join("\n") + + const imageData: string[] = [] + nonToolMessages.forEach((part) => { + if (part.type === "image" && "source" in part && part.source.type === "base64") { + // Ollama expects raw base64 strings, not data URLs + imageData.push(part.source.data) + } + }) + + ollamaMessages.push({ + role: "user", + content: textContent, + images: imageData.length > 0 ? imageData : undefined, + }) + } + } else if (anthropicMessage.role === "assistant") { + const { nonToolMessages } = anthropicMessage.content.reduce<{ + nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] + toolMessages: Anthropic.ToolUseBlockParam[] + }>( + (acc, part) => { + if (part.type === "tool_use") { + acc.toolMessages.push(part) + } else if (part.type === "text" || part.type === "image") { + acc.nonToolMessages.push(part) + } // assistant cannot send tool_result messages + return acc + }, + { nonToolMessages: [], toolMessages: [] }, + ) + + // Process non-tool messages + let content: string = "" + if (nonToolMessages.length > 0) { + content = nonToolMessages + .map((part) => { + if (part.type === "image") { + return "" // impossible as the assistant cannot send images + } + return part.text + }) + .join("\n") + } + + ollamaMessages.push({ + role: "assistant", + content, + }) + } + } + } + + return ollamaMessages +} + +export class NativeOllamaHandler extends BaseProvider implements SingleCompletionHandler { + protected options: ApiHandlerOptions + private client: Ollama | undefined + protected models: Record = {} + + constructor(options: ApiHandlerOptions) { + super() + this.options = options + } + + private ensureClient(): Ollama { + if (!this.client) { + try { + this.client = new Ollama({ + host: this.options.ollamaBaseUrl || "http://localhost:11434", + // Note: The ollama npm package handles timeouts internally + }) + } catch (error: any) { + throw new Error(`Error creating Ollama client: ${error.message}`) + } + } + return this.client + } + + override async *createMessage( + systemPrompt: string, + messages: Anthropic.Messages.MessageParam[], + metadata?: ApiHandlerCreateMessageMetadata, + ): ApiStream { + const client = this.ensureClient() + const { id: modelId, info: modelInfo } = await this.fetchModel() + const useR1Format = modelId.toLowerCase().includes("deepseek-r1") + + const ollamaMessages: Message[] = [ + { role: "system", content: systemPrompt }, + ...convertToOllamaMessages(messages), + ] + + const matcher = new XmlMatcher( + "think", + (chunk) => + ({ + type: chunk.matched ? "reasoning" : "text", + text: chunk.data, + }) as const, + ) + + try { + // Create the actual API request promise + const stream = await client.chat({ + model: modelId, + messages: ollamaMessages, + stream: true, + options: { + num_ctx: modelInfo.contextWindow, + temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0), + }, + }) + + let totalInputTokens = 0 + let totalOutputTokens = 0 + + try { + for await (const chunk of stream) { + if (typeof chunk.message.content === "string") { + // Process content through matcher for reasoning detection + for (const matcherChunk of matcher.update(chunk.message.content)) { + yield matcherChunk + } + } + + // Handle token usage if available + if (chunk.eval_count !== undefined || chunk.prompt_eval_count !== undefined) { + if (chunk.prompt_eval_count) { + totalInputTokens = chunk.prompt_eval_count + } + if (chunk.eval_count) { + totalOutputTokens = chunk.eval_count + } + } + } + + // Yield any remaining content from the matcher + for (const chunk of matcher.final()) { + yield chunk + } + + // Yield usage information if available + if (totalInputTokens > 0 || totalOutputTokens > 0) { + yield { + type: "usage", + inputTokens: totalInputTokens, + outputTokens: totalOutputTokens, + } + } + } catch (streamError: any) { + console.error("Error processing Ollama stream:", streamError) + throw new Error(`Ollama stream processing error: ${streamError.message || "Unknown error"}`) + } + } catch (error: any) { + // Enhance error reporting + const statusCode = error.status || error.statusCode + const errorMessage = error.message || "Unknown error" + + if (error.code === "ECONNREFUSED") { + throw new Error( + `Ollama service is not running at ${this.options.ollamaBaseUrl || "http://localhost:11434"}. Please start Ollama first.`, + ) + } else if (statusCode === 404) { + throw new Error( + `Model ${this.getModel().id} not found in Ollama. Please pull the model first with: ollama pull ${this.getModel().id}`, + ) + } + + console.error(`Ollama API error (${statusCode || "unknown"}): ${errorMessage}`) + throw error + } + } + + async fetchModel() { + this.models = await getOllamaModels(this.options.ollamaBaseUrl) + return this.getModel() + } + + override getModel(): { id: string; info: ModelInfo } { + const modelId = this.options.ollamaModelId || "" + return { + id: modelId, + info: this.models[modelId] || openAiModelInfoSaneDefaults, + } + } + + async completePrompt(prompt: string): Promise { + try { + const client = this.ensureClient() + const { id: modelId } = await this.fetchModel() + const useR1Format = modelId.toLowerCase().includes("deepseek-r1") + + const response = await client.chat({ + model: modelId, + messages: [{ role: "user", content: prompt }], + stream: false, + options: { + temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0), + }, + }) + + return response.message?.content || "" + } catch (error) { + if (error instanceof Error) { + throw new Error(`Ollama completion error: ${error.message}`) + } + throw error + } + } +} diff --git a/src/package.json b/src/package.json index 604354a31c..4928f450b3 100644 --- a/src/package.json +++ b/src/package.json @@ -458,6 +458,7 @@ "monaco-vscode-textmate-theme-converter": "^0.1.7", "node-cache": "^5.1.2", "node-ipc": "^12.0.0", + "ollama": "^0.5.17", "openai": "^5.0.0", "os-name": "^6.0.0", "p-limit": "^6.2.0", From 2a974e8bf63a6974d8ac55fbdac57148e2dd5391 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Fri, 15 Aug 2025 23:46:51 -1000 Subject: [PATCH 07/34] Emit event when a task ask requires interaction (#7128) Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- packages/types/npm/package.metadata.json | 2 +- packages/types/src/events.ts | 14 +++ packages/types/src/message.ts | 57 +++++++-- packages/types/src/task.ts | 24 +++- pnpm-lock.yaml | 20 ++-- src/core/task/Task.ts | 142 ++++++++++++++++++----- src/core/webview/ClineProvider.ts | 7 ++ src/package.json | 2 +- 8 files changed, 213 insertions(+), 55 deletions(-) diff --git a/packages/types/npm/package.metadata.json b/packages/types/npm/package.metadata.json index f9e3c71ead..a40b43aacc 100644 --- a/packages/types/npm/package.metadata.json +++ b/packages/types/npm/package.metadata.json @@ -1,6 +1,6 @@ { "name": "@roo-code/types", - "version": "1.51.0", + "version": "1.52.0", "description": "TypeScript type definitions for Roo Code.", "publishConfig": { "access": "public", diff --git a/packages/types/src/events.ts b/packages/types/src/events.ts index 42c389ab60..2b6b810c81 100644 --- a/packages/types/src/events.ts +++ b/packages/types/src/events.ts @@ -18,6 +18,8 @@ export enum RooCodeEventName { TaskFocused = "taskFocused", TaskUnfocused = "taskUnfocused", TaskActive = "taskActive", + TaskInteractive = "taskInteractive", + TaskResumable = "taskResumable", TaskIdle = "taskIdle", // Subtask Lifecycle @@ -59,6 +61,8 @@ export const rooCodeEventsSchema = z.object({ [RooCodeEventName.TaskFocused]: z.tuple([z.string()]), [RooCodeEventName.TaskUnfocused]: z.tuple([z.string()]), [RooCodeEventName.TaskActive]: z.tuple([z.string()]), + [RooCodeEventName.TaskInteractive]: z.tuple([z.string()]), + [RooCodeEventName.TaskResumable]: z.tuple([z.string()]), [RooCodeEventName.TaskIdle]: z.tuple([z.string()]), [RooCodeEventName.TaskPaused]: z.tuple([z.string()]), @@ -124,6 +128,16 @@ export const taskEventSchema = z.discriminatedUnion("eventName", [ payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskActive], taskId: z.number().optional(), }), + z.object({ + eventName: z.literal(RooCodeEventName.TaskInteractive), + payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskInteractive], + taskId: z.number().optional(), + }), + z.object({ + eventName: z.literal(RooCodeEventName.TaskResumable), + payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskResumable], + taskId: z.number().optional(), + }), z.object({ eventName: z.literal(RooCodeEventName.TaskIdle), payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskIdle], diff --git a/packages/types/src/message.ts b/packages/types/src/message.ts index 7197ab29a1..5037370f24 100644 --- a/packages/types/src/message.ts +++ b/packages/types/src/message.ts @@ -44,24 +44,61 @@ export const clineAskSchema = z.enum(clineAsks) export type ClineAsk = z.infer +// Needs classification: +// - `followup` +// - `command_output + /** - * BlockingAsk + * IdleAsk + * + * Asks that put the task into an "idle" state. */ -export const blockingAsks: ClineAsk[] = [ - "api_req_failed", - "mistake_limit_reached", +export const idleAsks = [ "completion_result", - "resume_task", + "api_req_failed", "resume_completed_task", - "command_output", + "mistake_limit_reached", "auto_approval_max_req_reached", -] as const +] as const satisfies readonly ClineAsk[] -export type BlockingAsk = (typeof blockingAsks)[number] +export type IdleAsk = (typeof idleAsks)[number] -export function isBlockingAsk(ask: ClineAsk): ask is BlockingAsk { - return blockingAsks.includes(ask) +export function isIdleAsk(ask: ClineAsk): ask is IdleAsk { + return (idleAsks as readonly ClineAsk[]).includes(ask) +} + +/** + * ResumableAsk + * + * Asks that put the task into an "resumable" state. + */ + +export const resumableAsks = ["resume_task"] as const satisfies readonly ClineAsk[] + +export type ResumableAsk = (typeof resumableAsks)[number] + +export function isResumableAsk(ask: ClineAsk): ask is ResumableAsk { + return (resumableAsks as readonly ClineAsk[]).includes(ask) +} + +/** + * InteractiveAsk + * + * Asks that put the task into an "user interaction required" state. + */ + +export const interactiveAsks = [ + "command", + "tool", + "browser_action_launch", + "use_mcp_server", +] as const satisfies readonly ClineAsk[] + +export type InteractiveAsk = (typeof interactiveAsks)[number] + +export function isInteractiveAsk(ask: ClineAsk): ask is InteractiveAsk { + return (interactiveAsks as readonly ClineAsk[]).includes(ask) } /** diff --git a/packages/types/src/task.ts b/packages/types/src/task.ts index 07789c88de..96daac424b 100644 --- a/packages/types/src/task.ts +++ b/packages/types/src/task.ts @@ -1,7 +1,7 @@ import { z } from "zod" import { RooCodeEventName } from "./events.js" -import { type ClineMessage, type BlockingAsk, type TokenUsage } from "./message.js" +import { type ClineMessage, type TokenUsage } from "./message.js" import { type ToolUsage, type ToolName } from "./tool.js" import type { StaticAppProperties, GitProperties, TelemetryProperties } from "./telemetry.js" @@ -54,6 +54,8 @@ export type TaskProviderEvents = { [RooCodeEventName.TaskFocused]: [taskId: string] [RooCodeEventName.TaskUnfocused]: [taskId: string] [RooCodeEventName.TaskActive]: [taskId: string] + [RooCodeEventName.TaskInteractive]: [taskId: string] + [RooCodeEventName.TaskResumable]: [taskId: string] [RooCodeEventName.TaskIdle]: [taskId: string] } @@ -61,8 +63,15 @@ export type TaskProviderEvents = { * TaskLike */ +export enum TaskStatus { + Running = "running", + Interactive = "interactive", + Resumable = "resumable", + Idle = "idle", + None = "none", +} + export const taskMetadataSchema = z.object({ - taskId: z.string(), task: z.string().optional(), images: z.array(z.string()).optional(), }) @@ -71,14 +80,17 @@ export type TaskMetadata = z.infer export interface TaskLike { readonly taskId: string - readonly rootTask?: TaskLike - readonly blockingAsk?: BlockingAsk + readonly taskStatus: TaskStatus + readonly taskAsk: ClineMessage | undefined readonly metadata: TaskMetadata + readonly rootTask?: TaskLike + on(event: K, listener: (...args: TaskEvents[K]) => void | Promise): this off(event: K, listener: (...args: TaskEvents[K]) => void | Promise): this - setMessageResponse(text: string, images?: string[]): void + approveAsk(options?: { text?: string; images?: string[] }): void + denyAsk(options?: { text?: string; images?: string[] }): void submitUserMessage(text: string, images?: string[]): void } @@ -90,6 +102,8 @@ export type TaskEvents = { [RooCodeEventName.TaskFocused]: [] [RooCodeEventName.TaskUnfocused]: [] [RooCodeEventName.TaskActive]: [taskId: string] + [RooCodeEventName.TaskInteractive]: [taskId: string] + [RooCodeEventName.TaskResumable]: [taskId: string] [RooCodeEventName.TaskIdle]: [taskId: string] // Subtask Lifecycle diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d514ddf028..0d615c6e04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -584,8 +584,8 @@ importers: specifier: ^1.14.0 version: 1.14.0(typescript@5.8.3) '@roo-code/cloud': - specifier: ^0.16.0 - version: 0.16.0 + specifier: ^0.17.0 + version: 0.17.0 '@roo-code/ipc': specifier: workspace:^ version: link:../packages/ipc @@ -3106,11 +3106,11 @@ packages: cpu: [x64] os: [win32] - '@roo-code/cloud@0.16.0': - resolution: {integrity: sha512-AMHjPFK6lSZeutELzdYgxs4r7tUW8NEffRkM3NagtGKK5KY/pCRwctdP0TDCJGwLCRu5JI21Ww9uYCgAQ4MM3Q==} + '@roo-code/cloud@0.17.0': + resolution: {integrity: sha512-Sh7KGbVapxocnoyDzWkvvtVzam5jZnU2RGKT7V0v3CbFMQc329NBnHRAgVtJUwcGPHUFyAkwEHo181Fn3rFpZw==} - '@roo-code/types@1.51.0': - resolution: {integrity: sha512-h+wihwF9iuKfb7xycS5yXgDzGGypjiZF4Sy4tu6vdkhzVcE8ExFtCwGn1w535p9KaLE1QCV/G5NddgajqRyPAQ==} + '@roo-code/types@1.52.0': + resolution: {integrity: sha512-jPCVZ2j4Y0MUiHvAJbXduR3yFGb5A3KDvIThi8cKBAUg8zb7jhIYKTvJ5vub1MGZkpK11K0JG6ex4RL19FjobA==} '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -12314,9 +12314,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.40.2': optional: true - '@roo-code/cloud@0.16.0': + '@roo-code/cloud@0.17.0': dependencies: - '@roo-code/types': 1.51.0 + '@roo-code/types': 1.52.0 ioredis: 5.6.1 p-wait-for: 5.0.2 socket.io-client: 4.8.1 @@ -12326,7 +12326,7 @@ snapshots: - supports-color - utf-8-validate - '@roo-code/types@1.51.0': + '@roo-code/types@1.52.0': dependencies: zod: 3.25.76 @@ -13555,7 +13555,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.50)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) '@vitest/utils@3.2.4': dependencies: diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 204348d028..a167d43798 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -21,16 +21,21 @@ import { type ClineMessage, type ClineSay, type ClineAsk, - type BlockingAsk, + type IdleAsk, + type ResumableAsk, + type InteractiveAsk, type ToolProgressStatus, type HistoryItem, RooCodeEventName, TelemetryEventName, + TaskStatus, TodoItem, + DEFAULT_CONSECUTIVE_MISTAKE_LIMIT, getApiProtocol, getModelId, - DEFAULT_CONSECUTIVE_MISTAKE_LIMIT, - isBlockingAsk, + isIdleAsk, + isInteractiveAsk, + isResumableAsk, } from "@roo-code/types" import { TelemetryService } from "@roo-code/telemetry" import { CloudService, ExtensionBridgeService } from "@roo-code/cloud" @@ -182,7 +187,12 @@ export class Task extends EventEmitter implements TaskLike { providerRef: WeakRef private readonly globalStoragePath: string abort: boolean = false - blockingAsk?: BlockingAsk + + // TaskStatus + idleAsk?: ClineMessage + resumableAsk?: ClineMessage + interactiveAsk?: ClineMessage + didFinishAbortingStream = false abandoned = false isInitialized = false @@ -290,7 +300,6 @@ export class Task extends EventEmitter implements TaskLike { this.taskId = historyItem ? historyItem.id : crypto.randomUUID() this.metadata = { - taskId: this.taskId, task: historyItem ? historyItem.task : task, images: historyItem ? [] : images, } @@ -497,6 +506,7 @@ export class Task extends EventEmitter implements TaskLike { if (this._taskMode === undefined) { throw new Error("Task mode accessed before initialization. Use getTaskMode() or wait for taskModeReady.") } + return this._taskMode } @@ -615,6 +625,16 @@ export class Task extends EventEmitter implements TaskLike { } } + private findMessageByTimestamp(ts: number): ClineMessage | undefined { + for (let i = this.clineMessages.length - 1; i >= 0; i--) { + if (this.clineMessages[i].ts === ts) { + return this.clineMessages[i] + } + } + + return undefined + } + // Note that `partial` has three valid states true (partial message), // false (completion of partial message), undefined (individual complete // message). @@ -713,16 +733,55 @@ export class Task extends EventEmitter implements TaskLike { await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text, isProtected }) } - // Detect if the task will enter an idle state. - const isReady = this.askResponse !== undefined || this.lastMessageTs !== askTs + // The state is mutable if the message is complete and the task will + // block (via the `pWaitFor`). + const isBlocking = !(this.askResponse !== undefined || this.lastMessageTs !== askTs) + const isStatusMutable = !partial && isBlocking + let statusMutationTimeouts: NodeJS.Timeout[] = [] - if (!partial && !isReady && isBlockingAsk(type)) { - this.blockingAsk = type - this.emit(RooCodeEventName.TaskIdle, this.taskId) + if (isStatusMutable) { + if (isInteractiveAsk(type)) { + statusMutationTimeouts.push( + setTimeout(() => { + const message = this.findMessageByTimestamp(askTs) + + if (message) { + this.interactiveAsk = message + this.emit(RooCodeEventName.TaskInteractive, this.taskId) + } + }, 1_000), + ) + } else if (isResumableAsk(type)) { + statusMutationTimeouts.push( + setTimeout(() => { + const message = this.findMessageByTimestamp(askTs) + + if (message) { + this.resumableAsk = message + this.emit(RooCodeEventName.TaskResumable, this.taskId) + } + }, 1_000), + ) + } else if (isIdleAsk(type)) { + statusMutationTimeouts.push( + setTimeout(() => { + const message = this.findMessageByTimestamp(askTs) + + if (message) { + this.idleAsk = message + this.emit(RooCodeEventName.TaskIdle, this.taskId) + } + }, 1_000), + ) + } } - console.log(`[Task#${this.taskId}] pWaitFor askResponse(${type}) -> blocking`) + console.log( + `[Task#${this.taskId}] pWaitFor askResponse(${type}) -> blocking (isStatusMutable = ${isStatusMutable}, statusMutationTimeouts = ${statusMutationTimeouts.length})`, + ) + await pWaitFor(() => this.askResponse !== undefined || this.lastMessageTs !== askTs, { interval: 100 }) + console.log(`[Task#${this.taskId}] pWaitFor askResponse(${type}) -> unblocked (${this.askResponse})`) if (this.lastMessageTs !== askTs) { @@ -737,9 +796,14 @@ export class Task extends EventEmitter implements TaskLike { this.askResponseText = undefined this.askResponseImages = undefined + // Cancel the timeouts if they are still running. + statusMutationTimeouts.forEach((timeout) => clearTimeout(timeout)) + // Switch back to an active state. - if (this.blockingAsk) { - this.blockingAsk = undefined + if (this.idleAsk || this.resumableAsk || this.interactiveAsk) { + this.idleAsk = undefined + this.resumableAsk = undefined + this.interactiveAsk = undefined this.emit(RooCodeEventName.TaskActive, this.taskId) } @@ -757,27 +821,30 @@ export class Task extends EventEmitter implements TaskLike { this.askResponseImages = images } + public approveAsk({ text, images }: { text?: string; images?: string[] } = {}) { + this.handleWebviewAskResponse("yesButtonClicked", text, images) + } + + public denyAsk({ text, images }: { text?: string; images?: string[] } = {}) { + this.handleWebviewAskResponse("noButtonClicked", text, images) + } + public submitUserMessage(text: string, images?: string[]): void { try { - const trimmed = (text ?? "").trim() - const imgs = images ?? [] + text = (text ?? "").trim() + images = images ?? [] - if (!trimmed && imgs.length === 0) { + if (text.length === 0 && images.length === 0) { return } const provider = this.providerRef.deref() - if (!provider) { - console.error("[Task#submitUserMessage] Provider reference lost") - return - } - void provider.postMessageToWebview({ - type: "invoke", - invoke: "sendMessage", - text: trimmed, - images: imgs, - }) + if (provider) { + provider.postMessageToWebview({ type: "invoke", invoke: "sendMessage", text, images }) + } else { + console.error("[Task#submitUserMessage] Provider reference lost") + } } catch (error) { console.error("[Task#submitUserMessage] Failed to submit user message:", error) } @@ -1030,12 +1097,11 @@ export class Task extends EventEmitter implements TaskLike { } public async resumePausedTask(lastMessage: string) { - // Release this Cline instance from paused state. this.isPaused = false this.emit(RooCodeEventName.TaskUnpaused) // Fake an answer from the subtask that it has completed running and - // this is the result of what it has done add the message to the chat + // this is the result of what it has done add the message to the chat // history and to the webview ui. try { await this.say("subtask_result", lastMessage) @@ -2520,4 +2586,24 @@ export class Task extends EventEmitter implements TaskLike { public get cwd() { return this.workspacePath } + + public get taskStatus(): TaskStatus { + if (this.interactiveAsk) { + return TaskStatus.Interactive + } + + if (this.resumableAsk) { + return TaskStatus.Resumable + } + + if (this.idleAsk) { + return TaskStatus.Idle + } + + return TaskStatus.Running + } + + public get taskAsk(): ClineMessage | undefined { + return this.idleAsk || this.resumableAsk || this.interactiveAsk + } } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 8c4a32c3c8..04d336d957 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -29,6 +29,7 @@ import { type TerminalActionId, type TerminalActionPromptType, type HistoryItem, + type ClineAsk, RooCodeEventName, requestyDefaultModelId, openRouterDefaultModelId, @@ -176,6 +177,8 @@ export class ClineProvider const onTaskFocused = () => this.emit(RooCodeEventName.TaskFocused, instance.taskId) const onTaskUnfocused = () => this.emit(RooCodeEventName.TaskUnfocused, instance.taskId) const onTaskActive = (taskId: string) => this.emit(RooCodeEventName.TaskActive, taskId) + const onTaskInteractive = (taskId: string) => this.emit(RooCodeEventName.TaskInteractive, taskId) + const onTaskResumable = (taskId: string) => this.emit(RooCodeEventName.TaskResumable, taskId) const onTaskIdle = (taskId: string) => this.emit(RooCodeEventName.TaskIdle, taskId) // Attach the listeners. @@ -185,6 +188,8 @@ export class ClineProvider instance.on(RooCodeEventName.TaskFocused, onTaskFocused) instance.on(RooCodeEventName.TaskUnfocused, onTaskUnfocused) instance.on(RooCodeEventName.TaskActive, onTaskActive) + instance.on(RooCodeEventName.TaskInteractive, onTaskInteractive) + instance.on(RooCodeEventName.TaskResumable, onTaskResumable) instance.on(RooCodeEventName.TaskIdle, onTaskIdle) // Store the cleanup functions for later removal. @@ -195,6 +200,8 @@ export class ClineProvider () => instance.off(RooCodeEventName.TaskFocused, onTaskFocused), () => instance.off(RooCodeEventName.TaskUnfocused, onTaskUnfocused), () => instance.off(RooCodeEventName.TaskActive, onTaskActive), + () => instance.off(RooCodeEventName.TaskInteractive, onTaskInteractive), + () => instance.off(RooCodeEventName.TaskResumable, onTaskResumable), () => instance.off(RooCodeEventName.TaskIdle, onTaskIdle), ]) } diff --git a/src/package.json b/src/package.json index 4928f450b3..e011ffdc7c 100644 --- a/src/package.json +++ b/src/package.json @@ -427,7 +427,7 @@ "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.9.0", "@qdrant/js-client-rest": "^1.14.0", - "@roo-code/cloud": "^0.16.0", + "@roo-code/cloud": "^0.17.0", "@roo-code/ipc": "workspace:^", "@roo-code/telemetry": "workspace:^", "@roo-code/types": "workspace:^", From 6b57c1bd3d53a95c5b02a769c038b73aee1cd6c8 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sat, 16 Aug 2025 22:51:42 -0400 Subject: [PATCH 08/34] Release v3.25.16 (#7151) --- .changeset/v3.25.16.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .changeset/v3.25.16.md diff --git a/.changeset/v3.25.16.md b/.changeset/v3.25.16.md new file mode 100644 index 0000000000..a04c5dfbb5 --- /dev/null +++ b/.changeset/v3.25.16.md @@ -0,0 +1,15 @@ +--- +"roo-cline": patch +--- + +- Add support for OpenAI gpt-5-chat-latest model (#7057 by @PeterDaveHello, PR by @app/roomote) +- Fix: Use native Ollama API instead of OpenAI compatibility layer (#7070 by @LivioGama, PR by @daniel-lxs) +- Fix: Prevent XML entity decoding in diff tools (#7107 by @indiesewell, PR by @app/roomote) +- Fix: Add type check before calling .match() on diffItem.content (#6905 by @pwilkin, PR by @app/roomote) +- Refactor task execution system: improve call stack management (thanks @catrielmuller!) +- Fix: Enable save button for provider dropdown and checkbox changes (thanks @daniel-lxs!) +- Add an API for resuming tasks by ID (thanks @mrubens!) +- Emit event when a task ask requires interaction (thanks @cte!) +- Make enhance with task history default to true (thanks @liwilliam2021!) +- Fix: Use cline.cwd as primary source for workspace path in codebaseSearchTool (thanks @NaccOll!) +- Hotfix multiple folder workspace checkpoint (thanks @NaccOll!) From 0d90facc53ddbcae1f0d1770a26d2ef055f8d3e6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 16 Aug 2025 19:54:59 -0700 Subject: [PATCH 09/34] Changeset version bump (#7152) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matt Rubens --- .changeset/v3.25.16.md | 15 --------------- CHANGELOG.md | 14 ++++++++++++++ src/package.json | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) delete mode 100644 .changeset/v3.25.16.md diff --git a/.changeset/v3.25.16.md b/.changeset/v3.25.16.md deleted file mode 100644 index a04c5dfbb5..0000000000 --- a/.changeset/v3.25.16.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"roo-cline": patch ---- - -- Add support for OpenAI gpt-5-chat-latest model (#7057 by @PeterDaveHello, PR by @app/roomote) -- Fix: Use native Ollama API instead of OpenAI compatibility layer (#7070 by @LivioGama, PR by @daniel-lxs) -- Fix: Prevent XML entity decoding in diff tools (#7107 by @indiesewell, PR by @app/roomote) -- Fix: Add type check before calling .match() on diffItem.content (#6905 by @pwilkin, PR by @app/roomote) -- Refactor task execution system: improve call stack management (thanks @catrielmuller!) -- Fix: Enable save button for provider dropdown and checkbox changes (thanks @daniel-lxs!) -- Add an API for resuming tasks by ID (thanks @mrubens!) -- Emit event when a task ask requires interaction (thanks @cte!) -- Make enhance with task history default to true (thanks @liwilliam2021!) -- Fix: Use cline.cwd as primary source for workspace path in codebaseSearchTool (thanks @NaccOll!) -- Hotfix multiple folder workspace checkpoint (thanks @NaccOll!) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c97f6fcb2..7d5f5f0f96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Roo Code Changelog +## [3.25.16] - 2025-08-16 + +- Add support for OpenAI gpt-5-chat-latest model (#7057 by @PeterDaveHello, PR by @app/roomote) +- Fix: Use native Ollama API instead of OpenAI compatibility layer (#7070 by @LivioGama, PR by @daniel-lxs) +- Fix: Prevent XML entity decoding in diff tools (#7107 by @indiesewell, PR by @app/roomote) +- Fix: Add type check before calling .match() on diffItem.content (#6905 by @pwilkin, PR by @app/roomote) +- Refactor task execution system: improve call stack management (thanks @catrielmuller!) +- Fix: Enable save button for provider dropdown and checkbox changes (thanks @daniel-lxs!) +- Add an API for resuming tasks by ID (thanks @mrubens!) +- Emit event when a task ask requires interaction (thanks @cte!) +- Make enhance with task history default to true (thanks @liwilliam2021!) +- Fix: Use cline.cwd as primary source for workspace path in codebaseSearchTool (thanks @NaccOll!) +- Hotfix multiple folder workspace checkpoint (thanks @NaccOll!) + ## [3.25.15] - 2025-08-14 - Fix: Remove 500-message limit to prevent scrollbar jumping in long conversations (#7052, #7063 by @daniel-lxs, PR by @app/roomote) diff --git a/src/package.json b/src/package.json index e011ffdc7c..a59503de7f 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "RooVeterinaryInc", - "version": "3.25.15", + "version": "3.25.16", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", From 185365af5d738f24c10780225a8202d2922ea59f Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 17 Aug 2025 02:10:59 -0400 Subject: [PATCH 10/34] Fix terminal reuse logic (#7157) --- .../tools/__tests__/executeCommand.spec.ts | 28 +++---------------- src/core/tools/executeCommandTool.ts | 2 +- src/integrations/terminal/TerminalRegistry.ts | 8 ------ 3 files changed, 5 insertions(+), 33 deletions(-) diff --git a/src/core/tools/__tests__/executeCommand.spec.ts b/src/core/tools/__tests__/executeCommand.spec.ts index 68dec5c456..2e973a24cb 100644 --- a/src/core/tools/__tests__/executeCommand.spec.ts +++ b/src/core/tools/__tests__/executeCommand.spec.ts @@ -213,12 +213,7 @@ describe("executeCommand", () => { // Verify expect(rejected).toBe(false) - expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith( - customCwd, - true, // customCwd provided - mockTask.taskId, - "vscode", - ) + expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith(customCwd, mockTask.taskId, "vscode") expect(result).toContain(`within working directory '${customCwd}'`) }) @@ -248,12 +243,7 @@ describe("executeCommand", () => { // Verify expect(rejected).toBe(false) - expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith( - resolvedCwd, - true, // customCwd provided - mockTask.taskId, - "vscode", - ) + expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith(resolvedCwd, mockTask.taskId, "vscode") expect(result).toContain(`within working directory '${resolvedCwd.toPosix()}'`) }) @@ -302,12 +292,7 @@ describe("executeCommand", () => { await executeCommand(mockTask, options) // Verify - expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith( - mockTask.cwd, - false, // no customCwd - mockTask.taskId, - "vscode", - ) + expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith(mockTask.cwd, mockTask.taskId, "vscode") }) it("should use execa provider when shell integration is disabled", async () => { @@ -330,12 +315,7 @@ describe("executeCommand", () => { await executeCommand(mockTask, options) // Verify - expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith( - mockTask.cwd, - false, // no customCwd - mockTask.taskId, - "execa", - ) + expect(TerminalRegistry.getOrCreateTerminal).toHaveBeenCalledWith(mockTask.cwd, mockTask.taskId, "execa") }) }) diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts index c346526a2e..2c7ce0d023 100644 --- a/src/core/tools/executeCommandTool.ts +++ b/src/core/tools/executeCommandTool.ts @@ -238,7 +238,7 @@ export async function executeCommand( } } - const terminal = await TerminalRegistry.getOrCreateTerminal(workingDir, !!customCwd, task.taskId, terminalProvider) + const terminal = await TerminalRegistry.getOrCreateTerminal(workingDir, task.taskId, terminalProvider) if (terminal instanceof Terminal) { terminal.terminal.show(true) diff --git a/src/integrations/terminal/TerminalRegistry.ts b/src/integrations/terminal/TerminalRegistry.ts index af334611c3..6e0531bebe 100644 --- a/src/integrations/terminal/TerminalRegistry.ts +++ b/src/integrations/terminal/TerminalRegistry.ts @@ -146,13 +146,11 @@ export class TerminalRegistry { * directory. * * @param cwd The working directory path - * @param requiredCwd Whether the working directory is required (if false, may reuse any non-busy terminal) * @param taskId Optional task ID to associate with the terminal * @returns A Terminal instance */ public static async getOrCreateTerminal( cwd: string, - requiredCwd: boolean = false, taskId?: string, provider: RooTerminalProvider = "vscode", ): Promise { @@ -194,12 +192,6 @@ export class TerminalRegistry { }) } - // Third priority: Find any non-busy terminal (only if directory is not - // required). - if (!terminal && !requiredCwd) { - terminal = terminals.find((t) => !t.busy && t.provider === provider) - } - // If no suitable terminal found, create a new one. if (!terminal) { terminal = this.createTerminal(cwd, provider) From fae811cc624429d8d17a13624991cf3749cee4ab Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 17 Aug 2025 22:22:33 -0700 Subject: [PATCH 11/34] Release v3.25.17 (#7169) --- .changeset/v3.25.17.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/v3.25.17.md diff --git a/.changeset/v3.25.17.md b/.changeset/v3.25.17.md new file mode 100644 index 0000000000..d34746e6bd --- /dev/null +++ b/.changeset/v3.25.17.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +- Fix: Resolve terminal reuse logic issues (thanks @mrubens!) From d941cee0557cce5f50579d24413dc9a84f96e313 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 17 Aug 2025 22:26:16 -0700 Subject: [PATCH 12/34] Changeset version bump (#7170) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matt Rubens --- .changeset/v3.25.17.md | 5 ----- CHANGELOG.md | 4 ++++ src/package.json | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) delete mode 100644 .changeset/v3.25.17.md diff --git a/.changeset/v3.25.17.md b/.changeset/v3.25.17.md deleted file mode 100644 index d34746e6bd..0000000000 --- a/.changeset/v3.25.17.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -- Fix: Resolve terminal reuse logic issues (thanks @mrubens!) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d5f5f0f96..cb6f5713c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Roo Code Changelog +## [3.25.17] - 2025-08-17 + +- Fix: Resolve terminal reuse logic issues + ## [3.25.16] - 2025-08-16 - Add support for OpenAI gpt-5-chat-latest model (#7057 by @PeterDaveHello, PR by @app/roomote) diff --git a/src/package.json b/src/package.json index a59503de7f..48e785d7b9 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "RooVeterinaryInc", - "version": "3.25.16", + "version": "3.25.17", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", From a8aea1407807b97a452796b016b7dc35ab0cf646 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 17 Aug 2025 23:30:45 -0700 Subject: [PATCH 13/34] chore: bump version to v1.53.0 (#7171) --- packages/types/npm/package.metadata.json | 2 +- packages/types/src/task.ts | 1 + src/core/task/Task.ts | 3 - src/core/task/__tests__/Task.spec.ts | 99 ++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 4 deletions(-) diff --git a/packages/types/npm/package.metadata.json b/packages/types/npm/package.metadata.json index a40b43aacc..b093d00a8f 100644 --- a/packages/types/npm/package.metadata.json +++ b/packages/types/npm/package.metadata.json @@ -1,6 +1,6 @@ { "name": "@roo-code/types", - "version": "1.52.0", + "version": "1.53.0", "description": "TypeScript type definitions for Roo Code.", "publishConfig": { "access": "public", diff --git a/packages/types/src/task.ts b/packages/types/src/task.ts index 96daac424b..3f741fc6dd 100644 --- a/packages/types/src/task.ts +++ b/packages/types/src/task.ts @@ -92,6 +92,7 @@ export interface TaskLike { approveAsk(options?: { text?: string; images?: string[] }): void denyAsk(options?: { text?: string; images?: string[] }): void submitUserMessage(text: string, images?: string[]): void + abortTask(): void } export type TaskEvents = { diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index a167d43798..cff8d5aec3 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -21,9 +21,6 @@ import { type ClineMessage, type ClineSay, type ClineAsk, - type IdleAsk, - type ResumableAsk, - type InteractiveAsk, type ToolProgressStatus, type HistoryItem, RooCodeEventName, diff --git a/src/core/task/__tests__/Task.spec.ts b/src/core/task/__tests__/Task.spec.ts index 39df433814..01469ddbf5 100644 --- a/src/core/task/__tests__/Task.spec.ts +++ b/src/core/task/__tests__/Task.spec.ts @@ -1614,4 +1614,103 @@ describe("Cline", () => { }) }) }) + + describe("abortTask", () => { + it("should set abort flag and emit TaskAborted event", async () => { + const task = new Task({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "test task", + startTask: false, + }) + + // Spy on emit method + const emitSpy = vi.spyOn(task, "emit") + + // Mock the dispose method to avoid actual cleanup + vi.spyOn(task, "dispose").mockImplementation(() => {}) + + // Call abortTask + await task.abortTask() + + // Verify abort flag is set + expect(task.abort).toBe(true) + + // Verify TaskAborted event was emitted + expect(emitSpy).toHaveBeenCalledWith("taskAborted") + }) + + it("should be equivalent to clicking Cancel button functionality", async () => { + const task = new Task({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "test task", + startTask: false, + }) + + // Mock the dispose method to track cleanup + const disposeSpy = vi.spyOn(task, "dispose").mockImplementation(() => {}) + + // Call abortTask + await task.abortTask() + + // Verify the same behavior as Cancel button + expect(task.abort).toBe(true) + expect(disposeSpy).toHaveBeenCalled() + }) + + it("should work with TaskLike interface", async () => { + const task = new Task({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "test task", + startTask: false, + }) + + // Cast to TaskLike to ensure interface compliance + const taskLike = task as any // TaskLike interface from types package + + // Verify abortTask method exists and is callable + expect(typeof taskLike.abortTask).toBe("function") + + // Mock the dispose method to avoid actual cleanup + vi.spyOn(task, "dispose").mockImplementation(() => {}) + + // Call abortTask through interface + await taskLike.abortTask() + + // Verify it works + expect(task.abort).toBe(true) + }) + + it("should handle errors during disposal gracefully", async () => { + const task = new Task({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "test task", + startTask: false, + }) + + // Mock dispose to throw an error + const mockError = new Error("Disposal failed") + vi.spyOn(task, "dispose").mockImplementation(() => { + throw mockError + }) + + // Spy on console.error to verify error is logged + const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) + + // abortTask should not throw even if dispose fails + await expect(task.abortTask()).resolves.not.toThrow() + + // Verify error was logged + expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Error during task"), mockError) + + // Verify abort flag is still set + expect(task.abort).toBe(true) + + // Restore console.error + consoleErrorSpy.mockRestore() + }) + }) }) From e7e827a3f88919d011abd39bc5ba4119a7232e02 Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Mon, 18 Aug 2025 10:17:43 -0500 Subject: [PATCH 14/34] fix: prevent duplicate LM Studio models with case-insensitive deduplication (#7185) * fix: prevent duplicate LM Studio models with case-insensitive deduplication - Keep both listDownloadedModels and listLoaded APIs to support JIT loading - Implement case-insensitive deduplication to prevent duplicates - When duplicates are found, prefer loaded model data for accurate runtime info - Add test coverage for deduplication logic - Addresses feedback about LM Studio's JIT Model Loading feature (v0.3.5+) Fixes #6954 * fix: correct deduplication logic to prefer loaded models - When a loaded model ID is found in any downloaded model key (case-insensitive) - Remove the downloaded model and replace with the loaded model - This ensures loaded models with runtime info take precedence - Updated tests to verify the correct deduplication behavior * fix: improve deduplication logic and add comprehensive test coverage - Enhanced deduplication to use path segment matching instead of simple substring - Prevents false positives like 'llama' matching 'codellama' - Added comprehensive test cases for edge cases and multiple scenarios - Maintains support for JIT Model Loading feature --- .../fetchers/__tests__/lmstudio.test.ts | 222 ++++++++++++++++++ src/api/providers/fetchers/lmstudio.ts | 28 ++- 2 files changed, 249 insertions(+), 1 deletion(-) diff --git a/src/api/providers/fetchers/__tests__/lmstudio.test.ts b/src/api/providers/fetchers/__tests__/lmstudio.test.ts index ff9a109e50..8e7e36c73f 100644 --- a/src/api/providers/fetchers/__tests__/lmstudio.test.ts +++ b/src/api/providers/fetchers/__tests__/lmstudio.test.ts @@ -143,6 +143,228 @@ describe("LMStudio Fetcher", () => { expect(result).toEqual({ [mockRawModel.modelKey]: expectedParsedModel }) }) + it("should deduplicate models when both downloaded and loaded", async () => { + const mockDownloadedModel: LLMInfo = { + type: "llm" as const, + modelKey: "mistralai/devstral-small-2505", + format: "safetensors", + displayName: "Devstral Small 2505", + path: "mistralai/devstral-small-2505", + sizeBytes: 13277565112, + architecture: "mistral", + vision: false, + trainedForToolUse: false, + maxContextLength: 131072, + } + + const mockLoadedModel: LLMInstanceInfo = { + type: "llm", + modelKey: "devstral-small-2505", // Different key but should match case-insensitively + format: "safetensors", + displayName: "Devstral Small 2505", + path: "mistralai/devstral-small-2505", + sizeBytes: 13277565112, + architecture: "mistral", + identifier: "mistralai/devstral-small-2505", + instanceReference: "RAP5qbeHVjJgBiGFQ6STCuTJ", + vision: false, + trainedForToolUse: false, + maxContextLength: 131072, + contextLength: 7161, // Runtime context info + } + + mockedAxios.get.mockResolvedValueOnce({ data: { status: "ok" } }) + mockListDownloadedModels.mockResolvedValueOnce([mockDownloadedModel]) + mockListLoaded.mockResolvedValueOnce([{ getModelInfo: vi.fn().mockResolvedValueOnce(mockLoadedModel) }]) + + const result = await getLMStudioModels(baseUrl) + + // Should only have one model, with the loaded model replacing the downloaded one + expect(Object.keys(result)).toHaveLength(1) + + // The loaded model's key should be used, with loaded model's data + const expectedParsedModel = parseLMStudioModel(mockLoadedModel) + expect(result[mockLoadedModel.modelKey]).toEqual(expectedParsedModel) + + // The downloaded model should have been removed + expect(result[mockDownloadedModel.path]).toBeUndefined() + }) + + it("should handle deduplication with path-based matching", async () => { + const mockDownloadedModel: LLMInfo = { + type: "llm" as const, + modelKey: "Meta/Llama-3.1/8B-Instruct", + format: "gguf", + displayName: "Llama 3.1 8B Instruct", + path: "Meta/Llama-3.1/8B-Instruct", + sizeBytes: 8000000000, + architecture: "llama", + vision: false, + trainedForToolUse: false, + maxContextLength: 8192, + } + + const mockLoadedModel: LLMInstanceInfo = { + type: "llm", + modelKey: "Llama-3.1", // Should match the path segment + format: "gguf", + displayName: "Llama 3.1", + path: "Meta/Llama-3.1/8B-Instruct", + sizeBytes: 8000000000, + architecture: "llama", + identifier: "Meta/Llama-3.1/8B-Instruct", + instanceReference: "ABC123", + vision: false, + trainedForToolUse: false, + maxContextLength: 8192, + contextLength: 4096, + } + + mockedAxios.get.mockResolvedValueOnce({ data: { status: "ok" } }) + mockListDownloadedModels.mockResolvedValueOnce([mockDownloadedModel]) + mockListLoaded.mockResolvedValueOnce([{ getModelInfo: vi.fn().mockResolvedValueOnce(mockLoadedModel) }]) + + const result = await getLMStudioModels(baseUrl) + + expect(Object.keys(result)).toHaveLength(1) + expect(result[mockLoadedModel.modelKey]).toBeDefined() + expect(result[mockDownloadedModel.path]).toBeUndefined() + }) + + it("should not deduplicate models with similar but distinct names", async () => { + const mockDownloadedModels: LLMInfo[] = [ + { + type: "llm" as const, + modelKey: "mistral-7b", + format: "gguf", + displayName: "Mistral 7B", + path: "mistralai/mistral-7b-instruct", + sizeBytes: 7000000000, + architecture: "mistral", + vision: false, + trainedForToolUse: false, + maxContextLength: 4096, + }, + { + type: "llm" as const, + modelKey: "codellama", + format: "gguf", + displayName: "Code Llama", + path: "meta/codellama/7b", + sizeBytes: 7000000000, + architecture: "llama", + vision: false, + trainedForToolUse: false, + maxContextLength: 4096, + }, + ] + + const mockLoadedModel: LLMInstanceInfo = { + type: "llm", + modelKey: "llama", // Should not match "codellama" or "mistral-7b" + format: "gguf", + displayName: "Llama", + path: "meta/llama/7b", + sizeBytes: 7000000000, + architecture: "llama", + identifier: "meta/llama/7b", + instanceReference: "XYZ789", + vision: false, + trainedForToolUse: false, + maxContextLength: 4096, + contextLength: 2048, + } + + mockedAxios.get.mockResolvedValueOnce({ data: { status: "ok" } }) + mockListDownloadedModels.mockResolvedValueOnce(mockDownloadedModels) + mockListLoaded.mockResolvedValueOnce([{ getModelInfo: vi.fn().mockResolvedValueOnce(mockLoadedModel) }]) + + const result = await getLMStudioModels(baseUrl) + + // Should have 3 models: mistral-7b (not deduped), codellama (not deduped), and llama (loaded) + expect(Object.keys(result)).toHaveLength(3) + expect(result["mistralai/mistral-7b-instruct"]).toBeDefined() // Should NOT be removed + expect(result["meta/codellama/7b"]).toBeDefined() // Should NOT be removed (codellama != llama) + expect(result[mockLoadedModel.modelKey]).toBeDefined() + }) + + it("should handle multiple loaded models with various duplicate scenarios", async () => { + const mockDownloadedModels: LLMInfo[] = [ + { + type: "llm" as const, + modelKey: "mistral-7b", + format: "gguf", + displayName: "Mistral 7B", + path: "mistralai/mistral-7b/instruct", + sizeBytes: 7000000000, + architecture: "mistral", + vision: false, + trainedForToolUse: false, + maxContextLength: 8192, + }, + { + type: "llm" as const, + modelKey: "llama-3.1", + format: "gguf", + displayName: "Llama 3.1", + path: "meta/llama-3.1/8b", + sizeBytes: 8000000000, + architecture: "llama", + vision: false, + trainedForToolUse: false, + maxContextLength: 8192, + }, + ] + + const mockLoadedModels: LLMInstanceInfo[] = [ + { + type: "llm", + modelKey: "mistral-7b", // Exact match with path segment + format: "gguf", + displayName: "Mistral 7B", + path: "mistralai/mistral-7b/instruct", + sizeBytes: 7000000000, + architecture: "mistral", + identifier: "mistralai/mistral-7b/instruct", + instanceReference: "REF1", + vision: false, + trainedForToolUse: false, + maxContextLength: 8192, + contextLength: 4096, + }, + { + type: "llm", + modelKey: "gpt-4", // No match, new model + format: "gguf", + displayName: "GPT-4", + path: "openai/gpt-4", + sizeBytes: 10000000000, + architecture: "gpt", + identifier: "openai/gpt-4", + instanceReference: "REF2", + vision: true, + trainedForToolUse: true, + maxContextLength: 32768, + contextLength: 16384, + }, + ] + + mockedAxios.get.mockResolvedValueOnce({ data: { status: "ok" } }) + mockListDownloadedModels.mockResolvedValueOnce(mockDownloadedModels) + mockListLoaded.mockResolvedValueOnce( + mockLoadedModels.map((model) => ({ getModelInfo: vi.fn().mockResolvedValueOnce(model) })), + ) + + const result = await getLMStudioModels(baseUrl) + + // Should have 3 models: llama-3.1 (downloaded), mistral-7b (loaded, replaced), gpt-4 (loaded, new) + expect(Object.keys(result)).toHaveLength(3) + expect(result["meta/llama-3.1/8b"]).toBeDefined() // Downloaded, not replaced + expect(result["mistralai/mistral-7b/instruct"]).toBeUndefined() // Downloaded, replaced + expect(result["mistral-7b"]).toBeDefined() // Loaded, replaced downloaded + expect(result["gpt-4"]).toBeDefined() // Loaded, new + }) + it("should use default baseUrl if an empty string is provided", async () => { const defaultBaseUrl = "http://localhost:1234" const defaultLmsUrl = "ws://localhost:1234" diff --git a/src/api/providers/fetchers/lmstudio.ts b/src/api/providers/fetchers/lmstudio.ts index 976822c67d..1e2e016df2 100644 --- a/src/api/providers/fetchers/lmstudio.ts +++ b/src/api/providers/fetchers/lmstudio.ts @@ -81,12 +81,38 @@ export async function getLMStudioModels(baseUrl = "http://localhost:1234"): Prom } catch (error) { console.warn("Failed to list downloaded models, falling back to loaded models only") } - // We want to list loaded models *anyway* since they provide valuable extra info (context size) + + // Get loaded models for their runtime info (context size) const loadedModels = (await client.llm.listLoaded().then((models: LLM[]) => { return Promise.all(models.map((m) => m.getModelInfo())) })) as Array + // Deduplicate: For each loaded model, check if any downloaded model path contains the loaded model's key + // This handles cases like loaded "llama-3.1" matching downloaded "Meta/Llama-3.1/Something" + // If found, remove the downloaded version and add the loaded model (prefer loaded over downloaded for accurate runtime info) for (const lmstudioModel of loadedModels) { + const loadedModelId = lmstudioModel.modelKey.toLowerCase() + + // Find if any downloaded model path contains the loaded model's key as a path segment + // Use word boundaries or path separators to avoid false matches like "llama" matching "codellama" + const existingKey = Object.keys(models).find((key) => { + const keyLower = key.toLowerCase() + // Check if the loaded model ID appears as a distinct segment in the path + // This matches "llama-3.1" in "Meta/Llama-3.1/Something" but not "llama" in "codellama" + return ( + keyLower.includes(`/${loadedModelId}/`) || + keyLower.includes(`/${loadedModelId}`) || + keyLower.startsWith(`${loadedModelId}/`) || + keyLower === loadedModelId + ) + }) + + if (existingKey) { + // Remove the downloaded version + delete models[existingKey] + } + + // Add the loaded model (either as replacement or new entry) models[lmstudioModel.modelKey] = parseLMStudioModel(lmstudioModel) modelsWithLoadedDetails.add(lmstudioModel.modelKey) } From b975ced81bbe282d026aa83d27c5c53e4334a499 Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Mon, 18 Aug 2025 11:12:16 -0500 Subject: [PATCH 15/34] feat: simplify ask_followup_question prompt documentation (#7191) --- .../architect-mode-prompt.snap | 33 +++++-------------- .../ask-mode-prompt.snap | 33 +++++-------------- .../mcp-server-creation-disabled.snap | 33 +++++-------------- .../mcp-server-creation-enabled.snap | 33 +++++-------------- .../partial-reads-enabled.snap | 33 +++++-------------- .../consistent-system-prompt.snap | 33 +++++-------------- .../with-computer-use-support.snap | 33 +++++-------------- .../with-diff-enabled-false.snap | 33 +++++-------------- .../system-prompt/with-diff-enabled-true.snap | 33 +++++-------------- .../with-diff-enabled-undefined.snap | 33 +++++-------------- .../with-different-viewport-size.snap | 33 +++++-------------- .../system-prompt/with-mcp-hub-provided.snap | 33 +++++-------------- .../system-prompt/with-undefined-mcp-hub.snap | 33 +++++-------------- .../prompts/tools/ask-followup-question.ts | 33 +++++-------------- 14 files changed, 112 insertions(+), 350 deletions(-) diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap index 632273dea0..cfec773b44 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/architect-mode-prompt.snap @@ -270,29 +270,22 @@ Examples: ## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -302,16 +295,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - - - ## attempt_completion Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap index 09b6b04348..0289fc2200 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/ask-mode-prompt.snap @@ -167,29 +167,22 @@ Examples: ## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -199,16 +192,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - - - ## attempt_completion Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap index b1fdcc2e32..f16c11a97f 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap @@ -269,29 +269,22 @@ Examples: ## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -301,16 +294,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - - - ## attempt_completion Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap index 7ca32b80a1..168dd87d59 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap @@ -319,29 +319,22 @@ Example: Requesting to access an MCP resource ## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -351,16 +344,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - - - ## attempt_completion Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap index 7dce6219f3..bafff056b5 100644 --- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap +++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/partial-reads-enabled.snap @@ -275,29 +275,22 @@ Examples: ## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -307,16 +300,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - - - ## attempt_completion Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap index 632273dea0..cfec773b44 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/consistent-system-prompt.snap @@ -270,29 +270,22 @@ Examples: ## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -302,16 +295,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - - - ## attempt_completion Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap index 419049609e..fb1fb5c77e 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-computer-use-support.snap @@ -323,29 +323,22 @@ Example: Requesting to click on the element at coordinates 450,300 ## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -355,16 +348,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - - - ## attempt_completion Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap index 632273dea0..cfec773b44 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-false.snap @@ -270,29 +270,22 @@ Examples: ## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -302,16 +295,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - - - ## attempt_completion Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap index 7a60f1d403..7c0108ff75 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-true.snap @@ -358,29 +358,22 @@ Examples: ## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -390,16 +383,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - - - ## attempt_completion Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap index 632273dea0..cfec773b44 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-diff-enabled-undefined.snap @@ -270,29 +270,22 @@ Examples: ## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -302,16 +295,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - - - ## attempt_completion Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap index 191816f180..bf764fdf83 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-different-viewport-size.snap @@ -323,29 +323,22 @@ Example: Requesting to click on the element at coordinates 450,300 ## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -355,16 +348,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - - - ## attempt_completion Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap index 7ca32b80a1..168dd87d59 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap @@ -319,29 +319,22 @@ Example: Requesting to access an MCP resource ## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -351,16 +344,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - - - ## attempt_completion Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap index 632273dea0..cfec773b44 100644 --- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap +++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-undefined-mcp-hub.snap @@ -270,29 +270,22 @@ Examples: ## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -302,16 +295,6 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - - - ## attempt_completion Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again. IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. diff --git a/src/core/prompts/tools/ask-followup-question.ts b/src/core/prompts/tools/ask-followup-question.ts index c69e5a697f..c40684b8bc 100644 --- a/src/core/prompts/tools/ask-followup-question.ts +++ b/src/core/prompts/tools/ask-followup-question.ts @@ -1,28 +1,21 @@ export function getAskFollowupQuestionDescription(): string { return `## 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. +Description: Ask the user a question to gather additional information needed to complete the task. Use when you need clarification or more details to proceed effectively. + Parameters: -- question: (required) The question to ask the user. This should be a clear, specific question that addresses the information you need. -- follow_up: (required) A list of 2-4 suggested answers that logically follow from the question, ordered by priority or logical sequence. Each suggestion must: - 1. Be provided in its own tag - 2. Be specific, actionable, and directly related to the completed task - 3. Be a complete answer to the question - the user should not need to provide additional information or fill in any missing details. DO NOT include placeholders with brackets or parentheses. - 4. Optionally include a mode attribute to switch to a specific mode when the suggestion is selected: suggestion text - - When using the mode attribute, focus the suggestion text on the action to be taken rather than mentioning the mode switch, as the mode change is handled automatically and indicated by a visual badge +- question: (required) A clear, specific question addressing the information needed +- follow_up: (required) A list of 2-4 suggested answers, each in its own tag. Suggestions must be complete, actionable answers without placeholders. Optionally include mode attribute to switch modes (code/architect/etc.) + Usage: Your question here - -Your suggested answer here - - -Implement the solution - +First suggestion +Action with mode switch -Example: Requesting to ask the user for the path to the frontend-config.json file +Example: What is the path to the frontend-config.json file? @@ -30,15 +23,5 @@ Example: Requesting to ask the user for the path to the frontend-config.json fil ./config/frontend-config.json ./frontend-config.json - - -Example: Asking a question with mode switching options - -How would you like to proceed with this task? - -Start implementing the solution -Plan the architecture first -Continue with more details - ` } From 87c42c1f268e63a5c4ed5bb18d7f3a62181aa868 Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 15:07:06 -0700 Subject: [PATCH 16/34] fix: respect enableReasoningEffort setting when determining reasoning usage (#7049) Co-authored-by: Matt Rubens Co-authored-by: Roo Code Co-authored-by: Daniel <57051444+daniel-lxs@users.noreply.github.com> --- src/shared/__tests__/api.spec.ts | 31 ++++++++++++++++++++++++++++++- src/shared/api.ts | 12 +++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/shared/__tests__/api.spec.ts b/src/shared/__tests__/api.spec.ts index a56fea8e14..7c25fe4197 100644 --- a/src/shared/__tests__/api.spec.ts +++ b/src/shared/__tests__/api.spec.ts @@ -375,12 +375,41 @@ describe("shouldUseReasoningEffort", () => { reasoningEffort: "medium", } - // Should return true regardless of settings + // Should return true regardless of settings (unless explicitly disabled) expect(shouldUseReasoningEffort({ model })).toBe(true) expect(shouldUseReasoningEffort({ model, settings: {} })).toBe(true) expect(shouldUseReasoningEffort({ model, settings: { reasoningEffort: undefined } })).toBe(true) }) + test("should return false when enableReasoningEffort is false, even if reasoningEffort is set", () => { + const model: ModelInfo = { + contextWindow: 200_000, + supportsPromptCache: true, + supportsReasoningEffort: true, + } + + const settings: ProviderSettings = { + enableReasoningEffort: false, + reasoningEffort: "medium", + } + + expect(shouldUseReasoningEffort({ model, settings })).toBe(false) + }) + + test("should return false when enableReasoningEffort is false, even if model has reasoningEffort property", () => { + const model: ModelInfo = { + contextWindow: 200_000, + supportsPromptCache: true, + reasoningEffort: "medium", + } + + const settings: ProviderSettings = { + enableReasoningEffort: false, + } + + expect(shouldUseReasoningEffort({ model, settings })).toBe(false) + }) + test("should return true when model supports reasoning effort and settings provide reasoning effort", () => { const model: ModelInfo = { contextWindow: 200_000, diff --git a/src/shared/api.ts b/src/shared/api.ts index f1bf7dbaea..274779fc16 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -63,7 +63,17 @@ export const shouldUseReasoningEffort = ({ }: { model: ModelInfo settings?: ProviderSettings -}): boolean => (!!model.supportsReasoningEffort && !!settings?.reasoningEffort) || !!model.reasoningEffort +}): boolean => { + // If enableReasoningEffort is explicitly set to false, reasoning should be disabled + if (settings?.enableReasoningEffort === false) { + return false + } + + // Otherwise, use reasoning if: + // 1. Model supports reasoning effort AND settings provide reasoning effort, OR + // 2. Model itself has a reasoningEffort property + return (!!model.supportsReasoningEffort && !!settings?.reasoningEffort) || !!model.reasoningEffort +} export const DEFAULT_HYBRID_REASONING_MODEL_MAX_TOKENS = 16_384 export const DEFAULT_HYBRID_REASONING_MODEL_THINKING_TOKENS = 8_192 From fd3535c21a8df4f9c7b0c2cbc37092aa639f9fdb Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 19 Aug 2025 00:53:32 -0700 Subject: [PATCH 17/34] Add support for Sonic model (#7207) Co-authored-by: cte --- packages/types/src/provider-settings.ts | 7 + packages/types/src/providers/index.ts | 1 + packages/types/src/providers/roo.ts | 19 + pnpm-lock.yaml | 18 +- src/api/index.ts | 3 + src/api/providers/__tests__/roo.spec.ts | 436 ++++++++++++++++++ src/api/providers/index.ts | 1 + src/api/providers/roo.ts | 53 +++ src/i18n/locales/ca/common.json | 3 + src/i18n/locales/de/common.json | 3 + src/i18n/locales/en/common.json | 3 + src/i18n/locales/es/common.json | 3 + src/i18n/locales/fr/common.json | 3 + src/i18n/locales/hi/common.json | 3 + src/i18n/locales/id/common.json | 3 + src/i18n/locales/it/common.json | 3 + src/i18n/locales/ja/common.json | 3 + src/i18n/locales/ko/common.json | 3 + src/i18n/locales/nl/common.json | 3 + src/i18n/locales/pl/common.json | 3 + src/i18n/locales/pt-BR/common.json | 3 + src/i18n/locales/ru/common.json | 3 + src/i18n/locales/tr/common.json | 3 + src/i18n/locales/vi/common.json | 3 + src/i18n/locales/zh-CN/common.json | 3 + src/i18n/locales/zh-TW/common.json | 3 + src/package.json | 2 +- .../src/components/settings/ApiOptions.tsx | 25 +- .../settings/ModelDescriptionMarkdown.tsx | 2 +- .../src/components/settings/ModelInfoView.tsx | 30 +- .../src/components/settings/constants.ts | 3 + .../components/ui/hooks/useSelectedModel.ts | 7 + webview-ui/src/i18n/locales/ca/chat.json | 4 +- webview-ui/src/i18n/locales/ca/settings.json | 4 + webview-ui/src/i18n/locales/de/chat.json | 4 +- webview-ui/src/i18n/locales/de/settings.json | 4 + webview-ui/src/i18n/locales/en/chat.json | 4 +- webview-ui/src/i18n/locales/en/settings.json | 4 + webview-ui/src/i18n/locales/es/chat.json | 4 +- webview-ui/src/i18n/locales/es/settings.json | 4 + webview-ui/src/i18n/locales/fr/chat.json | 4 +- webview-ui/src/i18n/locales/fr/settings.json | 4 + webview-ui/src/i18n/locales/hi/chat.json | 4 +- webview-ui/src/i18n/locales/hi/settings.json | 4 + webview-ui/src/i18n/locales/id/chat.json | 4 +- webview-ui/src/i18n/locales/id/settings.json | 4 + webview-ui/src/i18n/locales/it/chat.json | 4 +- webview-ui/src/i18n/locales/it/settings.json | 4 + webview-ui/src/i18n/locales/ja/chat.json | 4 +- webview-ui/src/i18n/locales/ja/settings.json | 4 + webview-ui/src/i18n/locales/ko/chat.json | 4 +- webview-ui/src/i18n/locales/ko/settings.json | 4 + webview-ui/src/i18n/locales/nl/chat.json | 4 +- webview-ui/src/i18n/locales/nl/settings.json | 4 + webview-ui/src/i18n/locales/pl/chat.json | 4 +- webview-ui/src/i18n/locales/pl/settings.json | 4 + webview-ui/src/i18n/locales/pt-BR/chat.json | 4 +- .../src/i18n/locales/pt-BR/settings.json | 4 + webview-ui/src/i18n/locales/ru/chat.json | 4 +- webview-ui/src/i18n/locales/ru/settings.json | 4 + webview-ui/src/i18n/locales/tr/chat.json | 4 +- webview-ui/src/i18n/locales/tr/settings.json | 4 + webview-ui/src/i18n/locales/vi/chat.json | 4 +- webview-ui/src/i18n/locales/vi/settings.json | 4 + webview-ui/src/i18n/locales/zh-CN/chat.json | 4 +- .../src/i18n/locales/zh-CN/settings.json | 4 + webview-ui/src/i18n/locales/zh-TW/chat.json | 4 +- .../src/i18n/locales/zh-TW/settings.json | 4 + 68 files changed, 739 insertions(+), 66 deletions(-) create mode 100644 packages/types/src/providers/roo.ts create mode 100644 src/api/providers/__tests__/roo.spec.ts create mode 100644 src/api/providers/roo.ts diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index fef7d811a4..c22683117f 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -47,6 +47,7 @@ export const providerNames = [ "zai", "fireworks", "io-intelligence", + "roo", ] as const export const providerNamesSchema = z.enum(providerNames) @@ -288,6 +289,10 @@ const ioIntelligenceSchema = apiModelIdProviderModelSchema.extend({ ioIntelligenceApiKey: z.string().optional(), }) +const rooSchema = apiModelIdProviderModelSchema.extend({ + // No additional fields needed - uses cloud authentication +}) + const defaultSchema = z.object({ apiProvider: z.undefined(), }) @@ -324,6 +329,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv zaiSchema.merge(z.object({ apiProvider: z.literal("zai") })), fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })), ioIntelligenceSchema.merge(z.object({ apiProvider: z.literal("io-intelligence") })), + rooSchema.merge(z.object({ apiProvider: z.literal("roo") })), defaultSchema, ]) @@ -360,6 +366,7 @@ export const providerSettingsSchema = z.object({ ...zaiSchema.shape, ...fireworksSchema.shape, ...ioIntelligenceSchema.shape, + ...rooSchema.shape, ...codebaseIndexProviderSchema.shape, }) diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts index b7f1cd334e..6dff64a979 100644 --- a/packages/types/src/providers/index.ts +++ b/packages/types/src/providers/index.ts @@ -25,3 +25,4 @@ export * from "./xai.js" export * from "./doubao.js" export * from "./zai.js" export * from "./fireworks.js" +export * from "./roo.js" diff --git a/packages/types/src/providers/roo.ts b/packages/types/src/providers/roo.ts new file mode 100644 index 0000000000..a213e35780 --- /dev/null +++ b/packages/types/src/providers/roo.ts @@ -0,0 +1,19 @@ +import type { ModelInfo } from "../model.js" + +// Roo provider with single model +export type RooModelId = "roo/sonic" + +export const rooDefaultModelId: RooModelId = "roo/sonic" + +export const rooModels = { + "roo/sonic": { + maxTokens: 8192, + contextWindow: 262_144, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: + "Stealth coding model with 262K context window, accessible for free through Roo Code Cloud for a limited time. (Note: prompts and completions are logged by the model creator and used to improve the model.)", + }, +} as const satisfies Record diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d615c6e04..c9fc6bb27f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -584,8 +584,8 @@ importers: specifier: ^1.14.0 version: 1.14.0(typescript@5.8.3) '@roo-code/cloud': - specifier: ^0.17.0 - version: 0.17.0 + specifier: ^0.18.0 + version: 0.18.0 '@roo-code/ipc': specifier: workspace:^ version: link:../packages/ipc @@ -3106,11 +3106,11 @@ packages: cpu: [x64] os: [win32] - '@roo-code/cloud@0.17.0': - resolution: {integrity: sha512-Sh7KGbVapxocnoyDzWkvvtVzam5jZnU2RGKT7V0v3CbFMQc329NBnHRAgVtJUwcGPHUFyAkwEHo181Fn3rFpZw==} + '@roo-code/cloud@0.18.0': + resolution: {integrity: sha512-Y2jbcUVB9RCQFAxHDPrfjWQU1o7yRvWaPAdA3eZjsUf+zfDL59Rwfghg6loqDfE/8HCkcJmHfLCKovNX5ju5qA==} - '@roo-code/types@1.52.0': - resolution: {integrity: sha512-jPCVZ2j4Y0MUiHvAJbXduR3yFGb5A3KDvIThi8cKBAUg8zb7jhIYKTvJ5vub1MGZkpK11K0JG6ex4RL19FjobA==} + '@roo-code/types@1.54.0': + resolution: {integrity: sha512-Xj3Zn2FhXbG2bpwXuhrjKnkeuWypQCIPKljOLXnOCUqaMUhP1zkWwNZ+I3gIBUpDng/iWN3KHon1if0UaoXYQw==} '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -12314,9 +12314,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.40.2': optional: true - '@roo-code/cloud@0.17.0': + '@roo-code/cloud@0.18.0': dependencies: - '@roo-code/types': 1.52.0 + '@roo-code/types': 1.54.0 ioredis: 5.6.1 p-wait-for: 5.0.2 socket.io-client: 4.8.1 @@ -12326,7 +12326,7 @@ snapshots: - supports-color - utf-8-validate - '@roo-code/types@1.52.0': + '@roo-code/types@1.54.0': dependencies: zod: 3.25.76 diff --git a/src/api/index.ts b/src/api/index.ts index 92a5c95770..c80fd5bf72 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -35,6 +35,7 @@ import { DoubaoHandler, ZAiHandler, FireworksHandler, + RooHandler, } from "./providers" import { NativeOllamaHandler } from "./providers/native-ollama" @@ -140,6 +141,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler { return new FireworksHandler(options) case "io-intelligence": return new IOIntelligenceHandler(options) + case "roo": + return new RooHandler(options) default: apiProvider satisfies "gemini-cli" | undefined return new AnthropicHandler(options) diff --git a/src/api/providers/__tests__/roo.spec.ts b/src/api/providers/__tests__/roo.spec.ts new file mode 100644 index 0000000000..5c89e8e1ad --- /dev/null +++ b/src/api/providers/__tests__/roo.spec.ts @@ -0,0 +1,436 @@ +// npx vitest run api/providers/__tests__/roo.spec.ts + +import { Anthropic } from "@anthropic-ai/sdk" +import { rooDefaultModelId, rooModels } from "@roo-code/types" + +import { ApiHandlerOptions } from "../../../shared/api" + +// Mock OpenAI client +const mockCreate = vitest.fn() + +vitest.mock("openai", () => { + return { + __esModule: true, + default: vitest.fn().mockImplementation(() => ({ + chat: { + completions: { + create: mockCreate.mockImplementation(async (options) => { + if (!options.stream) { + return { + id: "test-completion", + choices: [ + { + message: { role: "assistant", content: "Test response" }, + finish_reason: "stop", + index: 0, + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 5, + total_tokens: 15, + }, + } + } + + return { + [Symbol.asyncIterator]: async function* () { + yield { + choices: [ + { + delta: { content: "Test response" }, + index: 0, + }, + ], + usage: null, + } + yield { + choices: [ + { + delta: {}, + index: 0, + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 5, + total_tokens: 15, + }, + } + }, + } + }), + }, + }, + })), + } +}) + +// Mock CloudService - Define functions outside to avoid initialization issues +const mockGetSessionToken = vitest.fn() +const mockHasInstance = vitest.fn() + +// Create mock functions that we can control +const mockGetSessionTokenFn = vitest.fn() +const mockHasInstanceFn = vitest.fn() + +vitest.mock("@roo-code/cloud", () => ({ + CloudService: { + hasInstance: () => mockHasInstanceFn(), + get instance() { + return { + authService: { + getSessionToken: () => mockGetSessionTokenFn(), + }, + } + }, + }, +})) + +// Mock i18n +vitest.mock("../../../i18n", () => ({ + t: vitest.fn((key: string) => { + if (key === "common:errors.roo.authenticationRequired") { + return "Authentication required for Roo Code Cloud" + } + return key + }), +})) + +// Import after mocks are set up +import { RooHandler } from "../roo" +import { CloudService } from "@roo-code/cloud" +import { t } from "../../../i18n" + +describe("RooHandler", () => { + let handler: RooHandler + let mockOptions: ApiHandlerOptions + const systemPrompt = "You are a helpful assistant." + const messages: Anthropic.Messages.MessageParam[] = [ + { + role: "user", + content: "Hello!", + }, + ] + + beforeEach(() => { + mockOptions = { + apiModelId: "roo/sonic", + } + // Set up CloudService mocks for successful authentication + mockHasInstanceFn.mockReturnValue(true) + mockGetSessionTokenFn.mockReturnValue("test-session-token") + mockCreate.mockClear() + vitest.clearAllMocks() + }) + + describe("constructor", () => { + it("should initialize with valid session token", () => { + handler = new RooHandler(mockOptions) + expect(handler).toBeInstanceOf(RooHandler) + expect(handler.getModel().id).toBe(mockOptions.apiModelId) + }) + + it("should throw error if CloudService is not available", () => { + mockHasInstanceFn.mockReturnValue(false) + expect(() => { + new RooHandler(mockOptions) + }).toThrow("Authentication required for Roo Code Cloud") + expect(t).toHaveBeenCalledWith("common:errors.roo.authenticationRequired") + }) + + it("should throw error if session token is not available", () => { + mockHasInstanceFn.mockReturnValue(true) + mockGetSessionTokenFn.mockReturnValue(null) + expect(() => { + new RooHandler(mockOptions) + }).toThrow("Authentication required for Roo Code Cloud") + expect(t).toHaveBeenCalledWith("common:errors.roo.authenticationRequired") + }) + + it("should initialize with default model if no model specified", () => { + handler = new RooHandler({}) + expect(handler).toBeInstanceOf(RooHandler) + expect(handler.getModel().id).toBe(rooDefaultModelId) + }) + + it("should pass correct configuration to base class", () => { + handler = new RooHandler(mockOptions) + expect(handler).toBeInstanceOf(RooHandler) + // The handler should be initialized with correct base URL and API key + // We can't directly test the parent class constructor, but we can verify the handler works + expect(handler).toBeDefined() + }) + }) + + describe("createMessage", () => { + beforeEach(() => { + handler = new RooHandler(mockOptions) + }) + + it("should handle streaming responses", async () => { + const stream = handler.createMessage(systemPrompt, messages) + const chunks: any[] = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + expect(chunks.length).toBeGreaterThan(0) + const textChunks = chunks.filter((chunk) => chunk.type === "text") + expect(textChunks).toHaveLength(1) + expect(textChunks[0].text).toBe("Test response") + }) + + it("should include usage information", async () => { + const stream = handler.createMessage(systemPrompt, messages) + const chunks: any[] = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + const usageChunks = chunks.filter((chunk) => chunk.type === "usage") + expect(usageChunks).toHaveLength(1) + expect(usageChunks[0].inputTokens).toBe(10) + expect(usageChunks[0].outputTokens).toBe(5) + }) + + it("should handle API errors", async () => { + mockCreate.mockRejectedValueOnce(new Error("API Error")) + const stream = handler.createMessage(systemPrompt, messages) + await expect(async () => { + for await (const _chunk of stream) { + // Should not reach here + } + }).rejects.toThrow("API Error") + }) + + it("should handle empty response content", async () => { + mockCreate.mockResolvedValueOnce({ + [Symbol.asyncIterator]: async function* () { + yield { + choices: [ + { + delta: { content: null }, + index: 0, + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 0, + total_tokens: 10, + }, + } + }, + }) + + const stream = handler.createMessage(systemPrompt, messages) + const chunks: any[] = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + const textChunks = chunks.filter((chunk) => chunk.type === "text") + expect(textChunks).toHaveLength(0) + const usageChunks = chunks.filter((chunk) => chunk.type === "usage") + expect(usageChunks).toHaveLength(1) + }) + + it("should handle multiple messages in conversation", async () => { + const multipleMessages: Anthropic.Messages.MessageParam[] = [ + { role: "user", content: "First message" }, + { role: "assistant", content: "First response" }, + { role: "user", content: "Second message" }, + ] + + const stream = handler.createMessage(systemPrompt, multipleMessages) + const chunks: any[] = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + messages: expect.arrayContaining([ + expect.objectContaining({ role: "system", content: systemPrompt }), + expect.objectContaining({ role: "user", content: "First message" }), + expect.objectContaining({ role: "assistant", content: "First response" }), + expect.objectContaining({ role: "user", content: "Second message" }), + ]), + }), + ) + }) + }) + + describe("completePrompt", () => { + beforeEach(() => { + handler = new RooHandler(mockOptions) + }) + + it("should complete prompt successfully", async () => { + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("Test response") + expect(mockCreate).toHaveBeenCalledWith({ + model: mockOptions.apiModelId, + messages: [{ role: "user", content: "Test prompt" }], + }) + }) + + it("should handle API errors", async () => { + mockCreate.mockRejectedValueOnce(new Error("API Error")) + await expect(handler.completePrompt("Test prompt")).rejects.toThrow( + "Roo Code Cloud completion error: API Error", + ) + }) + + it("should handle empty response", async () => { + mockCreate.mockResolvedValueOnce({ + choices: [{ message: { content: "" } }], + }) + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("") + }) + + it("should handle missing response content", async () => { + mockCreate.mockResolvedValueOnce({ + choices: [{ message: {} }], + }) + const result = await handler.completePrompt("Test prompt") + expect(result).toBe("") + }) + }) + + describe("getModel", () => { + beforeEach(() => { + handler = new RooHandler(mockOptions) + }) + + it("should return model info for specified model", () => { + const modelInfo = handler.getModel() + expect(modelInfo.id).toBe(mockOptions.apiModelId) + expect(modelInfo.info).toBeDefined() + // roo/sonic is a valid model in rooModels + expect(modelInfo.info).toBe(rooModels["roo/sonic"]) + }) + + it("should return default model when no model specified", () => { + const handlerWithoutModel = new RooHandler({}) + const modelInfo = handlerWithoutModel.getModel() + expect(modelInfo.id).toBe(rooDefaultModelId) + expect(modelInfo.info).toBeDefined() + expect(modelInfo.info).toBe(rooModels[rooDefaultModelId]) + }) + + it("should handle unknown model ID with fallback info", () => { + const handlerWithUnknownModel = new RooHandler({ + apiModelId: "unknown-model-id", + }) + const modelInfo = handlerWithUnknownModel.getModel() + expect(modelInfo.id).toBe("unknown-model-id") + expect(modelInfo.info).toBeDefined() + // Should return fallback info for unknown models + expect(modelInfo.info.maxTokens).toBe(8192) + expect(modelInfo.info.contextWindow).toBe(262_144) + expect(modelInfo.info.supportsImages).toBe(false) + expect(modelInfo.info.supportsPromptCache).toBe(false) + expect(modelInfo.info.inputPrice).toBe(0) + expect(modelInfo.info.outputPrice).toBe(0) + }) + + it("should return correct model info for all Roo models", () => { + // Test each model in rooModels + const modelIds = Object.keys(rooModels) as Array + + for (const modelId of modelIds) { + const handlerWithModel = new RooHandler({ apiModelId: modelId }) + const modelInfo = handlerWithModel.getModel() + expect(modelInfo.id).toBe(modelId) + expect(modelInfo.info).toBe(rooModels[modelId]) + } + }) + }) + + describe("temperature and model configuration", () => { + it("should use default temperature of 0.7", async () => { + handler = new RooHandler(mockOptions) + const stream = handler.createMessage(systemPrompt, messages) + for await (const _chunk of stream) { + // Consume stream + } + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + temperature: 0.7, + }), + ) + }) + + it("should respect custom temperature setting", async () => { + handler = new RooHandler({ + ...mockOptions, + modelTemperature: 0.9, + }) + const stream = handler.createMessage(systemPrompt, messages) + for await (const _chunk of stream) { + // Consume stream + } + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + temperature: 0.9, + }), + ) + }) + + it("should use correct API endpoint", () => { + // The base URL should be set to Roo's API endpoint + // We can't directly test the OpenAI client configuration, but we can verify the handler initializes + handler = new RooHandler(mockOptions) + expect(handler).toBeInstanceOf(RooHandler) + // The handler should work with the Roo API endpoint + }) + }) + + describe("authentication flow", () => { + it("should use session token as API key", () => { + const testToken = "test-session-token-123" + mockGetSessionTokenFn.mockReturnValue(testToken) + + handler = new RooHandler(mockOptions) + expect(handler).toBeInstanceOf(RooHandler) + expect(mockGetSessionTokenFn).toHaveBeenCalled() + }) + + it("should handle undefined auth service", () => { + mockHasInstanceFn.mockReturnValue(true) + // Mock CloudService with undefined authService + const originalGetter = Object.getOwnPropertyDescriptor(CloudService, "instance")?.get + + try { + Object.defineProperty(CloudService, "instance", { + get: () => ({ authService: undefined }), + configurable: true, + }) + + expect(() => { + new RooHandler(mockOptions) + }).toThrow("Authentication required for Roo Code Cloud") + } finally { + // Always restore original getter, even if test fails + if (originalGetter) { + Object.defineProperty(CloudService, "instance", { + get: originalGetter, + configurable: true, + }) + } + } + }) + + it("should handle empty session token", () => { + mockGetSessionTokenFn.mockReturnValue("") + + expect(() => { + new RooHandler(mockOptions) + }).toThrow("Authentication required for Roo Code Cloud") + }) + }) +}) diff --git a/src/api/providers/index.ts b/src/api/providers/index.ts index 736da82d51..80ef0a2879 100644 --- a/src/api/providers/index.ts +++ b/src/api/providers/index.ts @@ -29,3 +29,4 @@ export { VsCodeLmHandler } from "./vscode-lm" export { XAIHandler } from "./xai" export { ZAiHandler } from "./zai" export { FireworksHandler } from "./fireworks" +export { RooHandler } from "./roo" diff --git a/src/api/providers/roo.ts b/src/api/providers/roo.ts new file mode 100644 index 0000000000..3b0540c2ea --- /dev/null +++ b/src/api/providers/roo.ts @@ -0,0 +1,53 @@ +import { rooDefaultModelId, rooModels, type RooModelId } from "@roo-code/types" +import { CloudService } from "@roo-code/cloud" + +import type { ApiHandlerOptions } from "../../shared/api" +import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" +import { t } from "../../i18n" + +export class RooHandler extends BaseOpenAiCompatibleProvider { + constructor(options: ApiHandlerOptions) { + // Check if CloudService is available and get the session token. + if (!CloudService.hasInstance()) { + throw new Error(t("common:errors.roo.authenticationRequired")) + } + + const sessionToken = CloudService.instance.authService?.getSessionToken() + + if (!sessionToken) { + throw new Error(t("common:errors.roo.authenticationRequired")) + } + + super({ + ...options, + providerName: "Roo Code Cloud", + baseURL: "https://api.roocode.com/proxy/v1", + apiKey: sessionToken, + defaultProviderModelId: rooDefaultModelId, + providerModels: rooModels, + defaultTemperature: 0.7, + }) + } + + override getModel() { + const modelId = this.options.apiModelId || rooDefaultModelId + const modelInfo = this.providerModels[modelId as RooModelId] ?? this.providerModels[rooDefaultModelId] + + if (modelInfo) { + return { id: modelId as RooModelId, info: modelInfo } + } + + // Return the requested model ID even if not found, with fallback info. + return { + id: modelId as RooModelId, + info: { + maxTokens: 8192, + contextWindow: 262_144, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + }, + } + } +} diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index d4fddfebf3..6235593f7e 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -104,6 +104,9 @@ "noResponseBody": "Error de l'API de Cerebras: No hi ha cos de resposta", "completionError": "Error de finalització de Cerebras: {{error}}" }, + "roo": { + "authenticationRequired": "El proveïdor Roo requereix autenticació al núvol. Si us plau, inicieu sessió a Roo Code Cloud." + }, "mode_import_failed": "Ha fallat la importació del mode: {{error}}" }, "warnings": { diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index af69ed7cfe..6819b27d73 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -100,6 +100,9 @@ "genericError": "Cerebras API-Fehler ({{status}}): {{message}}", "noResponseBody": "Cerebras API-Fehler: Kein Antworttext vorhanden", "completionError": "Cerebras-Vervollständigungsfehler: {{error}}" + }, + "roo": { + "authenticationRequired": "Roo-Anbieter erfordert Cloud-Authentifizierung. Bitte melde dich bei Roo Code Cloud an." } }, "warnings": { diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 05d039a495..696ecb44d4 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -100,6 +100,9 @@ "genericError": "Cerebras API Error ({{status}}): {{message}}", "noResponseBody": "Cerebras API Error: No response body", "completionError": "Cerebras completion error: {{error}}" + }, + "roo": { + "authenticationRequired": "Roo provider requires cloud authentication. Please sign in to Roo Code Cloud." } }, "warnings": { diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index 28e2fc3812..c1b399b84f 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -100,6 +100,9 @@ "genericError": "Error de la API de Cerebras ({{status}}): {{message}}", "noResponseBody": "Error de la API de Cerebras: Sin cuerpo de respuesta", "completionError": "Error de finalización de Cerebras: {{error}}" + }, + "roo": { + "authenticationRequired": "El proveedor Roo requiere autenticación en la nube. Por favor, inicia sesión en Roo Code Cloud." } }, "warnings": { diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index c3264e7ba3..682e12e224 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -100,6 +100,9 @@ "genericError": "Erreur de l'API Cerebras ({{status}}) : {{message}}", "noResponseBody": "Erreur de l'API Cerebras : Aucun corps de réponse", "completionError": "Erreur d'achèvement de Cerebras : {{error}}" + }, + "roo": { + "authenticationRequired": "Le fournisseur Roo nécessite une authentification cloud. Veuillez vous connecter à Roo Code Cloud." } }, "warnings": { diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index c68d002e1d..05e0a622cc 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -100,6 +100,9 @@ "genericError": "Cerebras API त्रुटि ({{status}}): {{message}}", "noResponseBody": "Cerebras API त्रुटि: कोई प्रतिक्रिया मुख्य भाग नहीं", "completionError": "Cerebras पूर्णता त्रुटि: {{error}}" + }, + "roo": { + "authenticationRequired": "Roo प्रदाता को क्लाउड प्रमाणीकरण की आवश्यकता है। कृपया Roo Code Cloud में साइन इन करें।" } }, "warnings": { diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index 4045b7e8bb..1595b795cf 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -100,6 +100,9 @@ "genericError": "Kesalahan API Cerebras ({{status}}): {{message}}", "noResponseBody": "Kesalahan API Cerebras: Tidak ada isi respons", "completionError": "Kesalahan penyelesaian Cerebras: {{error}}" + }, + "roo": { + "authenticationRequired": "Penyedia Roo memerlukan autentikasi cloud. Silakan masuk ke Roo Code Cloud." } }, "warnings": { diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index a059c48a9b..73f4d47788 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -100,6 +100,9 @@ "genericError": "Errore API Cerebras ({{status}}): {{message}}", "noResponseBody": "Errore API Cerebras: Nessun corpo di risposta", "completionError": "Errore di completamento Cerebras: {{error}}" + }, + "roo": { + "authenticationRequired": "Il provider Roo richiede l'autenticazione cloud. Accedi a Roo Code Cloud." } }, "warnings": { diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index f9eceb5979..cb55c7bf0b 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -100,6 +100,9 @@ "genericError": "Cerebras APIエラー ({{status}}): {{message}}", "noResponseBody": "Cerebras APIエラー: レスポンスボディなし", "completionError": "Cerebras完了エラー: {{error}}" + }, + "roo": { + "authenticationRequired": "Rooプロバイダーはクラウド認証が必要です。Roo Code Cloudにサインインしてください。" } }, "warnings": { diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 4292677e30..9bb61b6563 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -100,6 +100,9 @@ "genericError": "Cerebras API 오류 ({{status}}): {{message}}", "noResponseBody": "Cerebras API 오류: 응답 본문 없음", "completionError": "Cerebras 완료 오류: {{error}}" + }, + "roo": { + "authenticationRequired": "Roo 제공업체는 클라우드 인증이 필요합니다. Roo Code Cloud에 로그인하세요." } }, "warnings": { diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index 2133e7003b..fb2fcec9f9 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -100,6 +100,9 @@ "genericError": "Cerebras API-fout ({{status}}): {{message}}", "noResponseBody": "Cerebras API-fout: Geen responslichaam", "completionError": "Cerebras-voltooiingsfout: {{error}}" + }, + "roo": { + "authenticationRequired": "Roo provider vereist cloud authenticatie. Log in bij Roo Code Cloud." } }, "warnings": { diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index 1c63702911..2a6fee3e23 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -100,6 +100,9 @@ "genericError": "Błąd API Cerebras ({{status}}): {{message}}", "noResponseBody": "Błąd API Cerebras: Brak treści odpowiedzi", "completionError": "Błąd uzupełniania Cerebras: {{error}}" + }, + "roo": { + "authenticationRequired": "Dostawca Roo wymaga uwierzytelnienia w chmurze. Zaloguj się do Roo Code Cloud." } }, "warnings": { diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index 6aeb5f6ee7..83d960ad2d 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -104,6 +104,9 @@ "genericError": "Erro da API Cerebras ({{status}}): {{message}}", "noResponseBody": "Erro da API Cerebras: Sem corpo de resposta", "completionError": "Erro de conclusão do Cerebras: {{error}}" + }, + "roo": { + "authenticationRequired": "O provedor Roo requer autenticação na nuvem. Faça login no Roo Code Cloud." } }, "warnings": { diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 0d7b800d8b..9c37cfe3ed 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -100,6 +100,9 @@ "genericError": "Ошибка Cerebras API ({{status}}): {{message}}", "noResponseBody": "Ошибка Cerebras API: Нет тела ответа", "completionError": "Ошибка завершения Cerebras: {{error}}" + }, + "roo": { + "authenticationRequired": "Провайдер Roo требует облачной аутентификации. Войдите в Roo Code Cloud." } }, "warnings": { diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 49d0fba113..d99008755e 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -100,6 +100,9 @@ "genericError": "Cerebras API Hatası ({{status}}): {{message}}", "noResponseBody": "Cerebras API Hatası: Yanıt gövdesi yok", "completionError": "Cerebras tamamlama hatası: {{error}}" + }, + "roo": { + "authenticationRequired": "Roo sağlayıcısı bulut kimlik doğrulaması gerektirir. Lütfen Roo Code Cloud'a giriş yapın." } }, "warnings": { diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index 3f8947e620..d29525cc03 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -100,6 +100,9 @@ "genericError": "Lỗi API Cerebras ({{status}}): {{message}}", "noResponseBody": "Lỗi API Cerebras: Không có nội dung phản hồi", "completionError": "Lỗi hoàn thành Cerebras: {{error}}" + }, + "roo": { + "authenticationRequired": "Nhà cung cấp Roo yêu cầu xác thực đám mây. Vui lòng đăng nhập vào Roo Code Cloud." } }, "warnings": { diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index 808d534572..fc0386c95d 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -105,6 +105,9 @@ "genericError": "Cerebras API 错误 ({{status}}):{{message}}", "noResponseBody": "Cerebras API 错误:无响应主体", "completionError": "Cerebras 完成错误:{{error}}" + }, + "roo": { + "authenticationRequired": "Roo 提供商需要云认证。请登录 Roo Code Cloud。" } }, "warnings": { diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index 81e098fbcf..753463b9f5 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -100,6 +100,9 @@ "noResponseBody": "Cerebras API 錯誤:無回應主體", "completionError": "Cerebras 完成錯誤:{{error}}" }, + "roo": { + "authenticationRequired": "Roo 提供者需要雲端認證。請登入 Roo Code Cloud。" + }, "mode_import_failed": "匯入模式失敗:{{error}}" }, "warnings": { diff --git a/src/package.json b/src/package.json index 48e785d7b9..d6c447db39 100644 --- a/src/package.json +++ b/src/package.json @@ -427,7 +427,7 @@ "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.9.0", "@qdrant/js-client-rest": "^1.14.0", - "@roo-code/cloud": "^0.17.0", + "@roo-code/cloud": "^0.18.0", "@roo-code/ipc": "workspace:^", "@roo-code/telemetry": "workspace:^", "@roo-code/types": "workspace:^", diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index b51b171354..6db3dab529 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1,7 +1,7 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from "react" import { convertHeadersToObject } from "./utils/headers" import { useDebounce } from "react-use" -import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" +import { VSCodeLink, VSCodeButton } from "@vscode/webview-ui-toolkit/react" import { ExternalLinkIcon } from "@radix-ui/react-icons" import { @@ -32,6 +32,7 @@ import { mainlandZAiDefaultModelId, fireworksDefaultModelId, ioIntelligenceDefaultModelId, + rooDefaultModelId, } from "@roo-code/types" import { vscode } from "@src/utils/vscode" @@ -124,7 +125,7 @@ const ApiOptions = ({ setErrorMessage, }: ApiOptionsProps) => { const { t } = useAppTranslation() - const { organizationAllowList } = useExtensionState() + const { organizationAllowList, cloudIsAuthenticated } = useExtensionState() const [customHeaders, setCustomHeaders] = useState<[string, string][]>(() => { const headers = apiConfiguration?.openAiHeaders || {} @@ -327,6 +328,7 @@ const ApiOptions = ({ }, fireworks: { field: "apiModelId", default: fireworksDefaultModelId }, "io-intelligence": { field: "ioIntelligenceModelId", default: ioIntelligenceDefaultModelId }, + roo: { field: "apiModelId", default: rooDefaultModelId }, openai: { field: "openAiModelId" }, ollama: { field: "ollamaModelId" }, lmstudio: { field: "lmStudioModelId" }, @@ -579,6 +581,25 @@ const ApiOptions = ({ )} + {selectedProvider === "roo" && ( +
+ {cloudIsAuthenticated ? ( +
+ {t("settings:providers.roo.authenticatedMessage")} +
+ ) : ( +
+ vscode.postMessage({ type: "rooCloudSignIn" })} + className="w-fit"> + {t("settings:providers.roo.connectButton")} + +
+ )} +
+ )} + {selectedProviderModels.length > 0 && ( <>
diff --git a/webview-ui/src/components/settings/ModelDescriptionMarkdown.tsx b/webview-ui/src/components/settings/ModelDescriptionMarkdown.tsx index d6aac8ba74..b04ab1163e 100644 --- a/webview-ui/src/components/settings/ModelDescriptionMarkdown.tsx +++ b/webview-ui/src/components/settings/ModelDescriptionMarkdown.tsx @@ -34,7 +34,7 @@ export const ModelDescriptionMarkdown = memo( return ( -
+
{content}
diff --git a/webview-ui/src/components/settings/ModelInfoView.tsx b/webview-ui/src/components/settings/ModelInfoView.tsx index 2ba732effa..5091fb1a68 100644 --- a/webview-ui/src/components/settings/ModelInfoView.tsx +++ b/webview-ui/src/components/settings/ModelInfoView.tsx @@ -26,6 +26,18 @@ export const ModelInfoView = ({ const { t } = useAppTranslation() const infoItems = [ + typeof modelInfo?.contextWindow === "number" && modelInfo.contextWindow > 0 && ( + <> + {t("settings:modelInfo.contextWindow")}{" "} + {modelInfo.contextWindow?.toLocaleString()} tokens + + ), + typeof modelInfo?.maxTokens === "number" && modelInfo.maxTokens > 0 && ( + <> + {t("settings:modelInfo.maxOutput")}:{" "} + {modelInfo.maxTokens?.toLocaleString()} tokens + + ), , - typeof modelInfo?.contextWindow === "number" && modelInfo.contextWindow > 0 && ( - <> - {t("settings:modelInfo.contextWindow")}{" "} - {modelInfo.contextWindow?.toLocaleString()} tokens - - ), - typeof modelInfo?.maxTokens === "number" && modelInfo.maxTokens > 0 && ( - <> - {t("settings:modelInfo.maxOutput")}:{" "} - {modelInfo.maxTokens?.toLocaleString()} tokens - - ), modelInfo?.inputPrice !== undefined && modelInfo.inputPrice > 0 && ( <> {t("settings:modelInfo.inputPrice")}:{" "} @@ -119,11 +119,7 @@ const ModelInfoSupportsItem = ({ supportsLabel: string doesNotSupportLabel: string }) => ( -
+
{isSupported ? supportsLabel : doesNotSupportLabel}
diff --git a/webview-ui/src/components/settings/constants.ts b/webview-ui/src/components/settings/constants.ts index 5882b1bf4a..dc54d367eb 100644 --- a/webview-ui/src/components/settings/constants.ts +++ b/webview-ui/src/components/settings/constants.ts @@ -18,6 +18,7 @@ import { doubaoModels, internationalZAiModels, fireworksModels, + rooModels, } from "@roo-code/types" export const MODELS_BY_PROVIDER: Partial>> = { @@ -38,6 +39,7 @@ export const MODELS_BY_PROVIDER: Partial a.label.localeCompare(b.label)) diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index b188f3e342..a4a36857a0 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -48,6 +48,8 @@ import { fireworksDefaultModelId, ioIntelligenceDefaultModelId, ioIntelligenceModels, + rooDefaultModelId, + rooModels, BEDROCK_CLAUDE_SONNET_4_MODEL_ID, } from "@roo-code/types" @@ -296,6 +298,11 @@ function getSelectedModel({ routerModels["io-intelligence"]?.[id] ?? ioIntelligenceModels[id as keyof typeof ioIntelligenceModels] return { id, info } } + case "roo": { + const id = apiConfiguration.apiModelId ?? rooDefaultModelId + const info = rooModels[id as keyof typeof rooModels] + return { id, info } + } // case "anthropic": // case "human-relay": // case "fake-ai": diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index ede37e530e..3f7f1c901a 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "Versió {{version}} - Feu clic per veure les notes de llançament" }, "rooCloudCTA": { - "title": "Roo Code Cloud arribarà aviat!", + "title": "Roo Code Cloud està evolucionant!", "description": "Executa agents remots al núvol, accedeix a les teves tasques des de qualsevol lloc, col·labora amb altres i molt més.", - "joinWaitlist": "Uneix-te a la llista d'espera per obtenir accés anticipat." + "joinWaitlist": "Registra't per rebre les últimes actualitzacions." }, "editMessage": { "placeholder": "Edita el teu missatge..." diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 5964798684..97c5e39352 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -377,6 +377,10 @@ "description": "No es requereix clau API, però l'usuari necessita ajuda per copiar i enganxar informació al xat d'IA web.", "instructions": "Durant l'ús, apareixerà un diàleg i el missatge actual es copiarà automàticament al porta-retalls. Necessiteu enganxar-lo a les versions web d'IA (com ChatGPT o Claude), després copiar la resposta de l'IA de nou al diàleg i fer clic al botó de confirmació." }, + "roo": { + "authenticatedMessage": "Autenticat de forma segura a través del teu compte de Roo Code Cloud.", + "connectButton": "Connecta amb Roo Code Cloud" + }, "openRouter": { "providerRouting": { "title": "Encaminament de Proveïdors d'OpenRouter", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 0574da2df3..2eaee9feac 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "Version {{version}} - Klicken Sie, um die Versionshinweise anzuzeigen" }, "rooCloudCTA": { - "title": "Roo Code Cloud kommt bald!", + "title": "Roo Code Cloud entwickelt sich weiter!", "description": "Führe Remote-Agenten in der Cloud aus, greife von überall auf deine Aufgaben zu, arbeite mit anderen zusammen und vieles mehr.", - "joinWaitlist": "Tritt der Warteliste bei, um frühen Zugang zu erhalten." + "joinWaitlist": "Melde dich an, um die neuesten Updates zu erhalten." }, "command": { "triggerDescription": "Starte den {{name}} Befehl" diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 28f3e847fa..2d699a0e96 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -377,6 +377,10 @@ "description": "Es ist kein API-Schlüssel erforderlich, aber der Benutzer muss beim Kopieren und Einfügen der Informationen in den Web-Chat-KI helfen.", "instructions": "Während der Verwendung wird ein Dialogfeld angezeigt und die aktuelle Nachricht wird automatisch in die Zwischenablage kopiert. Du musst diese in Web-Versionen von KI (wie ChatGPT oder Claude) einfügen, dann die Antwort der KI zurück in das Dialogfeld kopieren und auf die Bestätigungsschaltfläche klicken." }, + "roo": { + "authenticatedMessage": "Sicher authentifiziert über dein Roo Code Cloud-Konto.", + "connectButton": "Mit Roo Code Cloud verbinden" + }, "openRouter": { "providerRouting": { "title": "OpenRouter Anbieter-Routing", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 3479cdf69b..ef5ff39e9a 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -352,9 +352,9 @@ "ariaLabel": "Version {{version}} - Click to view release notes" }, "rooCloudCTA": { - "title": "Roo Code Cloud is coming soon!", + "title": "Roo Code Cloud is evolving!", "description": "Run Roomote agents in the cloud, access your tasks from anywhere, collaborate with others, and more.", - "joinWaitlist": "Join the waitlist to get early access." + "joinWaitlist": "Sign up to get the latest updates." }, "command": { "triggerDescription": "Trigger the {{name}} command" diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index fca3d1ade9..4f70e437c2 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -376,6 +376,10 @@ "description": "No API key is required, but the user needs to help copy and paste the information to the web chat AI.", "instructions": "During use, a dialog box will pop up and the current message will be copied to the clipboard automatically. You need to paste these to web versions of AI (such as ChatGPT or Claude), then copy the AI's reply back to the dialog box and click the confirm button." }, + "roo": { + "authenticatedMessage": "Securely authenticated through your Roo Code Cloud account.", + "connectButton": "Connect to Roo Code Cloud" + }, "openRouter": { "providerRouting": { "title": "OpenRouter Provider Routing", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 3621aa16cf..dc28671d04 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "Versión {{version}} - Haz clic para ver las notas de la versión" }, "rooCloudCTA": { - "title": "¡Roo Code Cloud llegará pronto!", + "title": "¡Roo Code Cloud está evolucionando!", "description": "Ejecuta agentes remotos en la nube, accede a tus tareas desde cualquier lugar, colabora con otros y mucho más.", - "joinWaitlist": "Únete a la lista de espera para obtener acceso anticipado." + "joinWaitlist": "Regístrate para recibir las últimas actualizaciones." }, "editMessage": { "placeholder": "Edita tu mensaje..." diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index fd6e1e2715..7f22887ee1 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -377,6 +377,10 @@ "description": "No se requiere clave API, pero el usuario necesita ayudar a copiar y pegar la información en el chat web de IA.", "instructions": "Durante el uso, aparecerá un cuadro de diálogo y el mensaje actual se copiará automáticamente al portapapeles. Debe pegarlo en las versiones web de IA (como ChatGPT o Claude), luego copiar la respuesta de la IA de vuelta al cuadro de diálogo y hacer clic en el botón de confirmar." }, + "roo": { + "authenticatedMessage": "Autenticado de forma segura a través de tu cuenta de Roo Code Cloud.", + "connectButton": "Conectar a Roo Code Cloud" + }, "openRouter": { "providerRouting": { "title": "Enrutamiento de Proveedores de OpenRouter", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 41ea4dd5a1..f7acde6eec 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "Version {{version}} - Cliquez pour voir les notes de version" }, "rooCloudCTA": { - "title": "Roo Code Cloud arrive bientôt !", + "title": "Roo Code Cloud évolue !", "description": "Exécutez des agents distants dans le cloud, accédez à vos tâches de n'importe où, collaborez avec d'autres et bien plus encore.", - "joinWaitlist": "Rejoignez la liste d'attente pour obtenir un accès anticipé." + "joinWaitlist": "Inscrivez-vous pour recevoir les dernières mises à jour." }, "editMessage": { "placeholder": "Modifiez votre message..." diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 451e51d084..c544673df6 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -377,6 +377,10 @@ "description": "Aucune clé API n'est requise, mais l'utilisateur doit aider à copier et coller les informations dans le chat web de l'IA.", "instructions": "Pendant l'utilisation, une boîte de dialogue apparaîtra et le message actuel sera automatiquement copié dans le presse-papiers. Vous devez le coller dans les versions web de l'IA (comme ChatGPT ou Claude), puis copier la réponse de l'IA dans la boîte de dialogue et cliquer sur le bouton de confirmation." }, + "roo": { + "authenticatedMessage": "Authentifié de manière sécurisée via ton compte Roo Code Cloud.", + "connectButton": "Se connecter à Roo Code Cloud" + }, "openRouter": { "providerRouting": { "title": "Routage des fournisseurs OpenRouter", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 05288792cf..3c8984b405 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "संस्करण {{version}} - रिलीज़ नोट्स देखने के लिए क्लिक करें" }, "rooCloudCTA": { - "title": "Roo Code Cloud जल्द आ रहा है!", + "title": "Roo Code Cloud विकसित हो रहा है!", "description": "क्लाउड में रिमोट एजेंट चलाएं, कहीं से भी अपने कार्यों तक पहुंचें, दूसरों के साथ सहयोग करें, और बहुत कुछ।", - "joinWaitlist": "जल्दी पहुंच पाने के लिए प्रतीक्षा सूची में शामिल हों।" + "joinWaitlist": "नवीनतम अपडेट प्राप्त करने के लिए साइन अप करें।" }, "editMessage": { "placeholder": "अपना संदेश संपादित करें..." diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 83a4b7b81b..536d63d2a5 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -377,6 +377,10 @@ "description": "कोई API कुंजी आवश्यक नहीं है, लेकिन उपयोगकर्ता को वेब चैट AI में जानकारी कॉपी और पेस्ट करने में मदद करनी होगी।", "instructions": "उपयोग के दौरान, एक डायलॉग बॉक्स पॉप अप होगा और वर्तमान संदेश स्वचालित रूप से क्लिपबोर्ड पर कॉपी हो जाएगा। आपको इन्हें AI के वेब संस्करणों (जैसे ChatGPT या Claude) में पेस्ट करना होगा, फिर AI की प्रतिक्रिया को डायलॉग बॉक्स में वापस कॉपी करें और पुष्टि बटन पर क्लिक करें।" }, + "roo": { + "authenticatedMessage": "आपके Roo Code Cloud खाते के माध्यम से सुरक्षित रूप से प्रमाणित।", + "connectButton": "Roo Code Cloud से कनेक्ट करें" + }, "openRouter": { "providerRouting": { "title": "OpenRouter प्रदाता रूटिंग", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 909606ee91..bfcf3614f6 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -355,9 +355,9 @@ "ariaLabel": "Versi {{version}} - Klik untuk melihat catatan rilis" }, "rooCloudCTA": { - "title": "Roo Code Cloud segera hadir!", + "title": "Roo Code Cloud sedang berkembang!", "description": "Jalankan agen jarak jauh di cloud, akses tugas Anda dari mana saja, berkolaborasi dengan orang lain, dan banyak lagi.", - "joinWaitlist": "Bergabunglah dengan daftar tunggu untuk mendapatkan akses awal." + "joinWaitlist": "Daftar untuk mendapatkan pembaruan terbaru." }, "editMessage": { "placeholder": "Edit pesan Anda..." diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index fd7edf40cf..672adb9eda 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -381,6 +381,10 @@ "description": "Tidak diperlukan API key, tetapi pengguna perlu membantu menyalin dan menempel informasi ke web chat AI.", "instructions": "Selama penggunaan, kotak dialog akan muncul dan pesan saat ini akan disalin ke clipboard secara otomatis. Kamu perlu menempel ini ke versi web AI (seperti ChatGPT atau Claude), lalu salin balasan AI kembali ke kotak dialog dan klik tombol konfirmasi." }, + "roo": { + "authenticatedMessage": "Terautentikasi dengan aman melalui akun Roo Code Cloud Anda.", + "connectButton": "Hubungkan ke Roo Code Cloud" + }, "openRouter": { "providerRouting": { "title": "OpenRouter Provider Routing", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 480cc13fad..90ee97b7ce 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "Versione {{version}} - Clicca per visualizzare le note di rilascio" }, "rooCloudCTA": { - "title": "Roo Code Cloud arriva presto!", + "title": "Roo Code Cloud si sta evolvendo!", "description": "Esegui agenti remoti nel cloud, accedi alle tue attività da qualsiasi luogo, collabora con altri e molto altro.", - "joinWaitlist": "Unisciti alla lista d'attesa per ottenere l'accesso anticipato." + "joinWaitlist": "Registrati per ricevere gli ultimi aggiornamenti." }, "editMessage": { "placeholder": "Modifica il tuo messaggio..." diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index c8a8800e4f..a04258d398 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -377,6 +377,10 @@ "description": "Non è richiesta alcuna chiave API, ma l'utente dovrà aiutare a copiare e incollare le informazioni nella chat web AI.", "instructions": "Durante l'uso, apparirà una finestra di dialogo e il messaggio corrente verrà automaticamente copiato negli appunti. Dovrai incollarlo nelle versioni web dell'AI (come ChatGPT o Claude), quindi copiare la risposta dell'AI nella finestra di dialogo e fare clic sul pulsante di conferma." }, + "roo": { + "authenticatedMessage": "Autenticato in modo sicuro tramite il tuo account Roo Code Cloud.", + "connectButton": "Connetti a Roo Code Cloud" + }, "openRouter": { "providerRouting": { "title": "Routing dei fornitori OpenRouter", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 6b3340e7f0..46c228638a 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "バージョン {{version}} - クリックしてリリースノートを表示" }, "rooCloudCTA": { - "title": "Roo Code Cloud が間もなく登場!", + "title": "Roo Code Cloud が進化中!", "description": "クラウドでリモートエージェントを実行し、どこからでもタスクにアクセスし、他の人と協力し、その他多くの機能を利用できます。", - "joinWaitlist": "早期アクセスを取得するためにウェイトリストに参加してください。" + "joinWaitlist": "最新のアップデートを受け取るためにサインアップしてください。" }, "editMessage": { "placeholder": "メッセージを編集..." diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index d3e7b04baf..6c1859c85d 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -377,6 +377,10 @@ "description": "APIキーは不要ですが、ユーザーはウェブチャットAIに情報をコピー&ペーストする必要があります。", "instructions": "使用中にダイアログボックスが表示され、現在のメッセージが自動的にクリップボードにコピーされます。これらをウェブ版のAI(ChatGPTやClaudeなど)に貼り付け、AIの返答をダイアログボックスにコピーして確認ボタンをクリックする必要があります。" }, + "roo": { + "authenticatedMessage": "Roo Code Cloudアカウントを通じて安全に認証されています。", + "connectButton": "Roo Code Cloudに接続" + }, "openRouter": { "providerRouting": { "title": "OpenRouterプロバイダールーティング", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 4b8d652f03..9b625a7ae7 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "버전 {{version}} - 릴리스 노트를 보려면 클릭하세요" }, "rooCloudCTA": { - "title": "Roo Code Cloud가 곧 출시됩니다!", + "title": "Roo Code Cloud가 진화하고 있습니다!", "description": "클라우드에서 원격 에이전트를 실행하고, 어디서나 작업에 액세스하고, 다른 사람들과 협업하는 등 다양한 기능을 이용하세요.", - "joinWaitlist": "얼리 액세스를 받으려면 대기 목록에 가입하세요." + "joinWaitlist": "최신 업데이트를 받으려면 가입하세요." }, "editMessage": { "placeholder": "메시지 편집..." diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index a5bcd1f385..e77a806920 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -377,6 +377,10 @@ "description": "API 키가 필요하지 않지만, 사용자가 웹 채팅 AI에 정보를 복사하여 붙여넣어야 합니다.", "instructions": "사용 중에 대화 상자가 나타나고 현재 메시지가 자동으로 클립보드에 복사됩니다. 이를 웹 버전 AI(예: ChatGPT 또는 Claude)에 붙여넣은 다음, AI의 응답을 대화 상자에 복사하고 확인 버튼을 클릭해야 합니다." }, + "roo": { + "authenticatedMessage": "Roo Code Cloud 계정을 통해 안전하게 인증되었습니다.", + "connectButton": "Roo Code Cloud에 연결" + }, "openRouter": { "providerRouting": { "title": "OpenRouter 제공자 라우팅", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 871ab1371e..5fc5001750 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "Versie {{version}} - Klik om release notes te bekijken" }, "rooCloudCTA": { - "title": "Roo Code Cloud komt binnenkort!", + "title": "Roo Code Cloud evolueert!", "description": "Voer externe agenten uit in de cloud, krijg overal toegang tot je taken, werk samen met anderen en nog veel meer.", - "joinWaitlist": "Sluit je aan bij de wachtlijst voor vroege toegang." + "joinWaitlist": "Meld je aan om de laatste updates te ontvangen." }, "editMessage": { "placeholder": "Bewerk je bericht..." diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index b54e021be0..42c1d97bdb 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -377,6 +377,10 @@ "description": "Geen API-sleutel vereist, maar de gebruiker moet helpen met kopiëren en plakken naar de webchat-AI.", "instructions": "Tijdens gebruik verschijnt een dialoogvenster en wordt het huidige bericht automatisch naar het klembord gekopieerd. Je moet deze plakken in webversies van AI (zoals ChatGPT of Claude), vervolgens het antwoord van de AI terugkopiëren naar het dialoogvenster en op bevestigen klikken." }, + "roo": { + "authenticatedMessage": "Veilig geauthenticeerd via je Roo Code Cloud-account.", + "connectButton": "Verbinden met Roo Code Cloud" + }, "openRouter": { "providerRouting": { "title": "OpenRouter-providerroutering", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 5a06dc3091..8e8500bea1 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "Wersja {{version}} - Kliknij, aby wyświetlić informacje o wydaniu" }, "rooCloudCTA": { - "title": "Roo Code Cloud już wkrótce!", + "title": "Roo Code Cloud się rozwija!", "description": "Uruchamiaj zdalne agenty w chmurze, uzyskuj dostęp do swoich zadań z dowolnego miejsca, współpracuj z innymi i wiele więcej.", - "joinWaitlist": "Dołącz do listy oczekujących, aby uzyskać wczesny dostęp." + "joinWaitlist": "Zarejestruj się, aby otrzymywać najnowsze aktualizacje." }, "editMessage": { "placeholder": "Edytuj swoją wiadomość..." diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 194bd9029d..f1abf9c79d 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -377,6 +377,10 @@ "description": "Nie jest wymagany klucz API, ale użytkownik będzie musiał pomóc w kopiowaniu i wklejaniu informacji do czatu internetowego AI.", "instructions": "Podczas użytkowania pojawi się okno dialogowe, a bieżąca wiadomość zostanie automatycznie skopiowana do schowka. Będziesz musiał wkleić ją do internetowych wersji AI (takich jak ChatGPT lub Claude), a następnie skopiować odpowiedź AI z powrotem do okna dialogowego i kliknąć przycisk potwierdzenia." }, + "roo": { + "authenticatedMessage": "Bezpiecznie uwierzytelniony przez twoje konto Roo Code Cloud.", + "connectButton": "Połącz z Roo Code Cloud" + }, "openRouter": { "providerRouting": { "title": "Routing dostawców OpenRouter", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 65854c508d..72e3fc4b7d 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "Versão {{version}} - Clique para ver as notas de lançamento" }, "rooCloudCTA": { - "title": "Roo Code Cloud chegará em breve!", + "title": "Roo Code Cloud está evoluindo!", "description": "Execute agentes remotos na nuvem, acesse suas tarefas de qualquer lugar, colabore com outros e muito mais.", - "joinWaitlist": "Junte-se à lista de espera para obter acesso antecipado." + "joinWaitlist": "Cadastre-se para receber as últimas atualizações." }, "editMessage": { "placeholder": "Edite sua mensagem..." diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 7879dd5154..c566ee1e2d 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -377,6 +377,10 @@ "description": "Não é necessária chave de API, mas o usuário precisa ajudar a copiar e colar as informações para a IA do chat web.", "instructions": "Durante o uso, uma caixa de diálogo será exibida e a mensagem atual será copiada para a área de transferência automaticamente. Você precisa colar isso nas versões web de IA (como ChatGPT ou Claude), depois copiar a resposta da IA de volta para a caixa de diálogo e clicar no botão confirmar." }, + "roo": { + "authenticatedMessage": "Autenticado com segurança através da sua conta Roo Code Cloud.", + "connectButton": "Conectar ao Roo Code Cloud" + }, "openRouter": { "providerRouting": { "title": "Roteamento de Provedores OpenRouter", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index ff43851495..a9e242ac34 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "Версия {{version}} - Нажмите, чтобы просмотреть примечания к выпуску" }, "rooCloudCTA": { - "title": "Roo Code Cloud скоро появится!", + "title": "Roo Code Cloud развивается!", "description": "Запускайте удаленные агенты в облаке, получайте доступ к своим задачам из любого места, сотрудничайте с другими и многое другое.", - "joinWaitlist": "Присоединитесь к списку ожидания для получения раннего доступа." + "joinWaitlist": "Зарегистрируйтесь, чтобы получать последние обновления." }, "editMessage": { "placeholder": "Редактировать сообщение..." diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 3744ecedff..98df1f0138 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -377,6 +377,10 @@ "description": "API-ключ не требуется, но пользователю нужно вручную копировать и вставлять информацию в веб-чат ИИ.", "instructions": "Во время использования появится диалоговое окно, и текущее сообщение будет скопировано в буфер обмена автоматически. Вам нужно вставить его в веб-версию ИИ (например, ChatGPT или Claude), затем скопировать ответ ИИ обратно в диалоговое окно и нажать кнопку подтверждения." }, + "roo": { + "authenticatedMessage": "Безопасно аутентифицирован через твой аккаунт Roo Code Cloud.", + "connectButton": "Подключиться к Roo Code Cloud" + }, "openRouter": { "providerRouting": { "title": "Маршрутизация провайдера OpenRouter", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index cf3dee847b..361902d50a 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "Sürüm {{version}} - Sürüm notlarını görüntülemek için tıklayın" }, "rooCloudCTA": { - "title": "Roo Code Cloud yakında geliyor!", + "title": "Roo Code Cloud gelişiyor!", "description": "Bulutta uzak ajanlar çalıştırın, görevlerinize her yerden erişin, başkalarıyla işbirliği yapın ve daha fazlası.", - "joinWaitlist": "Erken erişim için bekleme listesine katılın." + "joinWaitlist": "En son güncellemeleri almak için kaydolun." }, "editMessage": { "placeholder": "Mesajını düzenle..." diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index be1a7a0169..4fb043a8a0 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -377,6 +377,10 @@ "description": "API anahtarı gerekmez, ancak kullanıcının bilgileri web sohbet yapay zekasına kopyalayıp yapıştırması gerekir.", "instructions": "Kullanım sırasında bir iletişim kutusu açılacak ve mevcut mesaj otomatik olarak panoya kopyalanacaktır. Bunları web yapay zekalarına (ChatGPT veya Claude gibi) yapıştırmanız, ardından yapay zekanın yanıtını iletişim kutusuna kopyalayıp onay düğmesine tıklamanız gerekir." }, + "roo": { + "authenticatedMessage": "Roo Code Cloud hesabın üzerinden güvenli bir şekilde kimlik doğrulandı.", + "connectButton": "Roo Code Cloud'a Bağlan" + }, "openRouter": { "providerRouting": { "title": "OpenRouter Sağlayıcı Yönlendirmesi", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index e1bf274511..11466382a7 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "Phiên bản {{version}} - Nhấp để xem ghi chú phát hành" }, "rooCloudCTA": { - "title": "Roo Code Cloud sắp ra mắt!", + "title": "Roo Code Cloud đang phát triển!", "description": "Chạy các agent từ xa trên cloud, truy cập các tác vụ của bạn từ mọi nơi, cộng tác với người khác và nhiều hơn nữa.", - "joinWaitlist": "Tham gia danh sách chờ để được truy cập sớm." + "joinWaitlist": "Đăng ký để nhận các cập nhật mới nhất." }, "editMessage": { "placeholder": "Chỉnh sửa tin nhắn của bạn..." diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 7526cf31a7..c9e6b5afbb 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -377,6 +377,10 @@ "description": "Không cần khóa API, nhưng người dùng cần giúp sao chép và dán thông tin vào AI trò chuyện web.", "instructions": "Trong quá trình sử dụng, một hộp thoại sẽ xuất hiện và tin nhắn hiện tại sẽ được tự động sao chép vào clipboard. Bạn cần dán chúng vào các phiên bản web của AI (như ChatGPT hoặc Claude), sau đó sao chép phản hồi của AI trở lại hộp thoại và nhấp vào nút xác nhận." }, + "roo": { + "authenticatedMessage": "Đã xác thực an toàn thông qua tài khoản Roo Code Cloud của bạn.", + "connectButton": "Kết nối với Roo Code Cloud" + }, "openRouter": { "providerRouting": { "title": "Định tuyến nhà cung cấp OpenRouter", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 1d446b43f5..83da424df0 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -349,9 +349,9 @@ "ariaLabel": "版本 {{version}} - 点击查看发布说明" }, "rooCloudCTA": { - "title": "Roo Code Cloud 即将推出!", + "title": "Roo Code Cloud 正在进化!", "description": "在云端运行远程代理,随时随地访问任务,与他人协作等更多功能。", - "joinWaitlist": "加入等待列表获取早期访问权限。" + "joinWaitlist": "注册获取最新更新。" }, "command": { "triggerDescription": "触发 {{name}} 命令" diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index c1985e7490..cc16cf349d 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -377,6 +377,10 @@ "description": "不需要 API 密钥,但用户需要帮助将信息复制并粘贴到网页聊天 AI。", "instructions": "使用期间,将弹出对话框并自动将当前消息复制到剪贴板。您需要将这些内容粘贴到 AI 的网页版本(如 ChatGPT 或 Claude),然后将 AI 的回复复制回对话框并点击确认按钮。" }, + "roo": { + "authenticatedMessage": "已通过 Roo Code Cloud 账户安全认证。", + "connectButton": "连接到 Roo Code Cloud" + }, "openRouter": { "providerRouting": { "title": "OpenRouter 提供商路由", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 0386a7540c..9cfbef6765 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -352,9 +352,9 @@ "ariaLabel": "版本 {{version}} - 點選查看發布說明" }, "rooCloudCTA": { - "title": "Roo Code Cloud 即將推出!", + "title": "Roo Code Cloud 正在進化!", "description": "在雲端執行 Roomote 遠端代理、隨時隨地存取您的工作、與他人協作等等。", - "joinWaitlist": "加入等候名單以獲得早期存取權限。" + "joinWaitlist": "註冊以獲得最新更新。" }, "command": { "triggerDescription": "觸發 {{name}} 命令" diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 6a673de60a..791be9ac02 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -377,6 +377,10 @@ "description": "不需要 API 金鑰,但使用者需要協助將資訊複製並貼上到網頁聊天 AI。", "instructions": "使用期間會彈出對話框,並自動將目前訊息複製到剪貼簿。您需要將這些內容貼上到網頁版 AI(如 ChatGPT 或 Claude),然後將 AI 的回覆複製回對話框並點選確認按鈕。" }, + "roo": { + "authenticatedMessage": "已透過 Roo Code Cloud 帳戶安全認證。", + "connectButton": "連接到 Roo Code Cloud" + }, "openRouter": { "providerRouting": { "title": "OpenRouter 供應商路由", From 532728ee85664ff856efe8c735189f32f035aeca Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Tue, 19 Aug 2025 04:01:01 -1000 Subject: [PATCH 18/34] Expose thinking tokens for `roo/sonic` (#7212) Co-authored-by: Roo Code Co-authored-by: Matt Rubens --- .env.sample | 1 + packages/types/src/providers/roo.ts | 4 +- src/api/providers/__tests__/roo.spec.ts | 2 +- .../base-openai-compatible-provider.ts | 14 ++++-- src/api/providers/roo.ts | 46 +++++++++++++++++-- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/.env.sample b/.env.sample index d89ef72792..aebe5cca44 100644 --- a/.env.sample +++ b/.env.sample @@ -3,3 +3,4 @@ POSTHOG_API_KEY=key-goes-here # Roo Code Cloud / Local Development CLERK_BASE_URL=https://epic-chamois-85.clerk.accounts.dev ROO_CODE_API_URL=http://localhost:3000 +ROO_CODE_PROVIDER_URL=http://localhost:8080/proxy/v1 diff --git a/packages/types/src/providers/roo.ts b/packages/types/src/providers/roo.ts index a213e35780..958e3cef7b 100644 --- a/packages/types/src/providers/roo.ts +++ b/packages/types/src/providers/roo.ts @@ -10,10 +10,10 @@ export const rooModels = { maxTokens: 8192, contextWindow: 262_144, supportsImages: false, - supportsPromptCache: false, + supportsPromptCache: true, inputPrice: 0, outputPrice: 0, description: - "Stealth coding model with 262K context window, accessible for free through Roo Code Cloud for a limited time. (Note: prompts and completions are logged by the model creator and used to improve the model.)", + "A stealth reasoning model that is blazing fast and excels at agentic coding, accessible for free through Roo Code Cloud for a limited time. (Note: prompts and completions are logged by the model creator and used to improve the model.)", }, } as const satisfies Record diff --git a/src/api/providers/__tests__/roo.spec.ts b/src/api/providers/__tests__/roo.spec.ts index 5c89e8e1ad..f774027538 100644 --- a/src/api/providers/__tests__/roo.spec.ts +++ b/src/api/providers/__tests__/roo.spec.ts @@ -331,7 +331,7 @@ describe("RooHandler", () => { expect(modelInfo.info.maxTokens).toBe(8192) expect(modelInfo.info.contextWindow).toBe(262_144) expect(modelInfo.info.supportsImages).toBe(false) - expect(modelInfo.info.supportsPromptCache).toBe(false) + expect(modelInfo.info.supportsPromptCache).toBe(true) expect(modelInfo.info.inputPrice).toBe(0) expect(modelInfo.info.outputPrice).toBe(0) }) diff --git a/src/api/providers/base-openai-compatible-provider.ts b/src/api/providers/base-openai-compatible-provider.ts index f196b5f309..3c824f2651 100644 --- a/src/api/providers/base-openai-compatible-provider.ts +++ b/src/api/providers/base-openai-compatible-provider.ts @@ -62,11 +62,11 @@ export abstract class BaseOpenAiCompatibleProvider }) } - override async *createMessage( + protected createStream( systemPrompt: string, messages: Anthropic.Messages.MessageParam[], metadata?: ApiHandlerCreateMessageMetadata, - ): ApiStream { + ) { const { id: model, info: { maxTokens: max_tokens }, @@ -83,7 +83,15 @@ export abstract class BaseOpenAiCompatibleProvider stream_options: { include_usage: true }, } - const stream = await this.client.chat.completions.create(params) + return this.client.chat.completions.create(params) + } + + override async *createMessage( + systemPrompt: string, + messages: Anthropic.Messages.MessageParam[], + metadata?: ApiHandlerCreateMessageMetadata, + ): ApiStream { + const stream = await this.createStream(systemPrompt, messages, metadata) for await (const chunk of stream) { const delta = chunk.choices[0]?.delta diff --git a/src/api/providers/roo.ts b/src/api/providers/roo.ts index 3b0540c2ea..ade1c360fc 100644 --- a/src/api/providers/roo.ts +++ b/src/api/providers/roo.ts @@ -1,10 +1,14 @@ +import { Anthropic } from "@anthropic-ai/sdk" import { rooDefaultModelId, rooModels, type RooModelId } from "@roo-code/types" import { CloudService } from "@roo-code/cloud" import type { ApiHandlerOptions } from "../../shared/api" -import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" +import { ApiStream } from "../transform/stream" import { t } from "../../i18n" +import type { ApiHandlerCreateMessageMetadata } from "../index" +import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" + export class RooHandler extends BaseOpenAiCompatibleProvider { constructor(options: ApiHandlerOptions) { // Check if CloudService is available and get the session token. @@ -21,7 +25,7 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { super({ ...options, providerName: "Roo Code Cloud", - baseURL: "https://api.roocode.com/proxy/v1", + baseURL: process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy/v1", apiKey: sessionToken, defaultProviderModelId: rooDefaultModelId, providerModels: rooModels, @@ -29,6 +33,42 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { }) } + override async *createMessage( + systemPrompt: string, + messages: Anthropic.Messages.MessageParam[], + metadata?: ApiHandlerCreateMessageMetadata, + ): ApiStream { + const stream = await this.createStream(systemPrompt, messages, metadata) + + for await (const chunk of stream) { + const delta = chunk.choices[0]?.delta + + if (delta) { + if (delta.content) { + yield { + type: "text", + text: delta.content, + } + } + + if ("reasoning_content" in delta && typeof delta.reasoning_content === "string") { + yield { + type: "reasoning", + text: delta.reasoning_content, + } + } + } + + if (chunk.usage) { + yield { + type: "usage", + inputTokens: chunk.usage.prompt_tokens || 0, + outputTokens: chunk.usage.completion_tokens || 0, + } + } + } + } + override getModel() { const modelId = this.options.apiModelId || rooDefaultModelId const modelInfo = this.providerModels[modelId as RooModelId] ?? this.providerModels[rooDefaultModelId] @@ -44,7 +84,7 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { maxTokens: 8192, contextWindow: 262_144, supportsImages: false, - supportsPromptCache: false, + supportsPromptCache: true, inputPrice: 0, outputPrice: 0, }, From ede26b8036e64716988ef3ff402b60ed30433b5a Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 19 Aug 2025 07:19:50 -0700 Subject: [PATCH 19/34] Increase sonic max_tokens to 16384 (#7218) --- packages/types/src/providers/roo.ts | 2 +- src/api/providers/__tests__/roo.spec.ts | 2 +- src/api/providers/roo.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/types/src/providers/roo.ts b/packages/types/src/providers/roo.ts index 958e3cef7b..c95e500f54 100644 --- a/packages/types/src/providers/roo.ts +++ b/packages/types/src/providers/roo.ts @@ -7,7 +7,7 @@ export const rooDefaultModelId: RooModelId = "roo/sonic" export const rooModels = { "roo/sonic": { - maxTokens: 8192, + maxTokens: 16_384, contextWindow: 262_144, supportsImages: false, supportsPromptCache: true, diff --git a/src/api/providers/__tests__/roo.spec.ts b/src/api/providers/__tests__/roo.spec.ts index f774027538..b16a6e7ac1 100644 --- a/src/api/providers/__tests__/roo.spec.ts +++ b/src/api/providers/__tests__/roo.spec.ts @@ -328,7 +328,7 @@ describe("RooHandler", () => { expect(modelInfo.id).toBe("unknown-model-id") expect(modelInfo.info).toBeDefined() // Should return fallback info for unknown models - expect(modelInfo.info.maxTokens).toBe(8192) + expect(modelInfo.info.maxTokens).toBe(16_384) expect(modelInfo.info.contextWindow).toBe(262_144) expect(modelInfo.info.supportsImages).toBe(false) expect(modelInfo.info.supportsPromptCache).toBe(true) diff --git a/src/api/providers/roo.ts b/src/api/providers/roo.ts index ade1c360fc..d986d6cd10 100644 --- a/src/api/providers/roo.ts +++ b/src/api/providers/roo.ts @@ -81,7 +81,7 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { return { id: modelId as RooModelId, info: { - maxTokens: 8192, + maxTokens: 16_384, contextWindow: 262_144, supportsImages: false, supportsPromptCache: true, From 865fb18334620d24619993f5157b5c9b6603c2ec Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 19 Aug 2025 09:24:20 -0700 Subject: [PATCH 20/34] Release v3.25.18 (#7219) --- .changeset/v3.25.18.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/v3.25.18.md diff --git a/.changeset/v3.25.18.md b/.changeset/v3.25.18.md new file mode 100644 index 0000000000..868c438be9 --- /dev/null +++ b/.changeset/v3.25.18.md @@ -0,0 +1,8 @@ +--- +"roo-cline": patch +--- + +- Fix: respect enableReasoningEffort setting when determining reasoning usage (#7048 by @ikbencasdoei, PR by @app/roomote) +- Fix: prevent duplicate LM Studio models with case-insensitive deduplication (#6954 by @fbuechler, PR by @daniel-lxs) +- Add support for Sonic model (thanks @mrubens!) +- Feat: simplify ask_followup_question prompt documentation (thanks @daniel-lxs!) From 9a7ddab1bce272f8056767348cccddf769cbb201 Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:57:57 -0500 Subject: [PATCH 21/34] feat: simple read_file tool for single-file-only models (#7222) --- packages/types/src/index.ts | 1 + packages/types/src/single-file-read-models.ts | 32 ++ .../presentAssistantMessage.ts | 26 +- src/core/prompts/system.ts | 4 + src/core/prompts/tools/index.ts | 14 +- src/core/prompts/tools/simple-read-file.ts | 35 +++ src/core/task/Task.ts | 2 + src/core/tools/simpleReadFileTool.ts | 287 ++++++++++++++++++ 8 files changed, 397 insertions(+), 4 deletions(-) create mode 100644 packages/types/src/single-file-read-models.ts create mode 100644 src/core/prompts/tools/simple-read-file.ts create mode 100644 src/core/tools/simpleReadFileTool.ts diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index c65d070086..b151067d1d 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -12,6 +12,7 @@ export * from "./message.js" export * from "./mode.js" export * from "./model.js" export * from "./provider-settings.js" +export * from "./single-file-read-models.js" export * from "./task.js" export * from "./todo.js" export * from "./telemetry.js" diff --git a/packages/types/src/single-file-read-models.ts b/packages/types/src/single-file-read-models.ts new file mode 100644 index 0000000000..4e6f4e1f57 --- /dev/null +++ b/packages/types/src/single-file-read-models.ts @@ -0,0 +1,32 @@ +/** + * Configuration for models that should use simplified single-file read_file tool + * These models will use the simpler ... format + * instead of the more complex multi-file args format + */ + +// List of model IDs (or patterns) that should use single file reads only +export const SINGLE_FILE_READ_MODELS = new Set(["roo/sonic"]) + +/** + * Check if a model should use single file read format + * @param modelId The model ID to check + * @returns true if the model should use single file reads + */ +export function shouldUseSingleFileRead(modelId: string): boolean { + // Direct match + if (SINGLE_FILE_READ_MODELS.has(modelId)) { + return true + } + + // Pattern matching for model families + // Check if model ID starts with any configured pattern + // Using Array.from for compatibility with older TypeScript targets + const patterns = Array.from(SINGLE_FILE_READ_MODELS) + for (const pattern of patterns) { + if (pattern.endsWith("*") && modelId.startsWith(pattern.slice(0, -1))) { + return true + } + } + + return false +} diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index acdc7f5412..a8b90728b1 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -10,6 +10,8 @@ import type { ToolParamName, ToolResponse } from "../../shared/tools" import { fetchInstructionsTool } from "../tools/fetchInstructionsTool" import { listFilesTool } from "../tools/listFilesTool" import { getReadFileToolDescription, readFileTool } from "../tools/readFileTool" +import { getSimpleReadFileToolDescription, simpleReadFileTool } from "../tools/simpleReadFileTool" +import { shouldUseSingleFileRead } from "@roo-code/types" import { writeToFileTool } from "../tools/writeToFileTool" import { applyDiffTool } from "../tools/multiApplyDiffTool" import { insertContentTool } from "../tools/insertContentTool" @@ -155,7 +157,13 @@ export async function presentAssistantMessage(cline: Task) { case "execute_command": return `[${block.name} for '${block.params.command}']` case "read_file": - return getReadFileToolDescription(block.name, block.params) + // Check if this model should use the simplified description + const modelId = cline.api.getModel().id + if (shouldUseSingleFileRead(modelId)) { + return getSimpleReadFileToolDescription(block.name, block.params) + } else { + return getReadFileToolDescription(block.name, block.params) + } case "fetch_instructions": return `[${block.name} for '${block.params.task}']` case "write_to_file": @@ -454,8 +462,20 @@ export async function presentAssistantMessage(cline: Task) { await searchAndReplaceTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag) break case "read_file": - await readFileTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag) - + // Check if this model should use the simplified single-file read tool + const modelId = cline.api.getModel().id + if (shouldUseSingleFileRead(modelId)) { + await simpleReadFileTool( + cline, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + } else { + await readFileTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag) + } break case "fetch_instructions": await fetchInstructionsTool(cline, block, askApproval, handleError, pushToolResult) diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 4ed1185da7..3cc327c815 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -61,6 +61,7 @@ async function generatePrompt( partialReadsEnabled?: boolean, settings?: SystemPromptSettings, todoList?: TodoItem[], + modelId?: string, ): Promise { if (!context) { throw new Error("Extension context is required for generating system prompt") @@ -106,6 +107,7 @@ ${getToolDescriptionsForMode( partialReadsEnabled, settings, enableMcpServerCreation, + modelId, )} ${getToolUseGuidelinesSection(codeIndexManager)} @@ -150,6 +152,7 @@ export const SYSTEM_PROMPT = async ( partialReadsEnabled?: boolean, settings?: SystemPromptSettings, todoList?: TodoItem[], + modelId?: string, ): Promise => { if (!context) { throw new Error("Extension context is required for generating system prompt") @@ -221,5 +224,6 @@ ${customInstructions}` partialReadsEnabled, settings, todoList, + modelId, ) } diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index d455abb8d7..3eb112d270 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -7,7 +7,9 @@ import { Mode, getModeConfig, isToolAllowedForMode, getGroupName } from "../../. import { ToolArgs } from "./types" import { getExecuteCommandDescription } from "./execute-command" import { getReadFileDescription } from "./read-file" +import { getSimpleReadFileDescription } from "./simple-read-file" import { getFetchInstructionsDescription } from "./fetch-instructions" +import { shouldUseSingleFileRead } from "@roo-code/types" import { getWriteToFileDescription } from "./write-to-file" import { getSearchFilesDescription } from "./search-files" import { getListFilesDescription } from "./list-files" @@ -28,7 +30,14 @@ import { CodeIndexManager } from "../../../services/code-index/manager" // Map of tool names to their description functions const toolDescriptionMap: Record string | undefined> = { execute_command: (args) => getExecuteCommandDescription(args), - read_file: (args) => getReadFileDescription(args), + read_file: (args) => { + // Check if the current model should use the simplified read_file tool + const modelId = args.settings?.modelId + if (modelId && shouldUseSingleFileRead(modelId)) { + return getSimpleReadFileDescription(args) + } + return getReadFileDescription(args) + }, fetch_instructions: (args) => getFetchInstructionsDescription(args.settings?.enableMcpServerCreation), write_to_file: (args) => getWriteToFileDescription(args), search_files: (args) => getSearchFilesDescription(args), @@ -62,6 +71,7 @@ export function getToolDescriptionsForMode( partialReadsEnabled?: boolean, settings?: Record, enableMcpServerCreation?: boolean, + modelId?: string, ): string { const config = getModeConfig(mode, customModes) const args: ToolArgs = { @@ -74,6 +84,7 @@ export function getToolDescriptionsForMode( settings: { ...settings, enableMcpServerCreation, + modelId, }, experiments, } @@ -138,6 +149,7 @@ export function getToolDescriptionsForMode( export { getExecuteCommandDescription, getReadFileDescription, + getSimpleReadFileDescription, getFetchInstructionsDescription, getWriteToFileDescription, getSearchFilesDescription, diff --git a/src/core/prompts/tools/simple-read-file.ts b/src/core/prompts/tools/simple-read-file.ts new file mode 100644 index 0000000000..28f4f1129e --- /dev/null +++ b/src/core/prompts/tools/simple-read-file.ts @@ -0,0 +1,35 @@ +import { ToolArgs } from "./types" + +/** + * Generate a simplified read_file tool description for models that only support single file reads + * Uses the simpler format: file/path.ext + */ +export function getSimpleReadFileDescription(args: ToolArgs): string { + return `## read_file +Description: Request to read the contents of a file. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when discussing code. + +Parameters: +- path: (required) File path (relative to workspace directory ${args.cwd}) + +Usage: + +path/to/file + + +Examples: + +1. Reading a TypeScript file: + +src/app.ts + + +2. Reading a configuration file: + +config.json + + +3. Reading a markdown file: + +README.md +` +} diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index cff8d5aec3..34f3218236 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -2226,6 +2226,8 @@ export class Task extends EventEmitter implements TaskLike { todoListEnabled: apiConfiguration?.todoListEnabled ?? true, useAgentRules: vscode.workspace.getConfiguration("roo-cline").get("useAgentRules") ?? true, }, + undefined, // todoList + this.api.getModel().id, ) })() } diff --git a/src/core/tools/simpleReadFileTool.ts b/src/core/tools/simpleReadFileTool.ts new file mode 100644 index 0000000000..ee6656c5c8 --- /dev/null +++ b/src/core/tools/simpleReadFileTool.ts @@ -0,0 +1,287 @@ +import path from "path" +import { isBinaryFile } from "isbinaryfile" + +import { Task } from "../task/Task" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { formatResponse } from "../prompts/responses" +import { t } from "../../i18n" +import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" +import { RecordSource } from "../context-tracking/FileContextTrackerTypes" +import { isPathOutsideWorkspace } from "../../utils/pathUtils" +import { getReadablePath } from "../../utils/path" +import { countFileLines } from "../../integrations/misc/line-counter" +import { readLines } from "../../integrations/misc/read-lines" +import { extractTextFromFile, addLineNumbers, getSupportedBinaryFormats } from "../../integrations/misc/extract-text" +import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter" +import { + DEFAULT_MAX_IMAGE_FILE_SIZE_MB, + DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB, + isSupportedImageFormat, + validateImageForProcessing, + processImageFile, +} from "./helpers/imageHelpers" + +/** + * Simplified read file tool for models that only support single file reads + * Uses the format: file/path.ext + * + * This is a streamlined version of readFileTool that: + * - Only accepts a single path parameter + * - Does not support multiple files + * - Does not support line ranges + * - Has simpler XML parsing + */ +export async function simpleReadFileTool( + cline: Task, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + _removeClosingTag: RemoveClosingTag, +) { + const filePath: string | undefined = block.params.path + + // Check if the current model supports images + const modelInfo = cline.api.getModel().info + const supportsImages = modelInfo.supportsImages ?? false + + // Handle partial message + if (block.partial) { + const fullPath = filePath ? path.resolve(cline.cwd, filePath) : "" + const sharedMessageProps: ClineSayTool = { + tool: "readFile", + path: getReadablePath(cline.cwd, filePath || ""), + isOutsideWorkspace: filePath ? isPathOutsideWorkspace(fullPath) : false, + } + const partialMessage = JSON.stringify({ + ...sharedMessageProps, + content: undefined, + } satisfies ClineSayTool) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } + + // Validate path parameter + if (!filePath) { + cline.consecutiveMistakeCount++ + cline.recordToolError("read_file") + const errorMsg = await cline.sayAndCreateMissingParamError("read_file", "path") + pushToolResult(`${errorMsg}`) + return + } + + const relPath = filePath + const fullPath = path.resolve(cline.cwd, relPath) + + try { + // Check RooIgnore validation + const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) + if (!accessAllowed) { + await cline.say("rooignore_error", relPath) + const errorMsg = formatResponse.rooIgnoreError(relPath) + pushToolResult(`${relPath}${errorMsg}`) + return + } + + // Get max read file line setting + const { maxReadFileLine = -1 } = (await cline.providerRef.deref()?.getState()) ?? {} + + // Create approval message + const isOutsideWorkspace = isPathOutsideWorkspace(fullPath) + let lineSnippet = "" + if (maxReadFileLine === 0) { + lineSnippet = t("tools:readFile.definitionsOnly") + } else if (maxReadFileLine > 0) { + lineSnippet = t("tools:readFile.maxLines", { max: maxReadFileLine }) + } + + const completeMessage = JSON.stringify({ + tool: "readFile", + path: getReadablePath(cline.cwd, relPath), + isOutsideWorkspace, + content: fullPath, + reason: lineSnippet, + } satisfies ClineSayTool) + + const { response, text, images } = await cline.ask("tool", completeMessage, false) + + if (response !== "yesButtonClicked") { + // Handle denial + if (text) { + await cline.say("user_feedback", text, images) + } + cline.didRejectTool = true + + const statusMessage = text ? formatResponse.toolDeniedWithFeedback(text) : formatResponse.toolDenied() + + pushToolResult(`${statusMessage}\n${relPath}Denied by user`) + return + } + + // Handle approval with feedback + if (text) { + await cline.say("user_feedback", text, images) + } + + // Process the file + const [totalLines, isBinary] = await Promise.all([countFileLines(fullPath), isBinaryFile(fullPath)]) + + // Handle binary files + if (isBinary) { + const fileExtension = path.extname(relPath).toLowerCase() + const supportedBinaryFormats = getSupportedBinaryFormats() + + // Check if it's a supported image format + if (isSupportedImageFormat(fileExtension)) { + try { + const { + maxImageFileSize = DEFAULT_MAX_IMAGE_FILE_SIZE_MB, + maxTotalImageSize = DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB, + } = (await cline.providerRef.deref()?.getState()) ?? {} + + // Validate image for processing + const validationResult = await validateImageForProcessing( + fullPath, + supportsImages, + maxImageFileSize, + maxTotalImageSize, + 0, // No cumulative memory for single file + ) + + if (!validationResult.isValid) { + await cline.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource) + pushToolResult( + `${relPath}\n${validationResult.notice}\n`, + ) + return + } + + // Process the image + const imageResult = await processImageFile(fullPath) + await cline.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource) + + // Return result with image data + const result = formatResponse.toolResult( + `${relPath}\n${imageResult.notice}\n`, + supportsImages ? [imageResult.dataUrl] : undefined, + ) + + if (typeof result === "string") { + pushToolResult(result) + } else { + pushToolResult(result) + } + return + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error) + pushToolResult( + `${relPath}Error reading image file: ${errorMsg}`, + ) + await handleError( + `reading image file ${relPath}`, + error instanceof Error ? error : new Error(errorMsg), + ) + return + } + } + + // Check if it's a supported binary format that can be processed + if (supportedBinaryFormats && supportedBinaryFormats.includes(fileExtension)) { + // For supported binary formats (.pdf, .docx, .ipynb), continue to extractTextFromFile + // Fall through to the normal extractTextFromFile processing below + } else { + // Handle unknown binary format + const fileFormat = fileExtension.slice(1) || "bin" + pushToolResult( + `${relPath}\nBinary file - content not displayed\n`, + ) + return + } + } + + // Handle definitions-only mode + if (maxReadFileLine === 0) { + try { + const defResult = await parseSourceCodeDefinitionsForFile(fullPath, cline.rooIgnoreController) + if (defResult) { + let xmlInfo = `Showing only definitions. Use standard read_file if you need to read actual content\n` + pushToolResult( + `${relPath}\n${defResult}\n${xmlInfo}`, + ) + } + } catch (error) { + if (error instanceof Error && error.message.startsWith("Unsupported language:")) { + console.warn(`[simple_read_file] Warning: ${error.message}`) + } else { + console.error( + `[simple_read_file] Unhandled error: ${error instanceof Error ? error.message : String(error)}`, + ) + } + } + return + } + + // Handle files exceeding line threshold + if (maxReadFileLine > 0 && totalLines > maxReadFileLine) { + const content = addLineNumbers(await readLines(fullPath, maxReadFileLine - 1, 0)) + const lineRangeAttr = ` lines="1-${maxReadFileLine}"` + let xmlInfo = `\n${content}\n` + + try { + const defResult = await parseSourceCodeDefinitionsForFile(fullPath, cline.rooIgnoreController) + if (defResult) { + xmlInfo += `${defResult}\n` + } + xmlInfo += `Showing only ${maxReadFileLine} of ${totalLines} total lines. File is too large for complete display\n` + pushToolResult(`${relPath}\n${xmlInfo}`) + } catch (error) { + if (error instanceof Error && error.message.startsWith("Unsupported language:")) { + console.warn(`[simple_read_file] Warning: ${error.message}`) + } else { + console.error( + `[simple_read_file] Unhandled error: ${error instanceof Error ? error.message : String(error)}`, + ) + } + } + return + } + + // Handle normal file read + const content = await extractTextFromFile(fullPath) + const lineRangeAttr = ` lines="1-${totalLines}"` + let xmlInfo = totalLines > 0 ? `\n${content}\n` : `` + + if (totalLines === 0) { + xmlInfo += `File is empty\n` + } + + // Track file read + await cline.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource) + + // Return the result + if (text) { + const statusMessage = formatResponse.toolApprovedWithFeedback(text) + pushToolResult(`${statusMessage}\n${relPath}\n${xmlInfo}`) + } else { + pushToolResult(`${relPath}\n${xmlInfo}`) + } + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error) + pushToolResult(`${relPath}Error reading file: ${errorMsg}`) + await handleError(`reading file ${relPath}`, error instanceof Error ? error : new Error(errorMsg)) + } +} + +/** + * Get description for the simple read file tool + * @param blockName The name of the tool block + * @param blockParams The parameters passed to the tool + * @returns A description string for the tool use + */ +export function getSimpleReadFileToolDescription(blockName: string, blockParams: any): string { + if (blockParams.path) { + return `[${blockName} for '${blockParams.path}']` + } else { + return `[${blockName} with missing path]` + } +} From 5d54ce96673af4cfc0cdb0c2a4af688638d22690 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 19 Aug 2025 11:04:31 -0700 Subject: [PATCH 22/34] chore: add changeset for v3.25.18 (#7223) --- .changeset/v3.25.18-2.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/v3.25.18-2.md diff --git a/.changeset/v3.25.18-2.md b/.changeset/v3.25.18-2.md new file mode 100644 index 0000000000..4742944d15 --- /dev/null +++ b/.changeset/v3.25.18-2.md @@ -0,0 +1,9 @@ +--- +"roo-cline": patch +--- + +- Fix: respect enableReasoningEffort setting when determining reasoning usage (#7048 by @ikbencasdoei, PR by @app/roomote) +- Fix: prevent duplicate LM Studio models with case-insensitive deduplication (#6954 by @fbuechler, PR by @daniel-lxs) +- Add support for Sonic model (thanks @mrubens!) +- Feat: simplify ask_followup_question prompt documentation (thanks @daniel-lxs!) +- Feat: simple read_file tool for single-file-only models (thanks @daniel-lxs!) From b06005d32149b0e4a73f12be558edc4866b6ccce Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:54:44 -0700 Subject: [PATCH 23/34] fix: add missing zaiApiKey and doubaoApiKey to SECRET_STATE_KEYS (#7083) Fixes first-time initialization issue with Z AI and Doubao providers where the API keys were not recognized as valid secret keys, causing the configuration to fail during initial setup. Fixes #7082 Co-authored-by: Roo Code --- packages/types/src/global-settings.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index c071726d8a..5d0854390d 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -178,6 +178,7 @@ export const SECRET_STATE_KEYS = [ "openAiNativeApiKey", "cerebrasApiKey", "deepSeekApiKey", + "doubaoApiKey", "moonshotApiKey", "mistralApiKey", "unboundApiKey", @@ -193,6 +194,7 @@ export const SECRET_STATE_KEYS = [ "codebaseIndexMistralApiKey", "huggingFaceApiKey", "sambaNovaApiKey", + "zaiApiKey", "fireworksApiKey", "ioIntelligenceApiKey", ] as const satisfies readonly (keyof ProviderSettings)[] From 21e9d24ba7ac8212ca621d5a54fcad6911a7760b Mon Sep 17 00:00:00 2001 From: NaccOll Date: Wed, 20 Aug 2025 04:24:29 +0800 Subject: [PATCH 24/34] feat: add new models and update configurations for vscode-lm (#7201) --- packages/types/src/providers/vscode-llm.ts | 48 +++++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/types/src/providers/vscode-llm.ts b/packages/types/src/providers/vscode-llm.ts index bf38cb814b..92e871a01a 100644 --- a/packages/types/src/providers/vscode-llm.ts +++ b/packages/types/src/providers/vscode-llm.ts @@ -101,6 +101,18 @@ export const vscodeLlmModels = { supportsToolCalling: true, maxInputTokens: 81638, }, + "claude-4-sonnet": { + contextWindow: 128000, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "claude-sonnet-4", + version: "claude-sonnet-4", + name: "Claude Sonnet 4", + supportsToolCalling: true, + maxInputTokens: 111836, + }, "gemini-2.0-flash-001": { contextWindow: 127827, supportsImages: true, @@ -114,7 +126,7 @@ export const vscodeLlmModels = { maxInputTokens: 127827, }, "gemini-2.5-pro": { - contextWindow: 63830, + contextWindow: 128000, supportsImages: true, supportsPromptCache: false, inputPrice: 0, @@ -123,10 +135,10 @@ export const vscodeLlmModels = { version: "gemini-2.5-pro-preview-03-25", name: "Gemini 2.5 Pro (Preview)", supportsToolCalling: true, - maxInputTokens: 63830, + maxInputTokens: 108637, }, "o4-mini": { - contextWindow: 111446, + contextWindow: 128000, supportsImages: false, supportsPromptCache: false, inputPrice: 0, @@ -135,10 +147,10 @@ export const vscodeLlmModels = { version: "o4-mini-2025-04-16", name: "o4-mini (Preview)", supportsToolCalling: true, - maxInputTokens: 111446, + maxInputTokens: 111452, }, "gpt-4.1": { - contextWindow: 111446, + contextWindow: 128000, supportsImages: true, supportsPromptCache: false, inputPrice: 0, @@ -147,7 +159,31 @@ export const vscodeLlmModels = { version: "gpt-4.1-2025-04-14", name: "GPT-4.1 (Preview)", supportsToolCalling: true, - maxInputTokens: 111446, + maxInputTokens: 111452, + }, + "gpt-5-mini": { + contextWindow: 128000, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "gpt-5-mini", + version: "gpt-5-mini", + name: "GPT-5 mini (Preview)", + supportsToolCalling: true, + maxInputTokens: 108637, + }, + "gpt-5": { + contextWindow: 128000, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + family: "gpt-5", + version: "gpt-5", + name: "GPT-5 (Preview)", + supportsToolCalling: true, + maxInputTokens: 108637, }, } as const satisfies Record< string, From 613abe08fc511d02307a1b3e5f4ab53d6162995b Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 19 Aug 2025 14:00:28 -0700 Subject: [PATCH 25/34] More changes (#7232) --- .changeset/v3.25.18-3.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/v3.25.18-3.md diff --git a/.changeset/v3.25.18-3.md b/.changeset/v3.25.18-3.md new file mode 100644 index 0000000000..9628455744 --- /dev/null +++ b/.changeset/v3.25.18-3.md @@ -0,0 +1,6 @@ +--- +"roo-cline": patch +--- + +- Fix: Add missing zaiApiKey and doubaoApiKey to SECRET_STATE_KEYS (#7082 by @app/roomote) +- Feat: Add new models and update configurations for vscode-lm (thanks @NaccOll!) From c249ff81e9dcd6cef2ba28a00b8cb9f3edd8461e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 17:45:45 -0700 Subject: [PATCH 26/34] Changeset version bump (#7220) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matt Rubens --- .changeset/v3.25.18-2.md | 9 --------- .changeset/v3.25.18-3.md | 6 ------ .changeset/v3.25.18.md | 8 -------- CHANGELOG.md | 10 ++++++++++ src/package.json | 2 +- 5 files changed, 11 insertions(+), 24 deletions(-) delete mode 100644 .changeset/v3.25.18-2.md delete mode 100644 .changeset/v3.25.18-3.md delete mode 100644 .changeset/v3.25.18.md diff --git a/.changeset/v3.25.18-2.md b/.changeset/v3.25.18-2.md deleted file mode 100644 index 4742944d15..0000000000 --- a/.changeset/v3.25.18-2.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"roo-cline": patch ---- - -- Fix: respect enableReasoningEffort setting when determining reasoning usage (#7048 by @ikbencasdoei, PR by @app/roomote) -- Fix: prevent duplicate LM Studio models with case-insensitive deduplication (#6954 by @fbuechler, PR by @daniel-lxs) -- Add support for Sonic model (thanks @mrubens!) -- Feat: simplify ask_followup_question prompt documentation (thanks @daniel-lxs!) -- Feat: simple read_file tool for single-file-only models (thanks @daniel-lxs!) diff --git a/.changeset/v3.25.18-3.md b/.changeset/v3.25.18-3.md deleted file mode 100644 index 9628455744..0000000000 --- a/.changeset/v3.25.18-3.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"roo-cline": patch ---- - -- Fix: Add missing zaiApiKey and doubaoApiKey to SECRET_STATE_KEYS (#7082 by @app/roomote) -- Feat: Add new models and update configurations for vscode-lm (thanks @NaccOll!) diff --git a/.changeset/v3.25.18.md b/.changeset/v3.25.18.md deleted file mode 100644 index 868c438be9..0000000000 --- a/.changeset/v3.25.18.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"roo-cline": patch ---- - -- Fix: respect enableReasoningEffort setting when determining reasoning usage (#7048 by @ikbencasdoei, PR by @app/roomote) -- Fix: prevent duplicate LM Studio models with case-insensitive deduplication (#6954 by @fbuechler, PR by @daniel-lxs) -- Add support for Sonic model (thanks @mrubens!) -- Feat: simplify ask_followup_question prompt documentation (thanks @daniel-lxs!) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb6f5713c4..9c78cb0b8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Roo Code Changelog +## [3.25.18] - 2025-08-19 + +- Add new stealth Sonic model through the Roo Code Cloud provider +- Fix: respect enableReasoningEffort setting when determining reasoning usage (#7048 by @ikbencasdoei, PR by @app/roomote) +- Fix: prevent duplicate LM Studio models with case-insensitive deduplication (#6954 by @fbuechler, PR by @daniel-lxs) +- Feat: simplify ask_followup_question prompt documentation (thanks @daniel-lxs!) +- Feat: simple read_file tool for single-file-only models (thanks @daniel-lxs!) +- Fix: Add missing zaiApiKey and doubaoApiKey to SECRET_STATE_KEYS (#7082 by @app/roomote) +- Feat: Add new models and update configurations for vscode-lm (thanks @NaccOll!) + ## [3.25.17] - 2025-08-17 - Fix: Resolve terminal reuse logic issues diff --git a/src/package.json b/src/package.json index d6c447db39..6ea9ab4b24 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "RooVeterinaryInc", - "version": "3.25.17", + "version": "3.25.18", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", From 912eefd7b3d4ea79bfded42920c04acdc7cae7ad Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Tue, 19 Aug 2025 21:13:44 -0500 Subject: [PATCH 27/34] Fix: Add 'roo' provider to checkExistKey function (#7239) --- src/shared/checkExistApiConfig.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/checkExistApiConfig.ts b/src/shared/checkExistApiConfig.ts index eca2dd4fe0..8acb88ed3f 100644 --- a/src/shared/checkExistApiConfig.ts +++ b/src/shared/checkExistApiConfig.ts @@ -5,8 +5,8 @@ export function checkExistKey(config: ProviderSettings | undefined) { return false } - // Special case for human-relay, fake-ai, and claude-code providers which don't need any configuration. - if (config.apiProvider && ["human-relay", "fake-ai", "claude-code"].includes(config.apiProvider)) { + // Special case for human-relay, fake-ai, claude-code, and roo providers which don't need any configuration. + if (config.apiProvider && ["human-relay", "fake-ai", "claude-code", "roo"].includes(config.apiProvider)) { return true } From 6e65aeeb13d705b29b69a69b00ff99e12a0c0341 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 19 Aug 2025 19:33:19 -0700 Subject: [PATCH 28/34] chore: add changeset for v3.25.19 (#7242) --- .changeset/v3.25.19.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/v3.25.19.md diff --git a/.changeset/v3.25.19.md b/.changeset/v3.25.19.md new file mode 100644 index 0000000000..05dd82dc7d --- /dev/null +++ b/.changeset/v3.25.19.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +- Fix: Add 'roo' provider to checkExistKey function (thanks @daniel-lxs!) From 1f7ae548ed6537e2457baf2ad8bd273c66e37ce2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 19:37:02 -0700 Subject: [PATCH 29/34] Changeset version bump (#7243) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matt Rubens --- .changeset/v3.25.19.md | 5 ----- CHANGELOG.md | 4 ++++ src/package.json | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) delete mode 100644 .changeset/v3.25.19.md diff --git a/.changeset/v3.25.19.md b/.changeset/v3.25.19.md deleted file mode 100644 index 05dd82dc7d..0000000000 --- a/.changeset/v3.25.19.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -- Fix: Add 'roo' provider to checkExistKey function (thanks @daniel-lxs!) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c78cb0b8f..08796b2d88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Roo Code Changelog +## [3.25.19] - 2025-08-19 + +- Fix issue where new users couldn't select the Roo Code Cloud provider (thanks @daniel-lxs!) + ## [3.25.18] - 2025-08-19 - Add new stealth Sonic model through the Roo Code Cloud provider diff --git a/src/package.json b/src/package.json index 6ea9ab4b24..f744232c1f 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "RooVeterinaryInc", - "version": "3.25.18", + "version": "3.25.19", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", From 94fa33bd6347c267cbba3f996f35758fed1fab13 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 19 Aug 2025 23:02:27 -0700 Subject: [PATCH 30/34] Add announcement for Sonic model (#7244) --- src/core/webview/ClineProvider.ts | 2 +- .../src/components/chat/Announcement.tsx | 125 +++++++----------- .../chat/__tests__/Announcement.spec.tsx | 60 +++++++-- webview-ui/src/i18n/locales/ca/chat.json | 6 + webview-ui/src/i18n/locales/de/chat.json | 6 + webview-ui/src/i18n/locales/en/chat.json | 13 +- webview-ui/src/i18n/locales/es/chat.json | 6 + webview-ui/src/i18n/locales/fr/chat.json | 6 + webview-ui/src/i18n/locales/hi/chat.json | 6 + webview-ui/src/i18n/locales/id/chat.json | 6 + webview-ui/src/i18n/locales/it/chat.json | 6 + webview-ui/src/i18n/locales/ja/chat.json | 6 + webview-ui/src/i18n/locales/ko/chat.json | 6 + webview-ui/src/i18n/locales/nl/chat.json | 6 + webview-ui/src/i18n/locales/pl/chat.json | 6 + webview-ui/src/i18n/locales/pt-BR/chat.json | 6 + webview-ui/src/i18n/locales/ru/chat.json | 6 + webview-ui/src/i18n/locales/tr/chat.json | 6 + webview-ui/src/i18n/locales/vi/chat.json | 6 + webview-ui/src/i18n/locales/zh-CN/chat.json | 6 + webview-ui/src/i18n/locales/zh-TW/chat.json | 6 + 21 files changed, 207 insertions(+), 95 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 04d336d957..9c28120f17 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -120,7 +120,7 @@ export class ClineProvider public isViewLaunched = false public settingsImportedAt?: number - public readonly latestAnnouncementId = "jul-29-2025-3-25-0" // Update for v3.25.0 announcement + public readonly latestAnnouncementId = "aug-20-2025-stealth-model" // Update for stealth model announcement public readonly providerSettingsManager: ProviderSettingsManager public readonly customModesManager: CustomModesManager diff --git a/webview-ui/src/components/chat/Announcement.tsx b/webview-ui/src/components/chat/Announcement.tsx index 65b78c3cd6..90ad7dffb1 100644 --- a/webview-ui/src/components/chat/Announcement.tsx +++ b/webview-ui/src/components/chat/Announcement.tsx @@ -3,9 +3,11 @@ import { Trans } from "react-i18next" import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" import { Package } from "@roo/package" - import { useAppTranslation } from "@src/i18n/TranslationContext" -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@src/components/ui" +import { useExtensionState } from "@src/context/ExtensionStateContext" +import { vscode } from "@src/utils/vscode" +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@src/components/ui" +import { Button } from "@src/components/ui" interface AnnouncementProps { hideAnnouncement: () => void @@ -23,6 +25,7 @@ interface AnnouncementProps { const Announcement = ({ hideAnnouncement }: AnnouncementProps) => { const { t } = useAppTranslation() const [open, setOpen] = useState(true) + const { cloudIsAuthenticated } = useExtensionState() return ( { {t("chat:announcement.title", { version: Package.version })} - - {t("chat:announcement.description", { version: Package.version })} -
-

{t("chat:announcement.whatsNew")}

  • •{" "} , - code: , - settingsLink: ( - { - e.preventDefault() - setOpen(false) - hideAnnouncement() - window.postMessage( - { - type: "action", - action: "settingsButtonClicked", - values: { section: "codebaseIndexing" }, - }, - "*", - ) - }} - /> - ), - }} - /> -
  • -
  • - •{" "} - , - code: , - }} - /> -
  • -
  • - •{" "} - , - code: , }} />
- , redditLink: }} - /> + +

{t("chat:announcement.stealthModel.note")}

+ +
+ {!cloudIsAuthenticated ? ( + + ) : ( +
+ , + settingsLink: ( + { + e.preventDefault() + setOpen(false) + hideAnnouncement() + window.postMessage( + { + type: "action", + action: "settingsButtonClicked", + values: { section: "provider" }, + }, + "*", + ) + }} + /> + ), + }} + /> +
+ )} +
) } -const DiscordLink = () => ( - { - e.preventDefault() - window.postMessage( - { type: "action", action: "openExternal", data: { url: "https://discord.gg/roocode" } }, - "*", - ) - }}> - Discord - -) - -const RedditLink = () => ( - { - e.preventDefault() - window.postMessage( - { type: "action", action: "openExternal", data: { url: "https://reddit.com/r/RooCode" } }, - "*", - ) - }}> - Reddit - -) - export default memo(Announcement) diff --git a/webview-ui/src/components/chat/__tests__/Announcement.spec.tsx b/webview-ui/src/components/chat/__tests__/Announcement.spec.tsx index 42c2d6ff50..c7da43032b 100644 --- a/webview-ui/src/components/chat/__tests__/Announcement.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/Announcement.spec.tsx @@ -11,17 +11,27 @@ vi.mock("@src/components/ui", () => ({ DialogDescription: ({ children }: { children: React.ReactNode }) =>
{children}
, DialogHeader: ({ children }: { children: React.ReactNode }) =>
{children}
, DialogTitle: ({ children }: { children: React.ReactNode }) =>
{children}
, + DialogFooter: ({ children }: { children: React.ReactNode }) =>
{children}
, + Button: ({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) => ( + + ), })) -// Mock the useAppTranslation hook +// Mock the useAppTranslation hook and Trans component vi.mock("@src/i18n/TranslationContext", () => ({ useAppTranslation: () => ({ t: (key: string, options?: { version: string }) => { if (key === "chat:announcement.title") { return `🎉 Roo Code ${options?.version} Released` } - if (key === "chat:announcement.description") { - return `Roo Code ${options?.version} brings powerful new features and improvements based on your feedback.` + if (key === "chat:announcement.stealthModel.feature") { + return "Stealth reasoning model with advanced capabilities" + } + if (key === "chat:announcement.stealthModel.note") { + return "Note: This is an experimental feature" + } + if (key === "chat:announcement.stealthModel.connectButton") { + return "Connect to Roo Code Cloud" } // Return key for other translations not relevant to this test return key @@ -29,6 +39,34 @@ vi.mock("@src/i18n/TranslationContext", () => ({ }), })) +// Mock react-i18next Trans component +vi.mock("react-i18next", () => ({ + Trans: ({ i18nKey, children }: { i18nKey?: string; children: React.ReactNode }) => { + if (i18nKey === "chat:announcement.stealthModel.feature") { + return <>Stealth reasoning model with advanced capabilities + } + if (i18nKey === "chat:announcement.stealthModel.selectModel") { + return <>Please select the roo/sonic model in settings + } + return <>{children} + }, +})) + +// Mock VSCodeLink +vi.mock("@vscode/webview-ui-toolkit/react", () => ({ + VSCodeLink: ({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) => ( + {children} + ), +})) + +// Mock the useExtensionState hook +vi.mock("@src/context/ExtensionStateContext", () => ({ + useExtensionState: () => ({ + apiConfiguration: null, + cloudIsAuthenticated: false, + }), +})) + describe("Announcement", () => { const mockHideAnnouncement = vi.fn() const expectedVersion = Package.version @@ -36,12 +74,16 @@ describe("Announcement", () => { it("renders the announcement with the version number from package.json", () => { render() - // Check if the mocked version number is present in the title and description + // Check if the mocked version number is present in the title expect(screen.getByText(`🎉 Roo Code ${expectedVersion} Released`)).toBeInTheDocument() - expect( - screen.getByText( - `Roo Code ${expectedVersion} brings powerful new features and improvements based on your feedback.`, - ), - ).toBeInTheDocument() + + // Check if the stealth model feature is displayed (using partial match due to bullet point) + expect(screen.getByText(/Stealth reasoning model with advanced capabilities/)).toBeInTheDocument() + + // Check if the note is displayed + expect(screen.getByText("Note: This is an experimental feature")).toBeInTheDocument() + + // Check if the connect button is displayed (since cloudIsAuthenticated is false in the mock) + expect(screen.getByText("Connect to Roo Code Cloud")).toBeInTheDocument() }) }) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 3f7f1c901a..e67ef6d0fb 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -265,6 +265,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} Llançat", + "stealthModel": { + "feature": "Model stealth GRATUÏT per temps limitat - Un model de raonament ultraràpid que destaca en codificació agèntica amb una finestra de context de 262k, disponible a través de Roo Code Cloud.", + "note": "(Nota: els prompts i completacions són registrats pel creador del model i utilitzats per millorar-lo)", + "connectButton": "Connectar a Roo Code Cloud", + "selectModel": "Selecciona roo/sonic del proveïdor Roo Code Cloud a
Configuració per començar" + }, "description": "Roo Code {{version}} porta noves funcions potents i millores significatives per millorar el vostre flux de treball de desenvolupament.", "whatsNew": "Novetats", "feature1": "Cua de Missatges: Posa en cua múltiples missatges mentre Roo està treballant, permetent-te continuar planificant el teu flux de treball sense interrupcions.", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 2eaee9feac..4994a15fab 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -265,6 +265,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} veröffentlicht", + "stealthModel": { + "feature": "Zeitlich begrenztes KOSTENLOSES Stealth-Modell - Ein blitzschnelles Reasoning-Modell, das sich bei agentic coding mit einem 262k Kontextfenster auszeichnet, verfügbar über Roo Code Cloud.", + "note": "(Hinweis: Prompts und Vervollständigungen werden vom Modellersteller protokolliert und zur Verbesserung des Modells verwendet)", + "connectButton": "Mit Roo Code Cloud verbinden", + "selectModel": "Wähle roo/sonic vom Roo Code Cloud Provider in
Einstellungen um zu beginnen" + }, "description": "Roo Code {{version}} bringt mächtige neue Funktionen und bedeutende Verbesserungen, um deinen Entwicklungsworkflow zu verbessern.", "whatsNew": "Was ist neu", "feature1": "Nachrichten-Warteschlange: Stelle mehrere Nachrichten in die Warteschlange, während Roo arbeitet, damit du deinen Workflow ohne Unterbrechung weiter planen kannst.", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index ef5ff39e9a..b3425d598d 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -274,13 +274,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} Released", - "description": "Roo Code {{version}} brings powerful new features and significant improvements to enhance your development workflow.", - "whatsNew": "What's New", - "feature1": "Message Queueing: Queue multiple messages while Roo is working, allowing you to continue planning your workflow without interruption.", - "feature2": "Custom Slash Commands: Create personalized slash commands for quick access to frequently used prompts and workflows, with full UI management.", - "feature3": "Enhanced Gemini Tools: New URL context and Google Search grounding capabilities provide Gemini models with real-time web information and enhanced research abilities.", - "hideButton": "Hide announcement", - "detailsDiscussLinks": "Get more details and discuss in Discord and Reddit 🚀" + "stealthModel": { + "feature": "Limited-time FREE stealth model - A blazing fast reasoning model that excels at agentic coding with a 262k context window, available through Roo Code Cloud.", + "note": "(Note: prompts and completions are logged by the model creator to improve the model)", + "connectButton": "Connect to Roo Code Cloud", + "selectModel": "Select roo/sonic from the Roo Code Cloud provider in
Settings to get started" + } }, "reasoning": { "thinking": "Thinking", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index dc28671d04..7d47d383bc 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -265,6 +265,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} publicado", + "stealthModel": { + "feature": "Modelo stealth GRATUITO por tiempo limitado - Un modelo de razonamiento ultrarrápido que sobresale en codificación agéntica con una ventana de contexto de 262k, disponible a través de Roo Code Cloud.", + "note": "(Nota: los prompts y completaciones son registrados por el creador del modelo y utilizados para mejorarlo)", + "connectButton": "Conectar a Roo Code Cloud", + "selectModel": "Selecciona roo/sonic del proveedor Roo Code Cloud en
Configuración para comenzar" + }, "description": "Roo Code {{version}} trae poderosas nuevas funcionalidades y mejoras significativas para mejorar tu flujo de trabajo de desarrollo.", "whatsNew": "Novedades", "feature1": "Cola de Mensajes: Pon en cola múltiples mensajes mientras Roo está trabajando, permitiéndote continuar planificando tu flujo de trabajo sin interrupciones.", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index f7acde6eec..e12c9bb5e8 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -265,6 +265,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} est sortie", + "stealthModel": { + "feature": "Modèle stealth GRATUIT pour une durée limitée - Un modèle de raisonnement ultra-rapide qui excelle dans le codage agentique avec une fenêtre de contexte de 262k, disponible via Roo Code Cloud.", + "note": "(Note : les prompts et complétions sont enregistrés par le créateur du modèle et utilisés pour l'améliorer)", + "connectButton": "Se connecter à Roo Code Cloud", + "selectModel": "Sélectionne roo/sonic du fournisseur Roo Code Cloud dans
Paramètres pour commencer" + }, "description": "Roo Code {{version}} apporte de puissantes nouvelles fonctionnalités et des améliorations significatives pour améliorer ton flux de travail de développement.", "whatsNew": "Quoi de neuf", "feature1": "File d'Attente de Messages : Mettez en file d'attente plusieurs messages pendant que Roo travaille, vous permettant de continuer à planifier votre flux de travail sans interruption.", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 3c8984b405..ef1edf7796 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -265,6 +265,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} रिलीज़ हुआ", + "stealthModel": { + "feature": "सीमित समय के लिए मुफ़्त स्टेल्थ मॉडल - एक अत्यंत तेज़ रीज़निंग मॉडल जो 262k कॉन्टेक्स्ट विंडो के साथ एजेंटिक कोडिंग में उत्कृष्ट है, Roo Code Cloud के माध्यम से उपलब्ध।", + "note": "(नोट: प्रॉम्प्ट्स और कम्प्लीशन्स मॉडल निर्माता द्वारा लॉग किए जाते हैं और मॉडल को बेहतर बनाने के लिए उपयोग किए जाते हैं)", + "connectButton": "Roo Code Cloud से कनेक्ट करें", + "selectModel": "
सेटिंग्स में Roo Code Cloud प्रोवाइडर से roo/sonic चुनें और शुरू करें" + }, "description": "Roo Code {{version}} आपके विकास वर्कफ़्लो को बेहतर बनाने के लिए शक्तिशाली नई सुविधाएं और महत्वपूर्ण सुधार लेकर आया है।", "whatsNew": "नया क्या है", "feature1": "संदेश कतार: Roo के काम करते समय कई संदेशों को कतार में रखें, जिससे आप बिना रुकावट के अपने वर्कफ़्लो की योजना बना सकते हैं।", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index bfcf3614f6..9db1acce02 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -277,6 +277,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} Dirilis", + "stealthModel": { + "feature": "Model stealth GRATIS waktu terbatas - Model penalaran super cepat yang unggul dalam coding agentik dengan jendela konteks 262k, tersedia melalui Roo Code Cloud.", + "note": "(Catatan: prompt dan completion dicatat oleh pembuat model dan digunakan untuk meningkatkan model)", + "connectButton": "Hubungkan ke Roo Code Cloud", + "selectModel": "Pilih roo/sonic dari penyedia Roo Code Cloud di
Pengaturan untuk memulai" + }, "description": "Roo Code {{version}} menghadirkan fitur-fitur baru yang kuat dan peningkatan signifikan untuk meningkatkan alur kerja pengembangan Anda.", "whatsNew": "Yang Baru", "feature1": "Antrian Pesan: Antrikan beberapa pesan saat Roo sedang bekerja, memungkinkan Anda melanjutkan perencanaan alur kerja tanpa gangguan.", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 90ee97b7ce..8b2d992915 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -265,6 +265,12 @@ }, "announcement": { "title": "🎉 Rilasciato Roo Code {{version}}", + "stealthModel": { + "feature": "Modello stealth GRATUITO per tempo limitato - Un modello di ragionamento velocissimo che eccelle nella programmazione agentica con una finestra di contesto di 262k, disponibile tramite Roo Code Cloud.", + "note": "(Nota: i prompt e le completazioni sono registrati dal creatore del modello e utilizzati per migliorarlo)", + "connectButton": "Connetti a Roo Code Cloud", + "selectModel": "Seleziona roo/sonic dal provider Roo Code Cloud in
Impostazioni per iniziare" + }, "description": "Roo Code {{version}} porta nuove potenti funzionalità e miglioramenti significativi per potenziare il tuo flusso di lavoro di sviluppo.", "whatsNew": "Novità", "feature1": "Coda Messaggi: Metti in coda più messaggi mentre Roo sta lavorando, permettendoti di continuare a pianificare il tuo flusso di lavoro senza interruzioni.", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 46c228638a..7008c8b8f8 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -265,6 +265,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} リリース", + "stealthModel": { + "feature": "期間限定無料ステルスモデル - 262kコンテキストウィンドウを持つ、エージェンティックコーディングに優れた超高速推論モデル、Roo Code Cloud経由で利用可能。", + "note": "(注意:プロンプトと補完はモデル作成者によってログに記録され、モデルの改善に使用されます)", + "connectButton": "Roo Code Cloudに接続", + "selectModel": "
設定でRoo Code Cloudプロバイダーからroo/sonicを選択して開始" + }, "description": "Roo Code {{version}}は、開発ワークフローを向上させる強力な新機能と重要な改善をもたらします。", "whatsNew": "新機能", "feature1": "メッセージキュー: Rooが作業中に複数のメッセージをキューに入れ、ワークフローの計画を中断することなく続行できます。", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 9b625a7ae7..46f204ed68 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -265,6 +265,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} 출시", + "stealthModel": { + "feature": "기간 한정 무료 스텔스 모델 - 262k 컨텍스트 윈도우를 가진 에이전틱 코딩에 뛰어난 초고속 추론 모델, Roo Code Cloud를 통해 이용 가능.", + "note": "(참고: 프롬프트와 완성은 모델 제작자에 의해 기록되고 모델 개선에 사용됩니다)", + "connectButton": "Roo Code Cloud에 연결", + "selectModel": "
설정에서 Roo Code Cloud 제공업체의 roo/sonic을 선택하여 시작" + }, "description": "Roo Code {{version}}은 개발 워크플로우를 향상시키는 강력한 새 기능과 중요한 개선사항을 제공합니다.", "whatsNew": "새로운 기능", "feature1": "메시지 대기열: Roo가 작업하는 동안 여러 메시지를 대기열에 넣어 워크플로우 계획을 중단 없이 계속할 수 있습니다.", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 5fc5001750..061d12269c 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -250,6 +250,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} uitgebracht", + "stealthModel": { + "feature": "Beperkt tijd GRATIS stealth model - Een bliksemsnelle redeneermodel die uitblinkt in agentische programmering met een 262k contextvenster, beschikbaar via Roo Code Cloud.", + "note": "(Opmerking: prompts en aanvullingen worden gelogd door de modelmaker en gebruikt om het model te verbeteren)", + "connectButton": "Verbinden met Roo Code Cloud", + "selectModel": "Selecteer roo/sonic van de Roo Code Cloud provider in
Instellingen om te beginnen" + }, "description": "Roo Code {{version}} brengt krachtige nieuwe functies en significante verbeteringen om je ontwikkelingsworkflow te verbeteren.", "whatsNew": "Wat is er nieuw", "feature1": "Berichtenwachtrij: Zet meerdere berichten in de wachtrij terwijl Roo werkt, zodat je je workflow kunt blijven plannen zonder onderbreking.", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 8e8500bea1..b997cc09ff 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -265,6 +265,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} wydany", + "stealthModel": { + "feature": "Darmowy model stealth na ograniczony czas - Błyskawiczny model rozumowania, który doskonale radzi sobie z kodowaniem agentowym z oknem kontekstu 262k, dostępny przez Roo Code Cloud.", + "note": "(Uwaga: prompty i uzupełnienia są rejestrowane przez twórcę modelu i używane do jego ulepszania)", + "connectButton": "Połącz z Roo Code Cloud", + "selectModel": "Wybierz roo/sonic od dostawcy Roo Code Cloud w
Ustawieniach aby rozpocząć" + }, "description": "Roo Code {{version}} wprowadza potężne nowe funkcje i znaczące ulepszenia, aby ulepszyć Twój przepływ pracy programistycznej.", "whatsNew": "Co nowego", "feature1": "Kolejka Wiadomości: Umieszczaj wiele wiadomości w kolejce podczas pracy Roo, pozwalając na kontynuowanie planowania przepływu pracy bez przerw.", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 72e3fc4b7d..5e09955cb8 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -265,6 +265,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} Lançado", + "stealthModel": { + "feature": "Modelo stealth GRATUITO por tempo limitado - Um modelo de raciocínio ultrarrápido que se destaca em codificação agêntica com uma janela de contexto de 262k, disponível através do Roo Code Cloud.", + "note": "(Nota: prompts e completações são registrados pelo criador do modelo e usados para melhorá-lo)", + "connectButton": "Conectar ao Roo Code Cloud", + "selectModel": "Selecione roo/sonic do provedor Roo Code Cloud em
Configurações para começar" + }, "description": "Roo Code {{version}} traz novos recursos poderosos e melhorias significativas para aprimorar seu fluxo de trabalho de desenvolvimento.", "whatsNew": "O que há de novo", "feature1": "Fila de Mensagens: Coloque várias mensagens na fila enquanto o Roo está trabalhando, permitindo que você continue planejando seu fluxo de trabalho sem interrupção.", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index a9e242ac34..fb5a1331e2 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -250,6 +250,12 @@ }, "announcement": { "title": "🎉 Выпущен Roo Code {{version}}", + "stealthModel": { + "feature": "Бесплатная скрытая модель на ограниченное время - Сверхбыстрая модель рассуждений, которая превосходно справляется с агентным программированием с окном контекста 262k, доступна через Roo Code Cloud.", + "note": "(Примечание: промпты и дополнения записываются создателем модели и используются для её улучшения)", + "connectButton": "Подключиться к Roo Code Cloud", + "selectModel": "Выберите roo/sonic от провайдера Roo Code Cloud в
Настройках для начала" + }, "description": "Roo Code {{version}} приносит мощные новые функции и значительные улучшения для совершенствования вашего рабочего процесса разработки.", "whatsNew": "Что нового", "feature1": "Очередь сообщений: Ставьте несколько сообщений в очередь, пока Roo работает, позволяя вам продолжать планировать рабочий процесс без прерывания.", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 361902d50a..832c780c40 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -265,6 +265,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} Yayınlandı", + "stealthModel": { + "feature": "Sınırlı süre ÜCRETSİZ gizli model - 262k bağlam penceresi ile ajantik kodlamada mükemmel olan çok hızlı akıl yürütme modeli, Roo Code Cloud üzerinden kullanılabilir.", + "note": "(Not: istemler ve tamamlamalar model yaratıcısı tarafından kaydedilir ve modeli geliştirmek için kullanılır)", + "connectButton": "Roo Code Cloud'a bağlan", + "selectModel": "
Ayarlar'da Roo Code Cloud sağlayıcısından roo/sonic'i seç ve başla" + }, "description": "Roo Code {{version}}, geliştirme iş akışınızı geliştirmek için güçlü yeni özellikler ve önemli iyileştirmeler getiriyor.", "whatsNew": "Yenilikler", "feature1": "Mesaj Kuyruğu: Roo çalışırken birden fazla mesajı kuyruğa alın, iş akışınızı kesintisiz olarak planlamaya devam etmenizi sağlar.", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 11466382a7..1106f2d85c 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -265,6 +265,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} Đã phát hành", + "stealthModel": { + "feature": "Mô hình stealth MIỄN PHÍ có thời hạn - Một mô hình lý luận cực nhanh xuất sắc trong lập trình agentic với cửa sổ ngữ cảnh 262k, có sẵn qua Roo Code Cloud.", + "note": "(Lưu ý: các prompt và completion được ghi lại bởi người tạo mô hình và được sử dụng để cải thiện mô hình)", + "connectButton": "Kết nối với Roo Code Cloud", + "selectModel": "Chọn roo/sonic từ nhà cung cấp Roo Code Cloud trong
Cài đặt để bắt đầu" + }, "description": "Roo Code {{version}} mang đến các tính năng mạnh mẽ mới và cải tiến đáng kể để nâng cao quy trình phát triển của bạn.", "whatsNew": "Có gì mới", "feature1": "Hàng đợi Tin nhắn: Xếp hàng nhiều tin nhắn trong khi Roo đang làm việc, cho phép bạn tiếp tục lập kế hoạch quy trình làm việc mà không bị gián đoạn.", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 83da424df0..bb06f535eb 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -265,6 +265,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} 已发布", + "stealthModel": { + "feature": "限时免费隐形模型 - 一个在代理编程方面表现出色的超快推理模型,拥有 262k 上下文窗口,通过 Roo Code Cloud 提供。", + "note": "(注意:提示词和补全内容会被模型创建者记录并用于改进模型)", + "connectButton": "连接到 Roo Code Cloud", + "selectModel": "在
设置中从 Roo Code Cloud 提供商选择 roo/sonic 开始使用" + }, "description": "Roo Code {{version}} 带来强大的新功能和重大改进,提升您的开发工作流程。", "whatsNew": "新特性", "feature1": "消息队列: 在 Roo 工作时将多个消息排队,让你可以不间断地继续规划工作流程。", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 9cfbef6765..bf5c6fa4df 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -274,6 +274,12 @@ }, "announcement": { "title": "🎉 Roo Code {{version}} 已發布", + "stealthModel": { + "feature": "限時免費隱形模型 - 一個在代理程式編程方面表現出色的超快推理模型,擁有 262k 上下文視窗,透過 Roo Code Cloud 提供。", + "note": "(注意:提示和完成會被模型創建者記錄並用於改進模型)", + "connectButton": "連接到 Roo Code Cloud", + "selectModel": "在
設定中從 Roo Code Cloud 提供商選擇 roo/sonic 開始使用" + }, "description": "Roo Code {{version}} 帶來強大的新功能和重大改進,提升您的開發工作流程。", "whatsNew": "新功能", "feature1": "訊息佇列:在 Roo 工作時將多個訊息排入佇列,讓您可以不間斷地繼續規劃工作流程。", From 1fa647134ffee3ec98f537473bfcb50946d515c9 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 19 Aug 2025 23:04:46 -0700 Subject: [PATCH 31/34] chore: add changeset for v3.25.20 (#7245) --- .changeset/v3.25.20.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/v3.25.20.md diff --git a/.changeset/v3.25.20.md b/.changeset/v3.25.20.md new file mode 100644 index 0000000000..62a9a00805 --- /dev/null +++ b/.changeset/v3.25.20.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +- Add announcement for Sonic model (thanks @mrubens!) From c608392a859ab1bdfe303d8b36236fc28eab732f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:07:50 -0700 Subject: [PATCH 32/34] Changeset version bump (#7246) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matt Rubens --- .changeset/v3.25.20.md | 5 ----- CHANGELOG.md | 4 ++++ src/package.json | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) delete mode 100644 .changeset/v3.25.20.md diff --git a/.changeset/v3.25.20.md b/.changeset/v3.25.20.md deleted file mode 100644 index 62a9a00805..0000000000 --- a/.changeset/v3.25.20.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -- Add announcement for Sonic model (thanks @mrubens!) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08796b2d88..8f4cccac46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Roo Code Changelog +## [3.25.20] - 2025-08-19 + +- Add announcement for Sonic model + ## [3.25.19] - 2025-08-19 - Fix issue where new users couldn't select the Roo Code Cloud provider (thanks @daniel-lxs!) diff --git a/src/package.json b/src/package.json index f744232c1f..da25f4ab56 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "RooVeterinaryInc", - "version": "3.25.19", + "version": "3.25.20", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", From 57ea6257dd01b00ea653dd0b0d9e1b78f40fc835 Mon Sep 17 00:00:00 2001 From: DarinVerheijke <32957890+DarinVerheijke@users.noreply.github.com> Date: Wed, 20 Aug 2025 19:42:57 +0200 Subject: [PATCH 33/34] feat: Featherless provider (#7235) Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> Co-authored-by: cte --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + packages/types/npm/package.metadata.json | 2 +- packages/types/src/global-settings.ts | 1 + packages/types/src/provider-settings.ts | 7 + packages/types/src/providers/featherless.ts | 58 ++++ packages/types/src/providers/index.ts | 1 + pnpm-lock.yaml | 18 +- src/api/index.ts | 3 + .../providers/__tests__/featherless.spec.ts | 286 ++++++++++++++++++ src/api/providers/featherless.ts | 103 +++++++ src/api/providers/index.ts | 1 + src/package.json | 2 +- src/shared/ProfileValidator.ts | 1 + src/shared/__tests__/ProfileValidator.spec.ts | 1 + .../src/components/settings/ApiOptions.tsx | 7 + .../src/components/settings/constants.ts | 3 + .../settings/providers/Featherless.tsx | 50 +++ .../components/settings/providers/index.ts | 1 + .../components/ui/hooks/useSelectedModel.ts | 7 + webview-ui/src/i18n/locales/ca/settings.json | 2 + webview-ui/src/i18n/locales/de/settings.json | 2 + webview-ui/src/i18n/locales/en/settings.json | 2 + webview-ui/src/i18n/locales/es/settings.json | 2 + webview-ui/src/i18n/locales/fr/settings.json | 2 + webview-ui/src/i18n/locales/hi/settings.json | 2 + webview-ui/src/i18n/locales/id/settings.json | 2 + webview-ui/src/i18n/locales/it/settings.json | 2 + webview-ui/src/i18n/locales/ja/settings.json | 2 + webview-ui/src/i18n/locales/ko/settings.json | 2 + webview-ui/src/i18n/locales/nl/settings.json | 2 + webview-ui/src/i18n/locales/pl/settings.json | 2 + .../src/i18n/locales/pt-BR/settings.json | 2 + webview-ui/src/i18n/locales/ru/settings.json | 2 + webview-ui/src/i18n/locales/tr/settings.json | 2 + webview-ui/src/i18n/locales/vi/settings.json | 2 + .../src/i18n/locales/zh-CN/settings.json | 2 + .../src/i18n/locales/zh-TW/settings.json | 2 + webview-ui/src/utils/validate.ts | 5 + 38 files changed, 583 insertions(+), 11 deletions(-) create mode 100644 packages/types/src/providers/featherless.ts create mode 100644 src/api/providers/__tests__/featherless.spec.ts create mode 100644 src/api/providers/featherless.ts create mode 100644 webview-ui/src/components/settings/providers/Featherless.tsx diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 965566a319..c659eb80eb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -25,6 +25,7 @@ body: - AWS Bedrock - Chutes AI - DeepSeek + - Featherless AI - Fireworks AI - Glama - Google Gemini diff --git a/packages/types/npm/package.metadata.json b/packages/types/npm/package.metadata.json index b093d00a8f..f34d00a087 100644 --- a/packages/types/npm/package.metadata.json +++ b/packages/types/npm/package.metadata.json @@ -1,6 +1,6 @@ { "name": "@roo-code/types", - "version": "1.53.0", + "version": "1.55.0", "description": "TypeScript type definitions for Roo Code.", "publishConfig": { "access": "public", diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 5d0854390d..dd72d72fe9 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -196,6 +196,7 @@ export const SECRET_STATE_KEYS = [ "sambaNovaApiKey", "zaiApiKey", "fireworksApiKey", + "featherlessApiKey", "ioIntelligenceApiKey", ] as const satisfies readonly (keyof ProviderSettings)[] export type SecretState = Pick diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index c22683117f..bacd74a1d6 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -46,6 +46,7 @@ export const providerNames = [ "sambanova", "zai", "fireworks", + "featherless", "io-intelligence", "roo", ] as const @@ -284,6 +285,10 @@ const fireworksSchema = apiModelIdProviderModelSchema.extend({ fireworksApiKey: z.string().optional(), }) +const featherlessSchema = apiModelIdProviderModelSchema.extend({ + featherlessApiKey: z.string().optional(), +}) + const ioIntelligenceSchema = apiModelIdProviderModelSchema.extend({ ioIntelligenceModelId: z.string().optional(), ioIntelligenceApiKey: z.string().optional(), @@ -328,6 +333,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv sambaNovaSchema.merge(z.object({ apiProvider: z.literal("sambanova") })), zaiSchema.merge(z.object({ apiProvider: z.literal("zai") })), fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })), + featherlessSchema.merge(z.object({ apiProvider: z.literal("featherless") })), ioIntelligenceSchema.merge(z.object({ apiProvider: z.literal("io-intelligence") })), rooSchema.merge(z.object({ apiProvider: z.literal("roo") })), defaultSchema, @@ -365,6 +371,7 @@ export const providerSettingsSchema = z.object({ ...sambaNovaSchema.shape, ...zaiSchema.shape, ...fireworksSchema.shape, + ...featherlessSchema.shape, ...ioIntelligenceSchema.shape, ...rooSchema.shape, ...codebaseIndexProviderSchema.shape, diff --git a/packages/types/src/providers/featherless.ts b/packages/types/src/providers/featherless.ts new file mode 100644 index 0000000000..d24f1fd882 --- /dev/null +++ b/packages/types/src/providers/featherless.ts @@ -0,0 +1,58 @@ +import type { ModelInfo } from "../model.js" + +export type FeatherlessModelId = + | "deepseek-ai/DeepSeek-V3-0324" + | "deepseek-ai/DeepSeek-R1-0528" + | "moonshotai/Kimi-K2-Instruct" + | "openai/gpt-oss-120b" + | "Qwen/Qwen3-Coder-480B-A35B-Instruct" + +export const featherlessModels = { + "deepseek-ai/DeepSeek-V3-0324": { + maxTokens: 4096, + contextWindow: 32678, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "DeepSeek V3 0324 model.", + }, + "deepseek-ai/DeepSeek-R1-0528": { + maxTokens: 4096, + contextWindow: 32678, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "DeepSeek R1 0528 model.", + }, + "moonshotai/Kimi-K2-Instruct": { + maxTokens: 4096, + contextWindow: 32678, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Kimi K2 Instruct model.", + }, + "openai/gpt-oss-120b": { + maxTokens: 4096, + contextWindow: 32678, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "GPT-OSS 120B model.", + }, + "Qwen/Qwen3-Coder-480B-A35B-Instruct": { + maxTokens: 4096, + contextWindow: 32678, + supportsImages: false, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + description: "Qwen3 Coder 480B A35B Instruct model.", + }, +} as const satisfies Record + +export const featherlessDefaultModelId: FeatherlessModelId = "deepseek-ai/DeepSeek-R1-0528" diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts index 6dff64a979..2f60680bc6 100644 --- a/packages/types/src/providers/index.ts +++ b/packages/types/src/providers/index.ts @@ -26,3 +26,4 @@ export * from "./doubao.js" export * from "./zai.js" export * from "./fireworks.js" export * from "./roo.js" +export * from "./featherless.js" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9fc6bb27f..ff063ecb0d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -584,8 +584,8 @@ importers: specifier: ^1.14.0 version: 1.14.0(typescript@5.8.3) '@roo-code/cloud': - specifier: ^0.18.0 - version: 0.18.0 + specifier: ^0.19.0 + version: 0.19.0 '@roo-code/ipc': specifier: workspace:^ version: link:../packages/ipc @@ -3106,11 +3106,11 @@ packages: cpu: [x64] os: [win32] - '@roo-code/cloud@0.18.0': - resolution: {integrity: sha512-Y2jbcUVB9RCQFAxHDPrfjWQU1o7yRvWaPAdA3eZjsUf+zfDL59Rwfghg6loqDfE/8HCkcJmHfLCKovNX5ju5qA==} + '@roo-code/cloud@0.19.0': + resolution: {integrity: sha512-alZ3X4+TPqRr0xSs9v/UDo3eTlcHaI8ZW8AbWPDtgqf86P8govnyM2hVUMhGXete3AlbYIPRE/9w3/7MrcIjsA==} - '@roo-code/types@1.54.0': - resolution: {integrity: sha512-Xj3Zn2FhXbG2bpwXuhrjKnkeuWypQCIPKljOLXnOCUqaMUhP1zkWwNZ+I3gIBUpDng/iWN3KHon1if0UaoXYQw==} + '@roo-code/types@1.55.0': + resolution: {integrity: sha512-+T5MP8IQcDp7htnGDnk3M4n7S5eYk6jNkw3VBSUBZRhS4EE2GuPDI+CcdmhnDiMb6NMV6yseL+CT4G4QV5ktUw==} '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -12314,9 +12314,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.40.2': optional: true - '@roo-code/cloud@0.18.0': + '@roo-code/cloud@0.19.0': dependencies: - '@roo-code/types': 1.54.0 + '@roo-code/types': 1.55.0 ioredis: 5.6.1 p-wait-for: 5.0.2 socket.io-client: 4.8.1 @@ -12326,7 +12326,7 @@ snapshots: - supports-color - utf-8-validate - '@roo-code/types@1.54.0': + '@roo-code/types@1.55.0': dependencies: zod: 3.25.76 diff --git a/src/api/index.ts b/src/api/index.ts index c80fd5bf72..48a0a89ec5 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -36,6 +36,7 @@ import { ZAiHandler, FireworksHandler, RooHandler, + FeatherlessHandler, } from "./providers" import { NativeOllamaHandler } from "./providers/native-ollama" @@ -143,6 +144,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler { return new IOIntelligenceHandler(options) case "roo": return new RooHandler(options) + case "featherless": + return new FeatherlessHandler(options) default: apiProvider satisfies "gemini-cli" | undefined return new AnthropicHandler(options) diff --git a/src/api/providers/__tests__/featherless.spec.ts b/src/api/providers/__tests__/featherless.spec.ts new file mode 100644 index 0000000000..b0b4c01b86 --- /dev/null +++ b/src/api/providers/__tests__/featherless.spec.ts @@ -0,0 +1,286 @@ +// npx vitest run api/providers/__tests__/featherless.spec.ts + +import { Anthropic } from "@anthropic-ai/sdk" +import OpenAI from "openai" + +import { + type FeatherlessModelId, + featherlessDefaultModelId, + featherlessModels, + DEEP_SEEK_DEFAULT_TEMPERATURE, +} from "@roo-code/types" + +import { FeatherlessHandler } from "../featherless" + +// Create mock functions +const mockCreate = vi.fn() + +// Mock OpenAI module +vi.mock("openai", () => ({ + default: vi.fn(() => ({ + chat: { + completions: { + create: mockCreate, + }, + }, + })), +})) + +describe("FeatherlessHandler", () => { + let handler: FeatherlessHandler + + beforeEach(() => { + vi.clearAllMocks() + // Set up default mock implementation + mockCreate.mockImplementation(async () => ({ + [Symbol.asyncIterator]: async function* () { + yield { + choices: [ + { + delta: { content: "Test response" }, + index: 0, + }, + ], + usage: null, + } + yield { + choices: [ + { + delta: {}, + index: 0, + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 5, + total_tokens: 15, + }, + } + }, + })) + handler = new FeatherlessHandler({ featherlessApiKey: "test-key" }) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it("should use the correct Featherless base URL", () => { + new FeatherlessHandler({ featherlessApiKey: "test-featherless-api-key" }) + expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://api.featherless.ai/v1" })) + }) + + it("should use the provided API key", () => { + const featherlessApiKey = "test-featherless-api-key" + new FeatherlessHandler({ featherlessApiKey }) + expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: featherlessApiKey })) + }) + + it("should handle DeepSeek R1 reasoning format", async () => { + // Override the mock for this specific test + mockCreate.mockImplementationOnce(async () => ({ + [Symbol.asyncIterator]: async function* () { + yield { + choices: [ + { + delta: { content: "Thinking..." }, + index: 0, + }, + ], + usage: null, + } + yield { + choices: [ + { + delta: { content: "Hello" }, + index: 0, + }, + ], + usage: null, + } + yield { + choices: [ + { + delta: {}, + index: 0, + }, + ], + usage: { prompt_tokens: 10, completion_tokens: 5 }, + } + }, + })) + + const systemPrompt = "You are a helpful assistant." + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }] + vi.spyOn(handler, "getModel").mockReturnValue({ + id: "deepseek-ai/DeepSeek-R1-0528", + info: { maxTokens: 1024, temperature: 0.7 }, + } as any) + + const stream = handler.createMessage(systemPrompt, messages) + const chunks = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + expect(chunks).toEqual([ + { type: "reasoning", text: "Thinking..." }, + { type: "text", text: "Hello" }, + { type: "usage", inputTokens: 10, outputTokens: 5 }, + ]) + }) + + it("should fall back to base provider for non-DeepSeek models", async () => { + // Use default mock implementation which returns text content + const systemPrompt = "You are a helpful assistant." + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }] + vi.spyOn(handler, "getModel").mockReturnValue({ + id: "some-other-model", + info: { maxTokens: 1024, temperature: 0.7 }, + } as any) + + const stream = handler.createMessage(systemPrompt, messages) + const chunks = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + expect(chunks).toEqual([ + { type: "text", text: "Test response" }, + { type: "usage", inputTokens: 10, outputTokens: 5 }, + ]) + }) + + it("should return default model when no model is specified", () => { + const model = handler.getModel() + expect(model.id).toBe(featherlessDefaultModelId) + expect(model.info).toEqual(expect.objectContaining(featherlessModels[featherlessDefaultModelId])) + }) + + it("should return specified model when valid model is provided", () => { + const testModelId: FeatherlessModelId = "deepseek-ai/DeepSeek-R1-0528" + const handlerWithModel = new FeatherlessHandler({ + apiModelId: testModelId, + featherlessApiKey: "test-featherless-api-key", + }) + const model = handlerWithModel.getModel() + expect(model.id).toBe(testModelId) + expect(model.info).toEqual(expect.objectContaining(featherlessModels[testModelId])) + }) + + it("completePrompt method should return text from Featherless API", async () => { + const expectedResponse = "This is a test response from Featherless" + mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] }) + const result = await handler.completePrompt("test prompt") + expect(result).toBe(expectedResponse) + }) + + it("should handle errors in completePrompt", async () => { + const errorMessage = "Featherless API error" + mockCreate.mockRejectedValueOnce(new Error(errorMessage)) + await expect(handler.completePrompt("test prompt")).rejects.toThrow( + `Featherless completion error: ${errorMessage}`, + ) + }) + + it("createMessage should yield text content from stream", async () => { + const testContent = "This is test content from Featherless stream" + + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: vi + .fn() + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: testContent } }] }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ type: "text", text: testContent }) + }) + + it("createMessage should yield usage data from stream", async () => { + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: vi + .fn() + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: {} }], usage: { prompt_tokens: 10, completion_tokens: 20 } }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ type: "usage", inputTokens: 10, outputTokens: 20 }) + }) + + it("createMessage should pass correct parameters to Featherless client for DeepSeek R1", async () => { + const modelId: FeatherlessModelId = "deepseek-ai/DeepSeek-R1-0528" + + // Clear previous mocks and set up new implementation + mockCreate.mockClear() + mockCreate.mockImplementationOnce(async () => ({ + [Symbol.asyncIterator]: async function* () { + // Empty stream for this test + }, + })) + + const handlerWithModel = new FeatherlessHandler({ + apiModelId: modelId, + featherlessApiKey: "test-featherless-api-key", + }) + + const systemPrompt = "Test system prompt for Featherless" + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message for Featherless" }] + + const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages) + await messageGenerator.next() + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: modelId, + messages: [ + { + role: "user", + content: `${systemPrompt}\n${messages[0].content}`, + }, + ], + }), + ) + }) + + it("should apply DeepSeek default temperature for R1 models", () => { + const testModelId: FeatherlessModelId = "deepseek-ai/DeepSeek-R1-0528" + const handlerWithModel = new FeatherlessHandler({ + apiModelId: testModelId, + featherlessApiKey: "test-featherless-api-key", + }) + const model = handlerWithModel.getModel() + expect(model.info.temperature).toBe(DEEP_SEEK_DEFAULT_TEMPERATURE) + }) + + it("should use default temperature for non-DeepSeek models", () => { + const testModelId: FeatherlessModelId = "moonshotai/Kimi-K2-Instruct" + const handlerWithModel = new FeatherlessHandler({ + apiModelId: testModelId, + featherlessApiKey: "test-featherless-api-key", + }) + const model = handlerWithModel.getModel() + expect(model.info.temperature).toBe(0.5) + }) +}) diff --git a/src/api/providers/featherless.ts b/src/api/providers/featherless.ts new file mode 100644 index 0000000000..56d7177de7 --- /dev/null +++ b/src/api/providers/featherless.ts @@ -0,0 +1,103 @@ +import { DEEP_SEEK_DEFAULT_TEMPERATURE, type FeatherlessModelId, featherlessDefaultModelId, featherlessModels } from "@roo-code/types" +import { Anthropic } from "@anthropic-ai/sdk" +import OpenAI from "openai" + +import type { ApiHandlerOptions } from "../../shared/api" +import { XmlMatcher } from "../../utils/xml-matcher" +import { convertToR1Format } from "../transform/r1-format" +import { convertToOpenAiMessages } from "../transform/openai-format" +import { ApiStream } from "../transform/stream" + +import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" + +export class FeatherlessHandler extends BaseOpenAiCompatibleProvider { + constructor(options: ApiHandlerOptions) { + super({ + ...options, + providerName: "Featherless", + baseURL: "https://api.featherless.ai/v1", + apiKey: options.featherlessApiKey, + defaultProviderModelId: featherlessDefaultModelId, + providerModels: featherlessModels, + defaultTemperature: 0.5, + }) + } + + private getCompletionParams( + systemPrompt: string, + messages: Anthropic.Messages.MessageParam[], + ): OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming { + const { + id: model, + info: { maxTokens: max_tokens }, + } = this.getModel() + + const temperature = this.options.modelTemperature ?? this.getModel().info.temperature + + return { + model, + max_tokens, + temperature, + messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], + stream: true, + stream_options: { include_usage: true }, + } + } + + override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream { + const model = this.getModel() + + if (model.id.includes("DeepSeek-R1")) { + const stream = await this.client.chat.completions.create({ + ...this.getCompletionParams(systemPrompt, messages), + messages: convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]), + }) + + const matcher = new XmlMatcher( + "think", + (chunk) => + ({ + type: chunk.matched ? "reasoning" : "text", + text: chunk.data, + }) as const, + ) + + for await (const chunk of stream) { + const delta = chunk.choices[0]?.delta + + if (delta?.content) { + for (const processedChunk of matcher.update(delta.content)) { + yield processedChunk + } + } + + if (chunk.usage) { + yield { + type: "usage", + inputTokens: chunk.usage.prompt_tokens || 0, + outputTokens: chunk.usage.completion_tokens || 0, + } + } + } + + // Process any remaining content + for (const processedChunk of matcher.final()) { + yield processedChunk + } + } else { + yield* super.createMessage(systemPrompt, messages) + } + } + + override getModel() { + const model = super.getModel() + const isDeepSeekR1 = model.id.includes("DeepSeek-R1") + return { + ...model, + info: { + ...model.info, + temperature: isDeepSeekR1 ? DEEP_SEEK_DEFAULT_TEMPERATURE : this.defaultTemperature, + }, + } + } +} diff --git a/src/api/providers/index.ts b/src/api/providers/index.ts index 80ef0a2879..d256fbbe55 100644 --- a/src/api/providers/index.ts +++ b/src/api/providers/index.ts @@ -30,3 +30,4 @@ export { XAIHandler } from "./xai" export { ZAiHandler } from "./zai" export { FireworksHandler } from "./fireworks" export { RooHandler } from "./roo" +export { FeatherlessHandler } from "./featherless" diff --git a/src/package.json b/src/package.json index da25f4ab56..c655e19ce6 100644 --- a/src/package.json +++ b/src/package.json @@ -427,7 +427,7 @@ "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.9.0", "@qdrant/js-client-rest": "^1.14.0", - "@roo-code/cloud": "^0.18.0", + "@roo-code/cloud": "^0.19.0", "@roo-code/ipc": "workspace:^", "@roo-code/telemetry": "workspace:^", "@roo-code/types": "workspace:^", diff --git a/src/shared/ProfileValidator.ts b/src/shared/ProfileValidator.ts index afdf1b5232..51eed227d3 100644 --- a/src/shared/ProfileValidator.ts +++ b/src/shared/ProfileValidator.ts @@ -70,6 +70,7 @@ export class ProfileValidator { case "sambanova": case "chutes": case "fireworks": + case "featherless": return profile.apiModelId case "litellm": return profile.litellmModelId diff --git a/src/shared/__tests__/ProfileValidator.spec.ts b/src/shared/__tests__/ProfileValidator.spec.ts index 0129f8fc20..4396e8268a 100644 --- a/src/shared/__tests__/ProfileValidator.spec.ts +++ b/src/shared/__tests__/ProfileValidator.spec.ts @@ -195,6 +195,7 @@ describe("ProfileValidator", () => { "chutes", "sambanova", "fireworks", + "featherless", ] apiModelProviders.forEach((provider) => { diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 6db3dab529..787a95b166 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -31,6 +31,7 @@ import { internationalZAiDefaultModelId, mainlandZAiDefaultModelId, fireworksDefaultModelId, + featherlessDefaultModelId, ioIntelligenceDefaultModelId, rooDefaultModelId, } from "@roo-code/types" @@ -87,6 +88,7 @@ import { XAI, ZAi, Fireworks, + Featherless, } from "./providers" import { MODELS_BY_PROVIDER, PROVIDERS } from "./constants" @@ -327,6 +329,7 @@ const ApiOptions = ({ : internationalZAiDefaultModelId, }, fireworks: { field: "apiModelId", default: fireworksDefaultModelId }, + featherless: { field: "apiModelId", default: featherlessDefaultModelId }, "io-intelligence": { field: "ioIntelligenceModelId", default: ioIntelligenceDefaultModelId }, roo: { field: "apiModelId", default: rooDefaultModelId }, openai: { field: "openAiModelId" }, @@ -600,6 +603,10 @@ const ApiOptions = ({
)} + {selectedProvider === "featherless" && ( + + )} + {selectedProviderModels.length > 0 && ( <>
diff --git a/webview-ui/src/components/settings/constants.ts b/webview-ui/src/components/settings/constants.ts index dc54d367eb..cdeb71814d 100644 --- a/webview-ui/src/components/settings/constants.ts +++ b/webview-ui/src/components/settings/constants.ts @@ -19,6 +19,7 @@ import { internationalZAiModels, fireworksModels, rooModels, + featherlessModels, } from "@roo-code/types" export const MODELS_BY_PROVIDER: Partial>> = { @@ -40,6 +41,7 @@ export const MODELS_BY_PROVIDER: Partial a.label.localeCompare(b.label)) diff --git a/webview-ui/src/components/settings/providers/Featherless.tsx b/webview-ui/src/components/settings/providers/Featherless.tsx new file mode 100644 index 0000000000..264e295dcc --- /dev/null +++ b/webview-ui/src/components/settings/providers/Featherless.tsx @@ -0,0 +1,50 @@ +import { useCallback } from "react" +import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" + +import type { ProviderSettings } from "@roo-code/types" + +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" + +import { inputEventTransform } from "../transforms" + +type FeatherlessProps = { + apiConfiguration: ProviderSettings + setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void +} + +export const Featherless = ({ apiConfiguration, setApiConfigurationField }: FeatherlessProps) => { + const { t } = useAppTranslation() + + const handleInputChange = useCallback( + ( + field: K, + transform: (event: E) => ProviderSettings[K] = inputEventTransform, + ) => + (event: E | Event) => { + setApiConfigurationField(field, transform(event as E)) + }, + [setApiConfigurationField], + ) + + return ( + <> + + + +
+ {t("settings:providers.apiKeyStorageNotice")} +
+ {!apiConfiguration?.featherlessApiKey && ( + + {t("settings:providers.getFeatherlessApiKey")} + + )} + + ) +} diff --git a/webview-ui/src/components/settings/providers/index.ts b/webview-ui/src/components/settings/providers/index.ts index f054780b06..eff33e1298 100644 --- a/webview-ui/src/components/settings/providers/index.ts +++ b/webview-ui/src/components/settings/providers/index.ts @@ -26,3 +26,4 @@ export { XAI } from "./XAI" export { ZAi } from "./ZAi" export { LiteLLM } from "./LiteLLM" export { Fireworks } from "./Fireworks" +export { Featherless } from "./Featherless" diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index a4a36857a0..75a4a968ad 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -46,6 +46,8 @@ import { mainlandZAiModels, fireworksModels, fireworksDefaultModelId, + featherlessModels, + featherlessDefaultModelId, ioIntelligenceDefaultModelId, ioIntelligenceModels, rooDefaultModelId, @@ -292,6 +294,11 @@ function getSelectedModel({ const info = fireworksModels[id as keyof typeof fireworksModels] return { id, info } } + case "featherless": { + const id = apiConfiguration.apiModelId ?? featherlessDefaultModelId + const info = featherlessModels[id as keyof typeof featherlessModels] + return { id, info } + } case "io-intelligence": { const id = apiConfiguration.ioIntelligenceModelId ?? ioIntelligenceDefaultModelId const info = diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 97c5e39352..d9c7ce7ee2 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "Obtenir clau API de Chutes", "fireworksApiKey": "Clau API de Fireworks", "getFireworksApiKey": "Obtenir clau API de Fireworks", + "featherlessApiKey": "Clau API de Featherless", + "getFeatherlessApiKey": "Obtenir clau API de Featherless", "ioIntelligenceApiKey": "Clau API d'IO Intelligence", "ioIntelligenceApiKeyPlaceholder": "Introdueix la teva clau d'API de IO Intelligence", "getIoIntelligenceApiKey": "Obtenir clau API d'IO Intelligence", diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 2d699a0e96..7f09401e57 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -271,6 +271,8 @@ "getChutesApiKey": "Chutes API-Schlüssel erhalten", "fireworksApiKey": "Fireworks API-Schlüssel", "getFireworksApiKey": "Fireworks API-Schlüssel erhalten", + "featherlessApiKey": "Featherless API-Schlüssel", + "getFeatherlessApiKey": "Featherless API-Schlüssel erhalten", "ioIntelligenceApiKey": "IO Intelligence API-Schlüssel", "ioIntelligenceApiKeyPlaceholder": "Gib deinen IO Intelligence API-Schlüssel ein", "getIoIntelligenceApiKey": "IO Intelligence API-Schlüssel erhalten", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 4f70e437c2..d18a3bbd5e 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -268,6 +268,8 @@ "getChutesApiKey": "Get Chutes API Key", "fireworksApiKey": "Fireworks API Key", "getFireworksApiKey": "Get Fireworks API Key", + "featherlessApiKey": "Featherless API Key", + "getFeatherlessApiKey": "Get Featherless API Key", "ioIntelligenceApiKey": "IO Intelligence API Key", "ioIntelligenceApiKeyPlaceholder": "Enter your IO Intelligence API key", "getIoIntelligenceApiKey": "Get IO Intelligence API Key", diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 7f22887ee1..ec9795d8b0 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "Obtener clave API de Chutes", "fireworksApiKey": "Clave API de Fireworks", "getFireworksApiKey": "Obtener clave API de Fireworks", + "featherlessApiKey": "Clave API de Featherless", + "getFeatherlessApiKey": "Obtener clave API de Featherless", "ioIntelligenceApiKey": "Clave API de IO Intelligence", "ioIntelligenceApiKeyPlaceholder": "Introduce tu clave de API de IO Intelligence", "getIoIntelligenceApiKey": "Obtener clave API de IO Intelligence", diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index c544673df6..68230b1a60 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "Obtenir la clé API Chutes", "fireworksApiKey": "Clé API Fireworks", "getFireworksApiKey": "Obtenir la clé API Fireworks", + "featherlessApiKey": "Clé API Featherless", + "getFeatherlessApiKey": "Obtenir la clé API Featherless", "ioIntelligenceApiKey": "Clé API IO Intelligence", "ioIntelligenceApiKeyPlaceholder": "Saisissez votre clé d'API IO Intelligence", "getIoIntelligenceApiKey": "Obtenir la clé API IO Intelligence", diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 536d63d2a5..f98f7e6510 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "Chutes API कुंजी प्राप्त करें", "fireworksApiKey": "Fireworks API कुंजी", "getFireworksApiKey": "Fireworks API कुंजी प्राप्त करें", + "featherlessApiKey": "Featherless API कुंजी", + "getFeatherlessApiKey": "Featherless API कुंजी प्राप्त करें", "ioIntelligenceApiKey": "IO Intelligence API कुंजी", "ioIntelligenceApiKeyPlaceholder": "अपना आईओ इंटेलिजेंस एपीआई कुंजी दर्ज करें", "getIoIntelligenceApiKey": "IO Intelligence API कुंजी प्राप्त करें", diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 672adb9eda..748bf198eb 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -273,6 +273,8 @@ "getChutesApiKey": "Dapatkan Chutes API Key", "fireworksApiKey": "Fireworks API Key", "getFireworksApiKey": "Dapatkan Fireworks API Key", + "featherlessApiKey": "Featherless API Key", + "getFeatherlessApiKey": "Dapatkan Featherless API Key", "ioIntelligenceApiKey": "IO Intelligence API Key", "ioIntelligenceApiKeyPlaceholder": "Masukkan kunci API IO Intelligence Anda", "getIoIntelligenceApiKey": "Dapatkan IO Intelligence API Key", diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index a04258d398..b97d96a61b 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "Ottieni chiave API Chutes", "fireworksApiKey": "Chiave API Fireworks", "getFireworksApiKey": "Ottieni chiave API Fireworks", + "featherlessApiKey": "Chiave API Featherless", + "getFeatherlessApiKey": "Ottieni chiave API Featherless", "ioIntelligenceApiKey": "Chiave API IO Intelligence", "ioIntelligenceApiKeyPlaceholder": "Inserisci la tua chiave API IO Intelligence", "getIoIntelligenceApiKey": "Ottieni chiave API IO Intelligence", diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 6c1859c85d..8061418d38 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "Chutes APIキーを取得", "fireworksApiKey": "Fireworks APIキー", "getFireworksApiKey": "Fireworks APIキーを取得", + "featherlessApiKey": "Featherless APIキー", + "getFeatherlessApiKey": "Featherless APIキーを取得", "ioIntelligenceApiKey": "IO Intelligence APIキー", "ioIntelligenceApiKeyPlaceholder": "IO Intelligence APIキーを入力してください", "getIoIntelligenceApiKey": "IO Intelligence APIキーを取得", diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index e77a806920..08f3a8c4e7 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "Chutes API 키 받기", "fireworksApiKey": "Fireworks API 키", "getFireworksApiKey": "Fireworks API 키 받기", + "featherlessApiKey": "Featherless API 키", + "getFeatherlessApiKey": "Featherless API 키 받기", "ioIntelligenceApiKey": "IO Intelligence API 키", "ioIntelligenceApiKeyPlaceholder": "IO Intelligence API 키를 입력하세요", "getIoIntelligenceApiKey": "IO Intelligence API 키 받기", diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 42c1d97bdb..cc4eb2c90f 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "Chutes API-sleutel ophalen", "fireworksApiKey": "Fireworks API-sleutel", "getFireworksApiKey": "Fireworks API-sleutel ophalen", + "featherlessApiKey": "Featherless API-sleutel", + "getFeatherlessApiKey": "Featherless API-sleutel ophalen", "ioIntelligenceApiKey": "IO Intelligence API-sleutel", "ioIntelligenceApiKeyPlaceholder": "Voer je IO Intelligence API-sleutel in", "getIoIntelligenceApiKey": "IO Intelligence API-sleutel ophalen", diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index f1abf9c79d..107be09fdd 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "Uzyskaj klucz API Chutes", "fireworksApiKey": "Klucz API Fireworks", "getFireworksApiKey": "Uzyskaj klucz API Fireworks", + "featherlessApiKey": "Klucz API Featherless", + "getFeatherlessApiKey": "Uzyskaj klucz API Featherless", "ioIntelligenceApiKey": "Klucz API IO Intelligence", "ioIntelligenceApiKeyPlaceholder": "Wprowadź swój klucz API IO Intelligence", "getIoIntelligenceApiKey": "Uzyskaj klucz API IO Intelligence", diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index c566ee1e2d..54343a2fc5 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "Obter chave de API Chutes", "fireworksApiKey": "Chave de API Fireworks", "getFireworksApiKey": "Obter chave de API Fireworks", + "featherlessApiKey": "Chave de API Featherless", + "getFeatherlessApiKey": "Obter chave de API Featherless", "ioIntelligenceApiKey": "Chave de API IO Intelligence", "ioIntelligenceApiKeyPlaceholder": "Insira sua chave de API da IO Intelligence", "getIoIntelligenceApiKey": "Obter chave de API IO Intelligence", diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 98df1f0138..ac5fafdc17 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "Получить Chutes API-ключ", "fireworksApiKey": "Fireworks API-ключ", "getFireworksApiKey": "Получить Fireworks API-ключ", + "featherlessApiKey": "Featherless API-ключ", + "getFeatherlessApiKey": "Получить Featherless API-ключ", "ioIntelligenceApiKey": "IO Intelligence API-ключ", "ioIntelligenceApiKeyPlaceholder": "Введите свой ключ API IO Intelligence", "getIoIntelligenceApiKey": "Получить IO Intelligence API-ключ", diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 4fb043a8a0..0c4138e783 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "Chutes API Anahtarı Al", "fireworksApiKey": "Fireworks API Anahtarı", "getFireworksApiKey": "Fireworks API Anahtarı Al", + "featherlessApiKey": "Featherless API Anahtarı", + "getFeatherlessApiKey": "Featherless API Anahtarı Al", "ioIntelligenceApiKey": "IO Intelligence API Anahtarı", "ioIntelligenceApiKeyPlaceholder": "IO Intelligence API anahtarınızı girin", "getIoIntelligenceApiKey": "IO Intelligence API Anahtarı Al", diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index c9e6b5afbb..5b2a5a6ef8 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "Lấy khóa API Chutes", "fireworksApiKey": "Khóa API Fireworks", "getFireworksApiKey": "Lấy khóa API Fireworks", + "featherlessApiKey": "Khóa API Featherless", + "getFeatherlessApiKey": "Lấy khóa API Featherless", "ioIntelligenceApiKey": "Khóa API IO Intelligence", "ioIntelligenceApiKeyPlaceholder": "Nhập khóa API IO Intelligence của bạn", "getIoIntelligenceApiKey": "Lấy khóa API IO Intelligence", diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index cc16cf349d..9e56ba74ee 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "获取 Chutes API 密钥", "fireworksApiKey": "Fireworks API 密钥", "getFireworksApiKey": "获取 Fireworks API 密钥", + "featherlessApiKey": "Featherless API 密钥", + "getFeatherlessApiKey": "获取 Featherless API 密钥", "ioIntelligenceApiKey": "IO Intelligence API 密钥", "ioIntelligenceApiKeyPlaceholder": "输入您的 IO Intelligence API 密钥", "getIoIntelligenceApiKey": "获取 IO Intelligence API 密钥", diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 791be9ac02..6bfab1435c 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -269,6 +269,8 @@ "getChutesApiKey": "取得 Chutes API 金鑰", "fireworksApiKey": "Fireworks API 金鑰", "getFireworksApiKey": "取得 Fireworks API 金鑰", + "featherlessApiKey": "Featherless API 金鑰", + "getFeatherlessApiKey": "取得 Featherless API 金鑰", "ioIntelligenceApiKey": "IO Intelligence API 金鑰", "ioIntelligenceApiKeyPlaceholder": "輸入您的 IO Intelligence API 金鑰", "getIoIntelligenceApiKey": "取得 IO Intelligence API 金鑰", diff --git a/webview-ui/src/utils/validate.ts b/webview-ui/src/utils/validate.ts index e4c9ed483d..348a373059 100644 --- a/webview-ui/src/utils/validate.ts +++ b/webview-ui/src/utils/validate.ts @@ -126,6 +126,11 @@ function validateModelsAndKeysProvided(apiConfiguration: ProviderSettings): stri return i18next.t("settings:validation.apiKey") } break + case "featherless": + if (!apiConfiguration.featherlessApiKey) { + return i18next.t("settings:validation.apiKey") + } + break } return undefined From 241df17483a340e2ec6f69a3a3a3835346e93bca Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Wed, 20 Aug 2025 11:26:51 -0700 Subject: [PATCH 34/34] Add MODELS_BY_PROVIDER for use in @roo-code/cloud (#7258) --- packages/types/npm/package.metadata.json | 2 +- packages/types/src/model.ts | 8 ++ packages/types/src/provider-settings.ts | 150 ++++++++++++++++++--- packages/types/src/providers/bedrock.ts | 2 + packages/types/src/providers/index.ts | 8 +- packages/types/src/providers/vscode-llm.ts | 1 + 6 files changed, 151 insertions(+), 20 deletions(-) diff --git a/packages/types/npm/package.metadata.json b/packages/types/npm/package.metadata.json index f34d00a087..70ab13bb4e 100644 --- a/packages/types/npm/package.metadata.json +++ b/packages/types/npm/package.metadata.json @@ -1,6 +1,6 @@ { "name": "@roo-code/types", - "version": "1.55.0", + "version": "1.59.0", "description": "TypeScript type definitions for Roo Code.", "publishConfig": { "access": "public", diff --git a/packages/types/src/model.ts b/packages/types/src/model.ts index 90b61ad879..969a4caa96 100644 --- a/packages/types/src/model.ts +++ b/packages/types/src/model.ts @@ -10,6 +10,14 @@ export const reasoningEffortsSchema = z.enum(reasoningEfforts) export type ReasoningEffort = z.infer +/** + * ReasoningEffortWithMinimal + */ + +export const reasoningEffortWithMinimalSchema = z.union([reasoningEffortsSchema, z.literal("minimal")]) + +export type ReasoningEffortWithMinimal = z.infer + /** * Verbosity */ diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index bacd74a1d6..c13319a956 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -1,15 +1,30 @@ import { z } from "zod" -import { reasoningEffortsSchema, verbosityLevelsSchema, modelInfoSchema } from "./model.js" +import { modelInfoSchema, reasoningEffortWithMinimalSchema, verbosityLevelsSchema } from "./model.js" import { codebaseIndexProviderSchema } from "./codebase-index.js" - -// Bedrock Claude Sonnet 4 model ID that supports 1M context -export const BEDROCK_CLAUDE_SONNET_4_MODEL_ID = "anthropic.claude-sonnet-4-20250514-v1:0" - -// Extended schema that includes "minimal" for GPT-5 models -export const extendedReasoningEffortsSchema = z.union([reasoningEffortsSchema, z.literal("minimal")]) - -export type ReasoningEffortWithMinimal = z.infer +import { + anthropicModels, + bedrockModels, + cerebrasModels, + chutesModels, + claudeCodeModels, + deepSeekModels, + doubaoModels, + featherlessModels, + fireworksModels, + geminiModels, + groqModels, + ioIntelligenceModels, + mistralModels, + moonshotModels, + openAiNativeModels, + rooModels, + sambaNovaModels, + vertexModels, + vscodeLlmModels, + xaiModels, + internationalZAiModels, +} from "./providers/index.js" /** * ProviderName @@ -87,7 +102,7 @@ const baseProviderSettingsSchema = z.object({ // Model reasoning. enableReasoningEffort: z.boolean().optional(), - reasoningEffort: extendedReasoningEffortsSchema.optional(), + reasoningEffort: reasoningEffortWithMinimalSchema.optional(), modelMaxTokens: z.number().optional(), modelMaxThinkingTokens: z.number().optional(), @@ -407,21 +422,126 @@ export const getModelId = (settings: ProviderSettings): string | undefined => { return modelIdKey ? (settings[modelIdKey] as string) : undefined } -// Providers that use Anthropic-style API protocol +// Providers that use Anthropic-style API protocol. export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = ["anthropic", "claude-code", "bedrock"] -// Helper function to determine API protocol for a provider and model export const getApiProtocol = (provider: ProviderName | undefined, modelId?: string): "anthropic" | "openai" => { - // First check if the provider is an Anthropic-style provider if (provider && ANTHROPIC_STYLE_PROVIDERS.includes(provider)) { return "anthropic" } - // For vertex provider, check if the model ID contains "claude" (case-insensitive) if (provider && provider === "vertex" && modelId && modelId.toLowerCase().includes("claude")) { return "anthropic" } - // Default to OpenAI protocol return "openai" } + +export const MODELS_BY_PROVIDER: Record< + Exclude, + { id: ProviderName; label: string; models: string[] } +> = { + anthropic: { + id: "anthropic", + label: "Anthropic", + models: Object.keys(anthropicModels), + }, + bedrock: { + id: "bedrock", + label: "Amazon Bedrock", + models: Object.keys(bedrockModels), + }, + cerebras: { + id: "cerebras", + label: "Cerebras", + models: Object.keys(cerebrasModels), + }, + chutes: { + id: "chutes", + label: "Chutes AI", + models: Object.keys(chutesModels), + }, + "claude-code": { id: "claude-code", label: "Claude Code", models: Object.keys(claudeCodeModels) }, + deepseek: { + id: "deepseek", + label: "DeepSeek", + models: Object.keys(deepSeekModels), + }, + doubao: { id: "doubao", label: "Doubao", models: Object.keys(doubaoModels) }, + featherless: { + id: "featherless", + label: "Featherless", + models: Object.keys(featherlessModels), + }, + fireworks: { + id: "fireworks", + label: "Fireworks", + models: Object.keys(fireworksModels), + }, + gemini: { + id: "gemini", + label: "Google Gemini", + models: Object.keys(geminiModels), + }, + groq: { id: "groq", label: "Groq", models: Object.keys(groqModels) }, + "io-intelligence": { + id: "io-intelligence", + label: "IO Intelligence", + models: Object.keys(ioIntelligenceModels), + }, + mistral: { + id: "mistral", + label: "Mistral", + models: Object.keys(mistralModels), + }, + moonshot: { + id: "moonshot", + label: "Moonshot", + models: Object.keys(moonshotModels), + }, + "openai-native": { + id: "openai-native", + label: "OpenAI", + models: Object.keys(openAiNativeModels), + }, + roo: { id: "roo", label: "Roo", models: Object.keys(rooModels) }, + sambanova: { + id: "sambanova", + label: "SambaNova", + models: Object.keys(sambaNovaModels), + }, + vertex: { + id: "vertex", + label: "GCP Vertex AI", + models: Object.keys(vertexModels), + }, + "vscode-lm": { + id: "vscode-lm", + label: "VS Code LM API", + models: Object.keys(vscodeLlmModels), + }, + xai: { id: "xai", label: "xAI (Grok)", models: Object.keys(xaiModels) }, + zai: { id: "zai", label: "Zai", models: Object.keys(internationalZAiModels) }, + + // Dynamic providers; models pulled from the respective APIs. + glama: { id: "glama", label: "Glama", models: [] }, + huggingface: { id: "huggingface", label: "Hugging Face", models: [] }, + litellm: { id: "litellm", label: "LiteLLM", models: [] }, + openrouter: { id: "openrouter", label: "OpenRouter", models: [] }, + requesty: { id: "requesty", label: "Requesty", models: [] }, + unbound: { id: "unbound", label: "Unbound", models: [] }, +} + +export const dynamicProviders = [ + "glama", + "huggingface", + "litellm", + "openrouter", + "requesty", + "unbound", +] as const satisfies readonly ProviderName[] + +export type DynamicProvider = (typeof dynamicProviders)[number] + +export const isDynamicProvider = (key: string): key is DynamicProvider => + dynamicProviders.includes(key as DynamicProvider) diff --git a/packages/types/src/providers/bedrock.ts b/packages/types/src/providers/bedrock.ts index d7319c132e..67215e7796 100644 --- a/packages/types/src/providers/bedrock.ts +++ b/packages/types/src/providers/bedrock.ts @@ -441,3 +441,5 @@ export const BEDROCK_REGIONS = [ { value: "us-gov-east-1", label: "us-gov-east-1" }, { value: "us-gov-west-1", label: "us-gov-west-1" }, ].sort((a, b) => a.value.localeCompare(b.value)) + +export const BEDROCK_CLAUDE_SONNET_4_MODEL_ID = "anthropic.claude-sonnet-4-20250514-v1:0" diff --git a/packages/types/src/providers/index.ts b/packages/types/src/providers/index.ts index 2f60680bc6..8ca9c2c9b2 100644 --- a/packages/types/src/providers/index.ts +++ b/packages/types/src/providers/index.ts @@ -4,6 +4,9 @@ export * from "./cerebras.js" export * from "./chutes.js" export * from "./claude-code.js" export * from "./deepseek.js" +export * from "./doubao.js" +export * from "./featherless.js" +export * from "./fireworks.js" export * from "./gemini.js" export * from "./glama.js" export * from "./groq.js" @@ -17,13 +20,10 @@ export * from "./ollama.js" export * from "./openai.js" export * from "./openrouter.js" export * from "./requesty.js" +export * from "./roo.js" export * from "./sambanova.js" export * from "./unbound.js" export * from "./vertex.js" export * from "./vscode-llm.js" export * from "./xai.js" -export * from "./doubao.js" export * from "./zai.js" -export * from "./fireworks.js" -export * from "./roo.js" -export * from "./featherless.js" diff --git a/packages/types/src/providers/vscode-llm.ts b/packages/types/src/providers/vscode-llm.ts index 92e871a01a..efe0691913 100644 --- a/packages/types/src/providers/vscode-llm.ts +++ b/packages/types/src/providers/vscode-llm.ts @@ -4,6 +4,7 @@ export type VscodeLlmModelId = keyof typeof vscodeLlmModels export const vscodeLlmDefaultModelId: VscodeLlmModelId = "claude-3.5-sonnet" +// https://docs.cline.bot/provider-config/vscode-language-model-api export const vscodeLlmModels = { "gpt-3.5-turbo": { contextWindow: 12114,