diff --git a/packages/types/src/events.ts b/packages/types/src/events.ts index d4a05f8e3e6..54267d67e4e 100644 --- a/packages/types/src/events.ts +++ b/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 @@ -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), diff --git a/packages/types/src/ipc.ts b/packages/types/src/ipc.ts index 9f6d2de04db..90a1478a4db 100644 --- a/packages/types/src/ipc.ts +++ b/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 diff --git a/src/extension/api.ts b/src/extension/api.ts index be78a09cb92..a2b389abdc6 100644 --- a/src/extension/api.ts +++ b/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 implements RooCodeAPI { private readonly outputChannel: vscode.OutputChannel @@ -64,7 +67,15 @@ export class API extends EventEmitter 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 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 } })