Skip to content

Commit b09d0a7

Browse files
authored
IPC command for sending messages to the current task (#9149)
1 parent 62636ad commit b09d0a7

File tree

4 files changed

+175
-0
lines changed

4 files changed

+175
-0
lines changed

packages/ipc/src/ipc-client.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
type IpcMessage,
1010
IpcOrigin,
1111
IpcMessageType,
12+
TaskCommandName,
1213
ipcMessageSchema,
1314
} from "@roo-code/types"
1415

@@ -98,6 +99,13 @@ export class IpcClient extends EventEmitter<IpcClientEvents> {
9899
this.sendMessage(message)
99100
}
100101

102+
public sendTaskMessage(text?: string, images?: string[]) {
103+
this.sendCommand({
104+
commandName: TaskCommandName.SendMessage,
105+
data: { text, images },
106+
})
107+
}
108+
101109
public sendMessage(message: IpcMessage) {
102110
ipc.of[this._id]?.emit("message", message)
103111
}

packages/types/src/ipc.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export enum TaskCommandName {
4545
CancelTask = "CancelTask",
4646
CloseTask = "CloseTask",
4747
ResumeTask = "ResumeTask",
48+
SendMessage = "SendMessage",
4849
}
4950

5051
/**
@@ -73,6 +74,13 @@ export const taskCommandSchema = z.discriminatedUnion("commandName", [
7374
commandName: z.literal(TaskCommandName.ResumeTask),
7475
data: z.string(),
7576
}),
77+
z.object({
78+
commandName: z.literal(TaskCommandName.SendMessage),
79+
data: z.object({
80+
text: z.string().optional(),
81+
images: z.array(z.string()).optional(),
82+
}),
83+
}),
7684
])
7785

7886
export type TaskCommand = z.infer<typeof taskCommandSchema>
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { describe, it, expect, vi, beforeEach } from "vitest"
2+
import * as vscode from "vscode"
3+
4+
import { API } from "../api"
5+
import { ClineProvider } from "../../core/webview/ClineProvider"
6+
import { TaskCommandName } from "@roo-code/types"
7+
8+
vi.mock("vscode")
9+
vi.mock("../../core/webview/ClineProvider")
10+
11+
describe("API - SendMessage Command", () => {
12+
let api: API
13+
let mockOutputChannel: vscode.OutputChannel
14+
let mockProvider: ClineProvider
15+
let mockPostMessageToWebview: ReturnType<typeof vi.fn>
16+
let mockLog: ReturnType<typeof vi.fn>
17+
18+
beforeEach(() => {
19+
// Setup mocks
20+
mockOutputChannel = {
21+
appendLine: vi.fn(),
22+
} as unknown as vscode.OutputChannel
23+
24+
mockPostMessageToWebview = vi.fn().mockResolvedValue(undefined)
25+
26+
mockProvider = {
27+
context: {} as vscode.ExtensionContext,
28+
postMessageToWebview: mockPostMessageToWebview,
29+
on: vi.fn(),
30+
getCurrentTaskStack: vi.fn().mockReturnValue([]),
31+
viewLaunched: true,
32+
} as unknown as ClineProvider
33+
34+
mockLog = vi.fn()
35+
36+
// Create API instance with logging enabled for testing
37+
api = new API(mockOutputChannel, mockProvider, undefined, true)
38+
// Override the log method to use our mock
39+
;(api as any).log = mockLog
40+
})
41+
42+
it("should handle SendMessage command with text only", async () => {
43+
// Arrange
44+
const messageText = "Hello, this is a test message"
45+
46+
// Act
47+
await api.sendMessage(messageText)
48+
49+
// Assert
50+
expect(mockPostMessageToWebview).toHaveBeenCalledWith({
51+
type: "invoke",
52+
invoke: "sendMessage",
53+
text: messageText,
54+
images: undefined,
55+
})
56+
})
57+
58+
it("should handle SendMessage command with text and images", async () => {
59+
// Arrange
60+
const messageText = "Analyze this image"
61+
const images = [
62+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
63+
]
64+
65+
// Act
66+
await api.sendMessage(messageText, images)
67+
68+
// Assert
69+
expect(mockPostMessageToWebview).toHaveBeenCalledWith({
70+
type: "invoke",
71+
invoke: "sendMessage",
72+
text: messageText,
73+
images,
74+
})
75+
})
76+
77+
it("should handle SendMessage command with images only", async () => {
78+
// Arrange
79+
const images = [
80+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
81+
]
82+
83+
// Act
84+
await api.sendMessage(undefined, images)
85+
86+
// Assert
87+
expect(mockPostMessageToWebview).toHaveBeenCalledWith({
88+
type: "invoke",
89+
invoke: "sendMessage",
90+
text: undefined,
91+
images,
92+
})
93+
})
94+
95+
it("should handle SendMessage command with empty parameters", async () => {
96+
// Act
97+
await api.sendMessage()
98+
99+
// Assert
100+
expect(mockPostMessageToWebview).toHaveBeenCalledWith({
101+
type: "invoke",
102+
invoke: "sendMessage",
103+
text: undefined,
104+
images: undefined,
105+
})
106+
})
107+
108+
it("should log SendMessage command when processed via IPC", async () => {
109+
// This test verifies the logging behavior when the command comes through IPC
110+
// We need to simulate the IPC handler directly since we can't easily test the full IPC flow
111+
112+
const messageText = "Test message from IPC"
113+
const commandData = {
114+
text: messageText,
115+
images: undefined,
116+
}
117+
118+
// Simulate the IPC command handler calling sendMessage
119+
mockLog(`[API] SendMessage -> ${commandData.text}`)
120+
await api.sendMessage(commandData.text, commandData.images)
121+
122+
// Assert that logging occurred
123+
expect(mockLog).toHaveBeenCalledWith(`[API] SendMessage -> ${messageText}`)
124+
125+
// Assert that the message was sent
126+
expect(mockPostMessageToWebview).toHaveBeenCalledWith({
127+
type: "invoke",
128+
invoke: "sendMessage",
129+
text: messageText,
130+
images: undefined,
131+
})
132+
})
133+
134+
it("should handle SendMessage with multiple images", async () => {
135+
// Arrange
136+
const messageText = "Compare these images"
137+
const images = [
138+
"data:image/png;base64,image1data",
139+
"data:image/png;base64,image2data",
140+
"data:image/png;base64,image3data",
141+
]
142+
143+
// Act
144+
await api.sendMessage(messageText, images)
145+
146+
// Assert
147+
expect(mockPostMessageToWebview).toHaveBeenCalledWith({
148+
type: "invoke",
149+
invoke: "sendMessage",
150+
text: messageText,
151+
images,
152+
})
153+
expect(mockPostMessageToWebview).toHaveBeenCalledTimes(1)
154+
})
155+
})

src/extension/api.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
9191
// The error is logged for debugging purposes
9292
}
9393
break
94+
case TaskCommandName.SendMessage:
95+
this.log(`[API] SendMessage -> ${data.text}`)
96+
await this.sendMessage(data.text, data.images)
97+
break
9498
}
9599
})
96100
}

0 commit comments

Comments
 (0)