Skip to content

Commit a73fce9

Browse files
authored
Allow processes to access the Roo Code API via a unix socket (#2232)
1 parent 0409509 commit a73fce9

24 files changed

+2684
-395
lines changed

.tool-versions

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodejs v20.18.1

e2e/VSCODE_INTEGRATION_TESTS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ while (Date.now() - startTime < timeout) {
156156
6. **Grading**: When grading tests, use the `Grade:` format to ensure the test is graded correctly (See modes.test.ts for an example).
157157

158158
```typescript
159-
await globalThis.api.startNewTask(
160-
`Given this prompt: ${testPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ${output} \n Be sure to say 'I AM DONE GRADING' after the task is complete`,
161-
)
159+
await globalThis.api.startNewTask({
160+
text: `Given this prompt: ${testPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ${output} \n Be sure to say 'I AM DONE GRADING' after the task is complete`,
161+
})
162162
```

e2e/package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
"version": "0.1.0",
44
"private": true,
55
"scripts": {
6-
"build": "cd .. && npm run compile && npm run build:webview",
7-
"compile": "rm -rf out && tsc -p tsconfig.json",
86
"lint": "eslint src --ext ts",
97
"check-types": "tsc --noEmit",
10-
"test": "npm run compile && npx dotenvx run -f .env.local -- node ./out/runTest.js",
11-
"ci": "npm run build && npm run test",
12-
"clean": "rimraf out"
8+
"test": "npm run build && npx dotenvx run -f .env.local -- node ./out/runTest.js",
9+
"ci": "npm run vscode-test && npm run test",
10+
"build": "rimraf out && tsc -p tsconfig.json",
11+
"vscode-test": "cd .. && npm run vscode-test"
1312
},
1413
"dependencies": {},
1514
"devDependencies": {

e2e/src/suite/extension.test.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@ import * as assert from "assert"
22
import * as vscode from "vscode"
33

44
suite("Roo Code Extension", () => {
5-
test("OPENROUTER_API_KEY environment variable is set", () => {
6-
if (!process.env.OPENROUTER_API_KEY) {
7-
assert.fail("OPENROUTER_API_KEY environment variable is not set")
8-
}
9-
})
10-
115
test("Commands should be registered", async () => {
126
const expectedCommands = [
137
"roo-cline.plusButtonClicked",

e2e/src/suite/index.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import Mocha from "mocha"
33
import { glob } from "glob"
44
import * as vscode from "vscode"
55

6-
import { RooCodeAPI } from "../../../src/exports/roo-code"
6+
import type { RooCodeAPI } from "../../../src/exports/roo-code"
77

8-
import { waitUntilReady } from "./utils"
8+
import { waitFor } from "./utils"
99

1010
declare global {
1111
var api: RooCodeAPI
@@ -18,18 +18,25 @@ export async function run() {
1818
throw new Error("Extension not found")
1919
}
2020

21-
// Activate the extension if it's not already active.
2221
const api = extension.isActive ? extension.exports : await extension.activate()
2322

24-
// TODO: We might want to support a "free" model out of the box so
25-
// contributors can run the tests locally without having to pay.
2623
await api.setConfiguration({
27-
apiProvider: "openrouter",
24+
apiProvider: "openrouter" as const,
2825
openRouterApiKey: process.env.OPENROUTER_API_KEY!,
29-
openRouterModelId: "anthropic/claude-3.5-sonnet",
26+
openRouterModelId: "google/gemini-2.0-flash-001",
27+
openRouterModelInfo: {
28+
maxTokens: 8192,
29+
contextWindow: 1000000,
30+
supportsImages: true,
31+
supportsPromptCache: false,
32+
inputPrice: 0.1,
33+
outputPrice: 0.4,
34+
thinking: false,
35+
},
3036
})
3137

32-
await waitUntilReady({ api })
38+
await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus")
39+
await waitFor(() => api.isReady())
3340

3441
// Expose the API to the tests.
3542
globalThis.api = api

e2e/src/suite/modes.test.ts

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as assert from "assert"
22

3-
import { getCompletion, getMessage, sleep, waitForCompletion, waitUntilAborted } from "./utils"
3+
import type { ClineMessage } from "../../../src/exports/roo-code"
4+
5+
import { waitUntilCompleted } from "./utils"
46

57
suite("Roo Code Modes", () => {
6-
test("Should handle switching modes correctly", async function () {
8+
test("Should handle switching modes correctly", async () => {
79
const api = globalThis.api
810

911
/**
@@ -14,30 +16,44 @@ suite("Roo Code Modes", () => {
1416
"For each mode (Code, Architect, Ask) respond with the mode name and what it specializes in after switching to that mode. " +
1517
"Do not start with the current mode."
1618

17-
await api.setConfiguration({ mode: "Code", alwaysAllowModeSwitch: true, autoApprovalEnabled: true })
18-
const switchModesTaskId = await api.startNewTask(switchModesPrompt)
19-
await waitForCompletion({ api, taskId: switchModesTaskId, timeout: 60_000 })
19+
let messages: ClineMessage[] = []
20+
21+
api.on("message", ({ message }) => {
22+
if (message.type === "say" && message.partial === false) {
23+
messages.push(message)
24+
}
25+
})
26+
27+
const switchModesTaskId = await api.startNewTask({
28+
configuration: { mode: "Code", alwaysAllowModeSwitch: true, autoApprovalEnabled: true },
29+
text: switchModesPrompt,
30+
})
31+
32+
await waitUntilCompleted({ api, taskId: switchModesTaskId, timeout: 60_000 })
2033

2134
/**
2235
* Grade the response.
2336
*/
2437

25-
const gradePrompt =
26-
`Given this prompt: ${switchModesPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)": ` +
27-
api
28-
.getMessages(switchModesTaskId)
29-
.filter(({ type }) => type === "say")
30-
.map(({ text }) => text ?? "")
31-
.join("\n")
38+
const response = messages
39+
.filter(({ type, say, partial }) => say === "text")
40+
.map(({ text }) => text ?? "")
41+
.join("\n")
42+
43+
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}`
44+
45+
messages = []
3246

33-
await api.setConfiguration({ mode: "Ask" })
34-
const gradeTaskId = await api.startNewTask(gradePrompt)
35-
await waitForCompletion({ api, taskId: gradeTaskId, timeout: 60_000 })
47+
const gradeTaskId = await api.startNewTask({ configuration: { mode: "Ask" }, text: gradePrompt })
48+
await waitUntilCompleted({ api, taskId: gradeTaskId })
3649

37-
const completion = getCompletion({ api, taskId: gradeTaskId })
50+
const completion = messages.find(({ type, say, partial }) => say === "completion_result")
3851
const match = completion?.text?.match(/Grade: (\d+)/)
3952
const score = parseInt(match?.[1] ?? "0")
40-
assert.ok(score >= 7 && score <= 10, `Grade must be between 7 and 10 - ${completion?.text}`)
53+
assert.ok(
54+
score >= 7 && score <= 10,
55+
`Grade must be between 7 and 10. DEBUG: score = ${score}, completion = ${completion?.text}`,
56+
)
4157

4258
await api.cancelCurrentTask()
4359
})

e2e/src/suite/subtasks.test.ts

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
import * as assert from "assert"
22

3-
import { sleep, waitFor, getMessage, waitForCompletion } from "./utils"
3+
import type { ClineMessage } from "../../../src/exports/roo-code"
4+
5+
import { sleep, waitFor, waitUntilCompleted } from "./utils"
46

57
suite("Roo Code Subtasks", () => {
6-
test("Should handle subtask cancellation and resumption correctly", async function () {
8+
test("Should handle subtask cancellation and resumption correctly", async () => {
79
const api = globalThis.api
810

11+
const messages: Record<string, ClineMessage[]> = {}
12+
13+
api.on("message", ({ taskId, message }) => {
14+
if (message.type === "say" && message.partial === false) {
15+
messages[taskId] = messages[taskId] || []
16+
messages[taskId].push(message)
17+
}
18+
})
19+
920
await api.setConfiguration({
10-
mode: "Code",
21+
mode: "ask",
1122
alwaysAllowModeSwitch: true,
1223
alwaysAllowSubtasks: true,
1324
autoApprovalEnabled: true,
@@ -17,18 +28,19 @@ suite("Roo Code Subtasks", () => {
1728
const childPrompt = "You are a calculator. Respond only with numbers. What is the square root of 9?"
1829

1930
// Start a parent task that will create a subtask.
20-
const parentTaskId = await api.startNewTask(
21-
"You are the parent task. " +
31+
const parentTaskId = await api.startNewTask({
32+
text:
33+
"You are the parent task. " +
2234
`Create a subtask by using the new_task tool with the message '${childPrompt}'.` +
2335
"After creating the subtask, wait for it to complete and then respond 'Parent task resumed'.",
24-
)
36+
})
2537

2638
let spawnedTaskId: string | undefined = undefined
2739

2840
// Wait for the subtask to be spawned and then cancel it.
2941
api.on("taskSpawned", (_, childTaskId) => (spawnedTaskId = childTaskId))
3042
await waitFor(() => !!spawnedTaskId)
31-
await sleep(2_000) // Give the task a chance to start and populate the history.
43+
await sleep(1_000) // Give the task a chance to start and populate the history.
3244
await api.cancelCurrentTask()
3345

3446
// Wait a bit to ensure any task resumption would have happened.
@@ -37,35 +49,27 @@ suite("Roo Code Subtasks", () => {
3749
// The parent task should not have resumed yet, so we shouldn't see
3850
// "Parent task resumed".
3951
assert.ok(
40-
getMessage({
41-
api,
42-
taskId: parentTaskId,
43-
include: "Parent task resumed",
44-
exclude: "You are the parent task",
45-
}) === undefined,
52+
messages[parentTaskId].find(({ type, text }) => type === "say" && text === "Parent task resumed") ===
53+
undefined,
4654
"Parent task should not have resumed after subtask cancellation",
4755
)
4856

4957
// Start a new task with the same message as the subtask.
50-
const anotherTaskId = await api.startNewTask(childPrompt)
51-
await waitForCompletion({ api, taskId: anotherTaskId })
58+
const anotherTaskId = await api.startNewTask({ text: childPrompt })
59+
await waitUntilCompleted({ api, taskId: anotherTaskId })
5260

5361
// Wait a bit to ensure any task resumption would have happened.
5462
await sleep(2_000)
5563

5664
// The parent task should still not have resumed.
5765
assert.ok(
58-
getMessage({
59-
api,
60-
taskId: parentTaskId,
61-
include: "Parent task resumed",
62-
exclude: "You are the parent task",
63-
}) === undefined,
66+
messages[parentTaskId].find(({ type, text }) => type === "say" && text === "Parent task resumed") ===
67+
undefined,
6468
"Parent task should not have resumed after subtask cancellation",
6569
)
6670

6771
// Clean up - cancel all tasks.
6872
await api.clearCurrentTask()
69-
await waitForCompletion({ api, taskId: parentTaskId })
73+
await waitUntilCompleted({ api, taskId: parentTaskId })
7074
})
7175
})

e2e/src/suite/task.test.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,33 @@
1-
import { waitForMessage } from "./utils"
1+
import * as assert from "assert"
2+
3+
import type { ClineMessage } from "../../../src/exports/roo-code"
4+
5+
import { waitUntilCompleted } from "./utils"
26

37
suite("Roo Code Task", () => {
4-
test("Should handle prompt and response correctly", async function () {
8+
test("Should handle prompt and response correctly", async () => {
59
const api = globalThis.api
6-
await api.setConfiguration({ mode: "Ask", alwaysAllowModeSwitch: true, autoApprovalEnabled: true })
7-
const taskId = await api.startNewTask("Hello world, what is your name? Respond with 'My name is ...'")
8-
await waitForMessage({ api, taskId, include: "My name is Roo" })
10+
11+
const messages: ClineMessage[] = []
12+
13+
api.on("message", ({ message }) => {
14+
if (message.type === "say" && message.partial === false) {
15+
messages.push(message)
16+
}
17+
})
18+
19+
const taskId = await api.startNewTask({
20+
configuration: { mode: "Ask", alwaysAllowModeSwitch: true, autoApprovalEnabled: true },
21+
text: "Hello world, what is your name? Respond with 'My name is ...'",
22+
})
23+
24+
await waitUntilCompleted({ api, taskId })
25+
26+
const completion = messages.find(({ say, partial }) => say === "completion_result")
27+
28+
assert.ok(
29+
completion?.text?.includes("My name is Roo"),
30+
`Completion should include "My name is Roo" - ${completion?.text}`,
31+
)
932
})
1033
})

e2e/src/suite/utils.ts

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as vscode from "vscode"
22

3-
import { RooCodeAPI } from "../../../src/exports/roo-code"
3+
import type { RooCodeAPI } from "../../../src/exports/roo-code"
44

55
type WaitForOptions = {
66
timeout?: number
@@ -9,7 +9,7 @@ type WaitForOptions = {
99

1010
export const waitFor = (
1111
condition: (() => Promise<boolean>) | (() => boolean),
12-
{ timeout = 60_000, interval = 250 }: WaitForOptions = {},
12+
{ timeout = 30_000, interval = 250 }: WaitForOptions = {},
1313
) => {
1414
let timeoutId: NodeJS.Timeout | undefined = undefined
1515

@@ -41,15 +41,6 @@ export const waitFor = (
4141
])
4242
}
4343

44-
type WaitUntilReadyOptions = WaitForOptions & {
45-
api: RooCodeAPI
46-
}
47-
48-
export const waitUntilReady = async ({ api, ...options }: WaitUntilReadyOptions) => {
49-
await vscode.commands.executeCommand("roo-cline.SidebarProvider.focus")
50-
await waitFor(() => api.isReady(), options)
51-
}
52-
5344
type WaitUntilAbortedOptions = WaitForOptions & {
5445
api: RooCodeAPI
5546
taskId: string
@@ -61,39 +52,15 @@ export const waitUntilAborted = async ({ api, taskId, ...options }: WaitUntilAbo
6152
await waitFor(() => set.has(taskId), options)
6253
}
6354

64-
export const waitForCompletion = async ({
65-
api,
66-
taskId,
67-
...options
68-
}: WaitUntilReadyOptions & {
69-
taskId: string
70-
}) => waitFor(() => !!getCompletion({ api, taskId }), options)
71-
72-
export const getCompletion = ({ api, taskId }: { api: RooCodeAPI; taskId: string }) =>
73-
api.getMessages(taskId).find(({ say, partial }) => say === "completion_result" && partial === false)
74-
75-
type WaitForMessageOptions = WaitUntilReadyOptions & {
76-
taskId: string
77-
include: string
78-
exclude?: string
79-
}
80-
81-
export const waitForMessage = async ({ api, taskId, include, exclude, ...options }: WaitForMessageOptions) =>
82-
waitFor(() => !!getMessage({ api, taskId, include, exclude }), options)
83-
84-
type GetMessageOptions = {
55+
type WaitUntilCompletedOptions = WaitForOptions & {
8556
api: RooCodeAPI
8657
taskId: string
87-
include: string
88-
exclude?: string
8958
}
9059

91-
export const getMessage = ({ api, taskId, include, exclude }: GetMessageOptions) =>
92-
api
93-
.getMessages(taskId)
94-
.find(
95-
({ type, text }) =>
96-
type === "say" && text && text.includes(include) && (!exclude || !text.includes(exclude)),
97-
)
60+
export const waitUntilCompleted = async ({ api, taskId, ...options }: WaitUntilCompletedOptions) => {
61+
const set = new Set<string>()
62+
api.on("taskCompleted", (taskId) => set.add(taskId))
63+
await waitFor(() => set.has(taskId), options)
64+
}
9865

9966
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

knip.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"benchmark/**",
1818
"src/activate/**",
1919
"src/exports/**",
20+
"src/schemas/ipc.ts",
2021
"src/extension.ts",
2122
"scripts/**"
2223
],

0 commit comments

Comments
 (0)