Просмотр исходного кода

feat: add IPC query handlers for commands, modes, and models (#11279)

Add GetCommands, GetModes, and GetModels to the IPC protocol so external
clients can fetch slash commands, available modes, and Roo provider models
without going through the internal webview message channel.

Co-authored-by: Claude Opus 4.6 <[email protected]>
Chris Estreich 6 дней назад
Родитель
Сommit
9b39d2242a
3 измененных файлов с 106 добавлено и 3 удалено
  1. 37 0
      packages/types/src/events.ts
  2. 12 0
      packages/types/src/ipc.ts
  3. 57 3
      src/extension/api.ts

+ 37 - 0
packages/types/src/events.ts

@@ -1,6 +1,7 @@
 import { z } from "zod"
 
 import { clineMessageSchema, queuedMessageSchema, tokenUsageSchema } from "./message.js"
+import { modelInfoSchema } from "./model.js"
 import { toolNamesSchema, toolUsageSchema } from "./tool.js"
 
 /**
@@ -45,6 +46,11 @@ export enum RooCodeEventName {
 	ModeChanged = "modeChanged",
 	ProviderProfileChanged = "providerProfileChanged",
 
+	// Query Responses
+	CommandsResponse = "commandsResponse",
+	ModesResponse = "modesResponse",
+	ModelsResponse = "modelsResponse",
+
 	// Evals
 	EvalPass = "evalPass",
 	EvalFail = "evalFail",
@@ -108,6 +114,20 @@ export const rooCodeEventsSchema = z.object({
 
 	[RooCodeEventName.ModeChanged]: z.tuple([z.string()]),
 	[RooCodeEventName.ProviderProfileChanged]: z.tuple([z.object({ name: z.string(), provider: z.string() })]),
+
+	[RooCodeEventName.CommandsResponse]: z.tuple([
+		z.array(
+			z.object({
+				name: z.string(),
+				source: z.enum(["global", "project", "built-in"]),
+				filePath: z.string().optional(),
+				description: z.string().optional(),
+				argumentHint: z.string().optional(),
+			}),
+		),
+	]),
+	[RooCodeEventName.ModesResponse]: z.tuple([z.array(z.object({ slug: z.string(), name: z.string() }))]),
+	[RooCodeEventName.ModelsResponse]: z.tuple([z.record(z.string(), modelInfoSchema)]),
 })
 
 export type RooCodeEvents = z.infer<typeof rooCodeEventsSchema>
@@ -237,6 +257,23 @@ export const taskEventSchema = z.discriminatedUnion("eventName", [
 		taskId: z.number().optional(),
 	}),
 
+	// Query Responses
+	z.object({
+		eventName: z.literal(RooCodeEventName.CommandsResponse),
+		payload: rooCodeEventsSchema.shape[RooCodeEventName.CommandsResponse],
+		taskId: z.number().optional(),
+	}),
+	z.object({
+		eventName: z.literal(RooCodeEventName.ModesResponse),
+		payload: rooCodeEventsSchema.shape[RooCodeEventName.ModesResponse],
+		taskId: z.number().optional(),
+	}),
+	z.object({
+		eventName: z.literal(RooCodeEventName.ModelsResponse),
+		payload: rooCodeEventsSchema.shape[RooCodeEventName.ModelsResponse],
+		taskId: z.number().optional(),
+	}),
+
 	// Evals
 	z.object({
 		eventName: z.literal(RooCodeEventName.EvalPass),

+ 12 - 0
packages/types/src/ipc.ts

@@ -46,6 +46,9 @@ export enum TaskCommandName {
 	CloseTask = "CloseTask",
 	ResumeTask = "ResumeTask",
 	SendMessage = "SendMessage",
+	GetCommands = "GetCommands",
+	GetModes = "GetModes",
+	GetModels = "GetModels",
 }
 
 /**
@@ -79,6 +82,15 @@ export const taskCommandSchema = z.discriminatedUnion("commandName", [
 			images: z.array(z.string()).optional(),
 		}),
 	}),
+	z.object({
+		commandName: z.literal(TaskCommandName.GetCommands),
+	}),
+	z.object({
+		commandName: z.literal(TaskCommandName.GetModes),
+	}),
+	z.object({
+		commandName: z.literal(TaskCommandName.GetModels),
+	}),
 ])
 
 export type TaskCommand = z.infer<typeof taskCommandSchema>

+ 57 - 3
src/extension/api.ts

@@ -20,10 +20,13 @@ import {
 	IpcMessageType,
 } from "@roo-code/types"
 import { IpcServer } from "@roo-code/ipc"
+import { CloudService } from "@roo-code/cloud"
 
 import { Package } from "../shared/package"
 import { ClineProvider } from "../core/webview/ClineProvider"
 import { openClineInNewTab } from "../activate/registerCommands"
+import { getCommands } from "../services/command/commands"
+import { getModels } from "../api/providers/fetchers/modelCache"
 
 export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
 	private readonly outputChannel: vscode.OutputChannel
@@ -64,7 +67,15 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
 			ipc.listen()
 			this.log(`[API] ipc server started: socketPath=${socketPath}, pid=${process.pid}, ppid=${process.ppid}`)
 
-			ipc.on(IpcMessageType.TaskCommand, async (_clientId, command) => {
+			ipc.on(IpcMessageType.TaskCommand, async (clientId, command) => {
+				const sendResponse = (eventName: RooCodeEventName, payload: unknown[]) => {
+					ipc.send(clientId, {
+						type: IpcMessageType.TaskEvent,
+						origin: IpcOrigin.Server,
+						data: { eventName, payload } as TaskEvent,
+					})
+				}
+
 				switch (command.commandName) {
 					case TaskCommandName.StartNewTask:
 						this.log(
@@ -88,13 +99,56 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
 						} catch (error) {
 							const errorMessage = error instanceof Error ? error.message : String(error)
 							this.log(`[API] ResumeTask failed for taskId ${command.data}: ${errorMessage}`)
-							// Don't rethrow - we want to prevent IPC server crashes
-							// The error is logged for debugging purposes
+							// Don't rethrow - we want to prevent IPC server crashes.
+							// The error is logged for debugging purposes.
 						}
 						break
 					case TaskCommandName.SendMessage:
 						this.log(`[API] SendMessage -> ${command.data.text}`)
 						await this.sendMessage(command.data.text, command.data.images)
+						break
+					case TaskCommandName.GetCommands:
+						try {
+							const commands = await getCommands(this.sidebarProvider.cwd)
+
+							sendResponse(RooCodeEventName.CommandsResponse, [
+								commands.map((cmd) => ({
+									name: cmd.name,
+									source: cmd.source,
+									filePath: cmd.filePath,
+									description: cmd.description,
+									argumentHint: cmd.argumentHint,
+								})),
+							])
+						} catch (error) {
+							sendResponse(RooCodeEventName.CommandsResponse, [[]])
+						}
+
+						break
+					case TaskCommandName.GetModes:
+						try {
+							const modes = await this.sidebarProvider.getModes()
+							sendResponse(RooCodeEventName.ModesResponse, [modes])
+						} catch (error) {
+							sendResponse(RooCodeEventName.ModesResponse, [[]])
+						}
+
+						break
+					case TaskCommandName.GetModels:
+						try {
+							const models = await getModels({
+								provider: "roo" as const,
+								baseUrl: process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy",
+								apiKey: CloudService.hasInstance()
+									? CloudService.instance.authService?.getSessionToken()
+									: undefined,
+							})
+
+							sendResponse(RooCodeEventName.ModelsResponse, [models])
+						} catch (error) {
+							sendResponse(RooCodeEventName.ModelsResponse, [{}])
+						}
+
 						break
 				}
 			})