diff --git a/e2e/src/suite/extension.test.ts b/e2e/src/suite/extension.test.ts index bc09f816f5..1f44824610 100644 --- a/e2e/src/suite/extension.test.ts +++ b/e2e/src/suite/extension.test.ts @@ -2,12 +2,6 @@ import * as assert from "assert" import * as vscode from "vscode" suite("Roo Code Extension", () => { - test("OPENROUTER_API_KEY environment variable is set", () => { - if (!process.env.OPENROUTER_API_KEY) { - assert.fail("OPENROUTER_API_KEY environment variable is not set") - } - }) - test("Commands should be registered", async () => { const expectedCommands = [ "roo-cline.plusButtonClicked", diff --git a/e2e/src/suite/index.ts b/e2e/src/suite/index.ts index 62dd9f7700..d371a0f4c8 100644 --- a/e2e/src/suite/index.ts +++ b/e2e/src/suite/index.ts @@ -5,7 +5,7 @@ import * as vscode from "vscode" import type { RooCodeAPI } from "../../../src/exports/roo-code" -import { waitUntilReady } from "./utils" +import { waitFor } from "./utils" declare global { var api: RooCodeAPI @@ -18,18 +18,25 @@ export async function run() { throw new Error("Extension not found") } - // Activate the extension if it's not already active. const api = extension.isActive ? extension.exports : await extension.activate() - // TODO: We might want to support a "free" model out of the box so - // contributors can run the tests locally without having to pay. await api.setConfiguration({ - apiProvider: "openrouter", + apiProvider: "openrouter" as const, openRouterApiKey: process.env.OPENROUTER_API_KEY!, - openRouterModelId: "anthropic/claude-3.5-sonnet", + openRouterModelId: "google/gemini-2.0-flash-001", + openRouterModelInfo: { + maxTokens: 8192, + contextWindow: 1000000, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0.1, + outputPrice: 0.4, + thinking: false, + }, }) - await waitUntilReady({ api }) + await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus") + await waitFor(() => api.isReady()) // Expose the API to the tests. globalThis.api = api diff --git a/e2e/src/suite/modes.test.ts b/e2e/src/suite/modes.test.ts index f910ed6f57..c4ae7f1af6 100644 --- a/e2e/src/suite/modes.test.ts +++ b/e2e/src/suite/modes.test.ts @@ -18,7 +18,11 @@ suite("Roo Code Modes", () => { let messages: ClineMessage[] = [] - api.on("message", ({ message }) => messages.push(message)) + api.on("message", ({ message }) => { + if (message.type === "say" && message.partial === false) { + messages.push(message) + } + }) const switchModesTaskId = await api.startNewTask({ configuration: { mode: "Code", alwaysAllowModeSwitch: true, autoApprovalEnabled: true }, @@ -31,22 +35,25 @@ suite("Roo Code Modes", () => { * Grade the response. */ - const gradePrompt = - `Given this prompt: ${switchModesPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ` + - messages - .filter(({ type }) => type === "say") - .map(({ text }) => text ?? "") - .join("\n") + const response = messages + .filter(({ type, say, partial }) => say === "text") + .map(({ text }) => text ?? "") + .join("\n") + + const gradePrompt = `Given this prompt: ${switchModesPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)". For example: Grade 7\n\nResponse: ${response}` messages = [] const gradeTaskId = await api.startNewTask({ configuration: { mode: "Ask" }, text: gradePrompt }) - await waitUntilCompleted({ api, taskId: gradeTaskId, timeout: 60_000 }) + await waitUntilCompleted({ api, taskId: gradeTaskId }) - const completion = messages.find(({ type, say, partial }) => say === "completion_result" && partial === false) + const completion = messages.find(({ type, say, partial }) => say === "completion_result") const match = completion?.text?.match(/Grade: (\d+)/) const score = parseInt(match?.[1] ?? "0") - assert.ok(score >= 7 && score <= 10, `Grade must be between 7 and 10 - ${completion?.text}`) + assert.ok( + score >= 7 && score <= 10, + `Grade must be between 7 and 10. DEBUG: score = ${score}, completion = ${completion?.text}`, + ) await api.cancelCurrentTask() }) diff --git a/e2e/src/suite/subtasks.test.ts b/e2e/src/suite/subtasks.test.ts index 595a218782..513b4c218e 100644 --- a/e2e/src/suite/subtasks.test.ts +++ b/e2e/src/suite/subtasks.test.ts @@ -8,11 +8,17 @@ suite("Roo Code Subtasks", () => { test("Should handle subtask cancellation and resumption correctly", async () => { const api = globalThis.api - const messages: ClineMessage[] = [] - api.on("message", ({ message }) => messages.push(message)) + const messages: Record = {} + + api.on("message", ({ taskId, message }) => { + if (message.type === "say" && message.partial === false) { + messages[taskId] = messages[taskId] || [] + messages[taskId].push(message) + } + }) await api.setConfiguration({ - mode: "Code", + mode: "ask", alwaysAllowModeSwitch: true, alwaysAllowSubtasks: true, autoApprovalEnabled: true, @@ -34,7 +40,7 @@ suite("Roo Code Subtasks", () => { // Wait for the subtask to be spawned and then cancel it. api.on("taskSpawned", (_, childTaskId) => (spawnedTaskId = childTaskId)) await waitFor(() => !!spawnedTaskId) - await sleep(2_000) // Give the task a chance to start and populate the history. + await sleep(1_000) // Give the task a chance to start and populate the history. await api.cancelCurrentTask() // Wait a bit to ensure any task resumption would have happened. @@ -42,15 +48,11 @@ suite("Roo Code Subtasks", () => { // The parent task should not have resumed yet, so we shouldn't see // "Parent task resumed". - // assert.ok( - // getMessage({ - // api, - // taskId: parentTaskId, - // include: "Parent task resumed", - // exclude: "You are the parent task", - // }) === undefined, - // "Parent task should not have resumed after subtask cancellation", - // ) + assert.ok( + messages[parentTaskId].find(({ type, text }) => type === "say" && text === "Parent task resumed") === + undefined, + "Parent task should not have resumed after subtask cancellation", + ) // Start a new task with the same message as the subtask. const anotherTaskId = await api.startNewTask({ text: childPrompt }) @@ -60,15 +62,11 @@ suite("Roo Code Subtasks", () => { await sleep(2_000) // The parent task should still not have resumed. - // assert.ok( - // getMessage({ - // api, - // taskId: parentTaskId, - // include: "Parent task resumed", - // exclude: "You are the parent task", - // }) === undefined, - // "Parent task should not have resumed after subtask cancellation", - // ) + assert.ok( + messages[parentTaskId].find(({ type, text }) => type === "say" && text === "Parent task resumed") === + undefined, + "Parent task should not have resumed after subtask cancellation", + ) // Clean up - cancel all tasks. await api.clearCurrentTask() diff --git a/e2e/src/suite/task.test.ts b/e2e/src/suite/task.test.ts index 25f2bfb435..e83f2734d1 100644 --- a/e2e/src/suite/task.test.ts +++ b/e2e/src/suite/task.test.ts @@ -9,7 +9,12 @@ suite("Roo Code Task", () => { const api = globalThis.api const messages: ClineMessage[] = [] - api.on("message", ({ message }) => messages.push(message)) + + api.on("message", ({ message }) => { + if (message.type === "say" && message.partial === false) { + messages.push(message) + } + }) const taskId = await api.startNewTask({ configuration: { mode: "Ask", alwaysAllowModeSwitch: true, autoApprovalEnabled: true }, @@ -18,7 +23,7 @@ suite("Roo Code Task", () => { await waitUntilCompleted({ api, taskId }) - const completion = messages.find(({ type, say, partial }) => say === "completion_result" && partial === false) + const completion = messages.find(({ say, partial }) => say === "completion_result") assert.ok( completion?.text?.includes("My name is Roo"), diff --git a/e2e/src/suite/utils.ts b/e2e/src/suite/utils.ts index 2e42f5ea20..3437c74e55 100644 --- a/e2e/src/suite/utils.ts +++ b/e2e/src/suite/utils.ts @@ -9,7 +9,7 @@ type WaitForOptions = { export const waitFor = ( condition: (() => Promise) | (() => boolean), - { timeout = 60_000, interval = 250 }: WaitForOptions = {}, + { timeout = 30_000, interval = 250 }: WaitForOptions = {}, ) => { let timeoutId: NodeJS.Timeout | undefined = undefined @@ -41,15 +41,6 @@ export const waitFor = ( ]) } -type WaitUntilReadyOptions = WaitForOptions & { - api: RooCodeAPI -} - -export const waitUntilReady = async ({ api, ...options }: WaitUntilReadyOptions) => { - await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus") - await waitFor(() => api.isReady(), options) -} - type WaitUntilAbortedOptions = WaitForOptions & { api: RooCodeAPI taskId: string diff --git a/knip.json b/knip.json index bf14bb5078..5e9695a9ab 100644 --- a/knip.json +++ b/knip.json @@ -17,7 +17,7 @@ "benchmark/**", "src/activate/**", "src/exports/**", - "src/schemas/**", + "src/schemas/ipc.ts", "src/extension.ts", "scripts/**" ], diff --git a/package-lock.json b/package-lock.json index 15c9ea20ab..0c228e8d3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8302,44 +8302,75 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/bare-events": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", - "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "license": "Apache-2.0", "optional": true }, "node_modules/bare-fs": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", - "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.0.2.tgz", + "integrity": "sha512-S5mmkMesiduMqnz51Bfh0Et9EX0aTCJxhsI4bvzFFLs8Z1AV8RDHadfY5CyLwdoLHgXbNBEN1gQcbEtGwuvixw==", + "license": "Apache-2.0", "optional": true, "dependencies": { - "bare-events": "^2.0.0", - "bare-path": "^2.0.0", - "bare-stream": "^2.0.0" + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } } }, "node_modules/bare-os": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", - "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", - "optional": true + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } }, "node_modules/bare-path": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", - "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", "optional": true, "dependencies": { - "bare-os": "^2.1.0" + "bare-os": "^3.0.1" } }, "node_modules/bare-stream": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.4.2.tgz", - "integrity": "sha512-XZ4ln/KV4KT+PXdIWTKjsLY+quqCaEtqqtgGJVPw9AoM73By03ij64YjepK0aQvHSWDb6AfAZwqKaFu68qkrdA==", + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "license": "Apache-2.0", "optional": true, "dependencies": { - "streamx": "^2.20.0" + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } } }, "node_modules/base64-js": { @@ -16664,84 +16695,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", - "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/jackspeak": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", - "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", - "dev": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/lru-cache": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", - "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", - "dev": true, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { "version": "4.37.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.37.0.tgz", @@ -17885,16 +17838,17 @@ } }, "node_modules/tar-fs": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", - "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz", + "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==", + "license": "MIT", "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { - "bare-fs": "^2.1.1", - "bare-path": "^2.1.0" + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" } }, "node_modules/tar-stream": { diff --git a/src/api/providers/__tests__/bedrock-custom-arn.test.ts b/src/api/providers/__tests__/bedrock-custom-arn.test.ts index f7dc2870fa..b5522180f9 100644 --- a/src/api/providers/__tests__/bedrock-custom-arn.test.ts +++ b/src/api/providers/__tests__/bedrock-custom-arn.test.ts @@ -3,10 +3,20 @@ import { ApiHandlerOptions } from "../../../shared/api" // Mock the AWS SDK jest.mock("@aws-sdk/client-bedrock-runtime", () => { + const mockResponse = { + output: { + message: { + content: [ + { + text: "Test response", + }, + ], + }, + }, + } + const mockSend = jest.fn().mockImplementation(() => { - return Promise.resolve({ - output: new TextEncoder().encode(JSON.stringify({ content: "Test response" })), - }) + return Promise.resolve(mockResponse) }) return { diff --git a/src/api/providers/__tests__/bedrock.test.ts b/src/api/providers/__tests__/bedrock.test.ts index 74249ac820..facba103fd 100644 --- a/src/api/providers/__tests__/bedrock.test.ts +++ b/src/api/providers/__tests__/bedrock.test.ts @@ -399,14 +399,20 @@ describe("AwsBedrockHandler", () => { }) }) + //response.output.message.content[0].text + describe("completePrompt", () => { it("should complete prompt successfully", async () => { const mockResponse = { - output: new TextEncoder().encode( - JSON.stringify({ - content: "Test response", - }), - ), + output: { + message: { + content: [ + { + text: "Test response", + }, + ], + }, + }, } const mockSend = jest.fn().mockResolvedValue(mockResponse) @@ -450,7 +456,9 @@ describe("AwsBedrockHandler", () => { it("should handle invalid response format", async () => { const mockResponse = { - output: new TextEncoder().encode("invalid json"), + output: { + message: {}, + }, } const mockSend = jest.fn().mockResolvedValue(mockResponse) @@ -464,9 +472,16 @@ describe("AwsBedrockHandler", () => { it("should handle empty response", async () => { const mockResponse = { - output: new TextEncoder().encode(JSON.stringify({})), + output: { + message: { + content: [ + { + text: "", + }, + ], + }, + }, } - const mockSend = jest.fn().mockResolvedValue(mockResponse) handler["client"] = { send: mockSend, @@ -486,11 +501,15 @@ describe("AwsBedrockHandler", () => { }) const mockResponse = { - output: new TextEncoder().encode( - JSON.stringify({ - content: "Test response", - }), - ), + output: { + message: { + content: [ + { + text: "Test response", + }, + ], + }, + }, } const mockSend = jest.fn().mockResolvedValue(mockResponse) @@ -519,11 +538,15 @@ describe("AwsBedrockHandler", () => { }) const mockResponse = { - output: new TextEncoder().encode( - JSON.stringify({ - content: "Test response", - }), - ), + output: { + message: { + content: [ + { + text: "Test response", + }, + ], + }, + }, } const mockSend = jest.fn().mockResolvedValue(mockResponse) @@ -552,13 +575,16 @@ describe("AwsBedrockHandler", () => { }) const mockResponse = { - output: new TextEncoder().encode( - JSON.stringify({ - content: "Test response", - }), - ), + output: { + message: { + content: [ + { + text: "Test response", + }, + ], + }, + }, } - const mockSend = jest.fn().mockResolvedValue(mockResponse) handler["client"] = { send: mockSend, @@ -585,11 +611,15 @@ describe("AwsBedrockHandler", () => { }) const mockResponse = { - output: new TextEncoder().encode( - JSON.stringify({ - content: "Test response", - }), - ), + output: { + message: { + content: [ + { + text: "Test response", + }, + ], + }, + }, } const mockSend = jest.fn().mockResolvedValue(mockResponse) diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts index 374b81d8a9..1b14bb7d7b 100644 --- a/src/api/providers/bedrock.ts +++ b/src/api/providers/bedrock.ts @@ -665,13 +665,14 @@ Please check: const command = new ConverseCommand(payload) const response = await this.client.send(command) - if (response.output && response.output instanceof Uint8Array) { + if ( + response?.output?.message?.content && + response.output.message.content.length > 0 && + response.output.message.content[0].text && + response.output.message.content[0].text.trim().length > 0 + ) { try { - const outputStr = new TextDecoder().decode(response.output) - const output = JSON.parse(outputStr) - if (output.content) { - return output.content - } + return response.output.message.content[0].text } catch (parseError) { logger.error("Failed to parse Bedrock response", { ctx: "bedrock", diff --git a/src/exports/api.ts b/src/exports/api.ts index f09e0f2d81..6ddac43056 100644 --- a/src/exports/api.ts +++ b/src/exports/api.ts @@ -17,20 +17,31 @@ export class API extends EventEmitter implements RooCodeAPI { private readonly context: vscode.ExtensionContext private readonly ipc?: IpcServer private readonly taskMap = new Map() + private readonly log: (...args: unknown[]) => void - constructor(outputChannel: vscode.OutputChannel, provider: ClineProvider, socketPath?: string) { + constructor( + outputChannel: vscode.OutputChannel, + provider: ClineProvider, + socketPath?: string, + enableLogging = false, + ) { super() this.outputChannel = outputChannel this.sidebarProvider = provider this.context = provider.context + this.log = enableLogging + ? (...args: unknown[]) => { + outputChannelLog(this.outputChannel, ...args) + console.log(args) + } + : () => {} + this.registerListeners(this.sidebarProvider) if (socketPath) { - const ipc = (this.ipc = new IpcServer(socketPath, (...args: unknown[]) => - outputChannelLog(this.outputChannel, ...args), - )) + const ipc = (this.ipc = new IpcServer(socketPath, this.log)) ipc.listen() this.log(`[API] ipc server started: socketPath=${socketPath}, pid=${process.pid}, ppid=${process.ppid}`) @@ -156,11 +167,6 @@ export class API extends EventEmitter implements RooCodeAPI { return this.sidebarProvider.viewLaunched } - public log(message: string) { - this.outputChannel.appendLine(message) - console.log(`${message}\n`) - } - private registerListeners(provider: ClineProvider) { provider.on("clineCreated", (cline) => { cline.on("taskStarted", () => { diff --git a/src/exports/interface.ts b/src/exports/interface.ts index 9b2c619409..6e0eb8adec 100644 --- a/src/exports/interface.ts +++ b/src/exports/interface.ts @@ -76,10 +76,4 @@ export interface RooCodeAPI extends EventEmitter { * Returns true if the API is ready to use. */ isReady(): boolean - - /** - * Logs a message to the output channel. - * @param message The message to log. - */ - log(message: string): void } diff --git a/src/exports/ipc.ts b/src/exports/ipc.ts index 2808dd4613..33a5b1ff66 100644 --- a/src/exports/ipc.ts +++ b/src/exports/ipc.ts @@ -99,7 +99,7 @@ export class IpcServer extends EventEmitter { this.emit(IpcMessageType.TaskCommand, payload.clientId, payload.data) break default: - throw new Error(`[server#onMessage] unhandled payload: ${JSON.stringify(payload)}`) + this.log(`[server#onMessage] unhandled payload: ${JSON.stringify(payload)}`) break } } diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 2db5b90768..350d0ae5a3 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -575,11 +575,6 @@ interface RooCodeAPI extends EventEmitter { * Returns true if the API is ready to use. */ isReady(): boolean - /** - * Logs a message to the output channel. - * @param message The message to log. - */ - log(message: string): void } export { diff --git a/src/extension.ts b/src/extension.ts index a6de6b5ed8..aa834c560e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -119,7 +119,9 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.executeCommand("roo-cline.activationCompleted") // Implements the `RooCodeAPI` interface. - return new API(outputChannel, provider, process.env.ROO_CODE_IPC_SOCKET_PATH) + const socketPath = process.env.ROO_CODE_IPC_SOCKET_PATH + const enableLogging = typeof socketPath === "string" + return new API(outputChannel, provider, socketPath, enableLogging) } // This method is called when your extension is deactivated diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index ebc781e8eb..6f82da00d7 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -32,6 +32,7 @@ interface ChatTextAreaProps { inputValue: string setInputValue: (value: string) => void textAreaDisabled: boolean + selectApiConfigDisabled: boolean placeholderText: string selectedImages: string[] setSelectedImages: React.Dispatch> @@ -50,6 +51,7 @@ const ChatTextArea = forwardRef( inputValue, setInputValue, textAreaDisabled, + selectApiConfigDisabled, placeholderText, selectedImages, setSelectedImages, @@ -975,7 +977,7 @@ const ChatTextArea = forwardRef(
{ setInputValue: jest.fn(), onSend: jest.fn(), textAreaDisabled: false, + selectApiConfigDisabled: false, onSelectImages: jest.fn(), shouldDisableImages: false, placeholderText: "Type a message...", @@ -408,4 +409,21 @@ describe("ChatTextArea", () => { expect(setInputValue).not.toHaveBeenCalled() }) }) + + describe("selectApiConfig", () => { + // Helper function to get the API config dropdown + const getApiConfigDropdown = () => { + return screen.getByTitle("chat:selectApiConfig") + } + it("should be enabled independently of textAreaDisabled", () => { + render() + const apiConfigDropdown = getApiConfigDropdown() + expect(apiConfigDropdown).not.toHaveAttribute("disabled") + }) + it("should be disabled when selectApiConfigDisabled is true", () => { + render() + const apiConfigDropdown = getApiConfigDropdown() + expect(apiConfigDropdown).toHaveAttribute("disabled") + }) + }) }) diff --git a/webview-ui/src/components/history/HistoryView.tsx b/webview-ui/src/components/history/HistoryView.tsx index 085de35670..2e2424c7fa 100644 --- a/webview-ui/src/components/history/HistoryView.tsx +++ b/webview-ui/src/components/history/HistoryView.tsx @@ -85,7 +85,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { : `${t("history:enterSelectionMode")}` }> {isSelectionMode ? t("history:exitSelection") : t("history:selectionMode")} diff --git a/webview-ui/src/components/settings/constants.ts b/webview-ui/src/components/settings/constants.ts index 4634f334df..186a380340 100644 --- a/webview-ui/src/components/settings/constants.ts +++ b/webview-ui/src/components/settings/constants.ts @@ -39,22 +39,30 @@ export const PROVIDERS = [ { value: "human-relay", label: "Human Relay" }, ].sort((a, b) => a.label.localeCompare(b.label)) +//This list alpha sorted and updated April 2, 2025 to include any region that supported 1 or +//more models shown at https://docs.aws.amazon.com/bedrock/latest/userguide/models-regions.html + export const AWS_REGIONS = [ - { value: "us-east-1", label: "us-east-1" }, - { value: "us-east-2", label: "us-east-2" }, - { value: "us-west-2", label: "us-west-2" }, - { value: "ap-south-1", label: "ap-south-1" }, { value: "ap-northeast-1", label: "ap-northeast-1" }, { value: "ap-northeast-2", label: "ap-northeast-2" }, + { value: "ap-south-1", label: "ap-south-1" }, { value: "ap-southeast-1", label: "ap-southeast-1" }, { value: "ap-southeast-2", label: "ap-southeast-2" }, { value: "ca-central-1", label: "ca-central-1" }, { value: "eu-central-1", label: "eu-central-1" }, + { value: "eu-central-2", label: "eu-central-2" }, + { value: "eu-north-1", label: "eu-north-1" }, + { value: "eu-south-1", label: "eu-south-1" }, + { value: "eu-south-2", label: "eu-south-2" }, { value: "eu-west-1", label: "eu-west-1" }, { value: "eu-west-2", label: "eu-west-2" }, { value: "eu-west-3", label: "eu-west-3" }, { value: "sa-east-1", label: "sa-east-1" }, + { value: "us-east-1", label: "us-east-1" }, + { value: "us-east-2", label: "us-east-2" }, + { value: "us-gov-east-1", label: "us-gov-east-1" }, { value: "us-gov-west-1", label: "us-gov-west-1" }, + { value: "us-west-2", label: "us-west-2" }, ] export const VERTEX_REGIONS = [ diff --git a/webview-ui/src/components/ui/select-dropdown.tsx b/webview-ui/src/components/ui/select-dropdown.tsx index 680a01f811..bd11ea33f7 100644 --- a/webview-ui/src/components/ui/select-dropdown.tsx +++ b/webview-ui/src/components/ui/select-dropdown.tsx @@ -83,7 +83,7 @@ export const SelectDropdown = React.forwardRef + setOpen(false)} onInteractOutside={() => setOpen(false)} container={portalContainer} - className={contentClassName}> + className={cn("overflow-y-auto max-h-[80vh]", contentClassName)}> {options.map((option, index) => { if (option.type === DropdownOptionType.SEPARATOR) { return