Chris Estreich 1 ماه پیش
والد
کامیت
b55409c0b0

+ 1 - 1
apps/cli/src/agent/json-event-emitter.ts

@@ -156,7 +156,7 @@ export class JsonEventEmitter {
 	emitControl(event: {
 		subtype: "ack" | "done" | "error"
 		requestId?: string
-		command?: string
+		command?: JsonEvent["command"]
 		taskId?: string
 		content?: string
 		success?: boolean

+ 15 - 10
apps/cli/src/commands/cli/stdin-stream.ts

@@ -1,7 +1,12 @@
 import { createInterface } from "readline"
 import { randomUUID } from "crypto"
 
-import type { RooCodeSettings } from "@roo-code/types"
+import {
+	rooCliCommandNames,
+	type RooCliCommandName,
+	type RooCliInputCommand,
+	type RooCliStartCommand,
+} from "@roo-code/types"
 
 import { isRecord } from "@/lib/utils/guards.js"
 
@@ -12,20 +17,15 @@ import type { JsonEventEmitter } from "@/agent/json-event-emitter.js"
 // Types
 // ---------------------------------------------------------------------------
 
-export type StdinStreamCommandName = "start" | "message" | "cancel" | "ping" | "shutdown"
+export type StdinStreamCommandName = RooCliCommandName
 
-export type StdinStreamCommand =
-	| { command: "start"; requestId: string; prompt: string; configuration?: RooCodeSettings }
-	| { command: "message"; requestId: string; prompt: string }
-	| { command: "cancel"; requestId: string }
-	| { command: "ping"; requestId: string }
-	| { command: "shutdown"; requestId: string }
+export type StdinStreamCommand = RooCliInputCommand
 
 // ---------------------------------------------------------------------------
 // Parsing
 // ---------------------------------------------------------------------------
 
-export const VALID_STDIN_COMMANDS = new Set<StdinStreamCommandName>(["start", "message", "cancel", "ping", "shutdown"])
+export const VALID_STDIN_COMMANDS = new Set<StdinStreamCommandName>(rooCliCommandNames)
 
 export function parseStdinStreamCommand(line: string, lineNumber: number): StdinStreamCommand {
 	let parsed: unknown
@@ -67,7 +67,12 @@ export function parseStdinStreamCommand(line: string, lineNumber: number): Stdin
 		}
 
 		if (command === "start" && isRecord(parsed.configuration)) {
-			return { command, requestId, prompt: promptRaw, configuration: parsed.configuration as RooCodeSettings }
+			return {
+				command,
+				requestId,
+				prompt: promptRaw,
+				configuration: parsed.configuration as RooCliStartCommand["configuration"],
+			}
 		}
 
 		return { command, requestId, prompt: promptRaw }

+ 21 - 51
apps/cli/src/types/json-events.ts

@@ -1,3 +1,15 @@
+import {
+	rooCliOutputFormats,
+	type RooCliCost,
+	type RooCliEventType,
+	type RooCliFinalOutput,
+	type RooCliOutputFormat,
+	type RooCliQueueItem,
+	type RooCliStreamEvent,
+	type RooCliToolResult,
+	type RooCliToolUse,
+} from "@roo-code/types"
+
 /**
  * JSON Event Types for Structured CLI Output
  *
@@ -14,9 +26,9 @@
 /**
  * Output format options for the CLI.
  */
-export const OUTPUT_FORMATS = ["text", "json", "stream-json"] as const
+export const OUTPUT_FORMATS = rooCliOutputFormats
 
-export type OutputFormat = (typeof OUTPUT_FORMATS)[number]
+export type OutputFormat = RooCliOutputFormat
 
 export function isValidOutputFormat(format: string): format is OutputFormat {
 	return (OUTPUT_FORMATS as readonly string[]).includes(format)
@@ -25,66 +37,24 @@ export function isValidOutputFormat(format: string): format is OutputFormat {
 /**
  * Event type discriminators for JSON output.
  */
-export type JsonEventType =
-	| "system" // System messages (init, ready, shutdown)
-	| "control" // Transport/control protocol events
-	| "queue" // Message queue telemetry from extension state
-	| "assistant" // Assistant text messages
-	| "user" // User messages (echoed input)
-	| "tool_use" // Tool invocations (file ops, commands, browser, MCP)
-	| "tool_result" // Results from tool execution
-	| "thinking" // Reasoning/thinking content
-	| "error" // Errors
-	| "result" // Final task result
+export type JsonEventType = RooCliEventType
 
-export interface JsonEventQueueItem {
-	/** Queue item id generated by MessageQueueService */
-	id: string
-	/** Queued text prompt preview */
-	text?: string
-	/** Number of attached images in the queued message */
-	imageCount?: number
-	/** Queue insertion/update timestamp (ms epoch) */
-	timestamp?: number
-}
+export type JsonEventQueueItem = RooCliQueueItem
 
 /**
  * Tool use information for tool_use events.
  */
-export interface JsonEventToolUse {
-	/** Tool name (e.g., "read_file", "write_to_file", "execute_command") */
-	name: string
-	/** Tool input parameters */
-	input?: Record<string, unknown>
-}
+export type JsonEventToolUse = RooCliToolUse
 
 /**
  * Tool result information for tool_result events.
  */
-export interface JsonEventToolResult {
-	/** Tool name that produced this result */
-	name: string
-	/** Tool output (for successful execution) */
-	output?: string
-	/** Error message (for failed execution) */
-	error?: string
-}
+export type JsonEventToolResult = RooCliToolResult
 
 /**
  * Cost and token usage information.
  */
-export interface JsonEventCost {
-	/** Total cost in USD */
-	totalCost?: number
-	/** Input tokens used */
-	inputTokens?: number
-	/** Output tokens generated */
-	outputTokens?: number
-	/** Cache write tokens */
-	cacheWrites?: number
-	/** Cache read tokens */
-	cacheReads?: number
-}
+export type JsonEventCost = RooCliCost
 
 /**
  * Base JSON event structure.
@@ -94,7 +64,7 @@ export interface JsonEventCost {
  * - Each delta includes `id` for easy correlation
  * - Final message has `done: true`
  */
-export interface JsonEvent {
+export type JsonEvent = RooCliStreamEvent & {
 	/** Event type discriminator */
 	type: JsonEventType
 	/** Protocol schema version (included on system.init) */
@@ -137,7 +107,7 @@ export interface JsonEvent {
  * Final JSON output for "json" mode (single object at end).
  * Contains the result and accumulated messages.
  */
-export interface JsonFinalOutput {
+export type JsonFinalOutput = RooCliFinalOutput & {
 	/** Final result type */
 	type: "result"
 	/** Whether the task succeeded */

+ 80 - 0
packages/types/src/__tests__/cli.test.ts

@@ -0,0 +1,80 @@
+import {
+	rooCliControlEventSchema,
+	rooCliFinalOutputSchema,
+	rooCliInputCommandSchema,
+	rooCliStreamEventSchema,
+} from "../cli.js"
+
+describe("CLI types", () => {
+	describe("rooCliInputCommandSchema", () => {
+		it("validates a start command", () => {
+			const result = rooCliInputCommandSchema.safeParse({
+				command: "start",
+				requestId: "req-1",
+				prompt: "hello",
+				configuration: {},
+			})
+
+			expect(result.success).toBe(true)
+		})
+
+		it("rejects a message command without prompt", () => {
+			const result = rooCliInputCommandSchema.safeParse({
+				command: "message",
+				requestId: "req-2",
+			})
+
+			expect(result.success).toBe(false)
+		})
+	})
+
+	describe("rooCliControlEventSchema", () => {
+		it("validates a control done event", () => {
+			const result = rooCliControlEventSchema.safeParse({
+				type: "control",
+				subtype: "done",
+				requestId: "req-3",
+				command: "start",
+				success: true,
+				code: "task_completed",
+			})
+
+			expect(result.success).toBe(true)
+		})
+
+		it("rejects control event without requestId", () => {
+			const result = rooCliControlEventSchema.safeParse({
+				type: "control",
+				subtype: "ack",
+			})
+
+			expect(result.success).toBe(false)
+		})
+	})
+
+	describe("rooCliStreamEventSchema", () => {
+		it("accepts passthrough fields for forward compatibility", () => {
+			const result = rooCliStreamEventSchema.safeParse({
+				type: "assistant",
+				id: 42,
+				content: "partial",
+				customField: "future",
+			})
+
+			expect(result.success).toBe(true)
+		})
+	})
+
+	describe("rooCliFinalOutputSchema", () => {
+		it("validates final json output shape", () => {
+			const result = rooCliFinalOutputSchema.safeParse({
+				type: "result",
+				success: true,
+				content: "done",
+				events: [],
+			})
+
+			expect(result.success).toBe(true)
+		})
+	})
+})

+ 173 - 0
packages/types/src/cli.ts

@@ -0,0 +1,173 @@
+import { z } from "zod"
+
+import { rooCodeSettingsSchema } from "./global-settings.js"
+
+/**
+ * Roo CLI stdin commands
+ */
+
+export const rooCliCommandNames = ["start", "message", "cancel", "ping", "shutdown"] as const
+
+export const rooCliCommandNameSchema = z.enum(rooCliCommandNames)
+
+export type RooCliCommandName = z.infer<typeof rooCliCommandNameSchema>
+
+export const rooCliCommandBaseSchema = z.object({
+	command: rooCliCommandNameSchema,
+	requestId: z.string().min(1),
+})
+
+export type RooCliCommandBase = z.infer<typeof rooCliCommandBaseSchema>
+
+export const rooCliStartCommandSchema = rooCliCommandBaseSchema.extend({
+	command: z.literal("start"),
+	prompt: z.string(),
+	configuration: rooCodeSettingsSchema.optional(),
+})
+
+export type RooCliStartCommand = z.infer<typeof rooCliStartCommandSchema>
+
+export const rooCliMessageCommandSchema = rooCliCommandBaseSchema.extend({
+	command: z.literal("message"),
+	prompt: z.string(),
+})
+
+export type RooCliMessageCommand = z.infer<typeof rooCliMessageCommandSchema>
+
+export const rooCliCancelCommandSchema = rooCliCommandBaseSchema.extend({
+	command: z.literal("cancel"),
+})
+
+export type RooCliCancelCommand = z.infer<typeof rooCliCancelCommandSchema>
+
+export const rooCliPingCommandSchema = rooCliCommandBaseSchema.extend({
+	command: z.literal("ping"),
+})
+
+export type RooCliPingCommand = z.infer<typeof rooCliPingCommandSchema>
+
+export const rooCliShutdownCommandSchema = rooCliCommandBaseSchema.extend({
+	command: z.literal("shutdown"),
+})
+
+export type RooCliShutdownCommand = z.infer<typeof rooCliShutdownCommandSchema>
+
+export const rooCliInputCommandSchema = z.discriminatedUnion("command", [
+	rooCliStartCommandSchema,
+	rooCliMessageCommandSchema,
+	rooCliCancelCommandSchema,
+	rooCliPingCommandSchema,
+	rooCliShutdownCommandSchema,
+])
+
+export type RooCliInputCommand = z.infer<typeof rooCliInputCommandSchema>
+
+/**
+ * Roo CLI stream-json output
+ */
+
+export const rooCliOutputFormats = ["text", "json", "stream-json"] as const
+
+export const rooCliOutputFormatSchema = z.enum(rooCliOutputFormats)
+
+export type RooCliOutputFormat = z.infer<typeof rooCliOutputFormatSchema>
+
+export const rooCliEventTypes = [
+	"system",
+	"control",
+	"queue",
+	"assistant",
+	"user",
+	"tool_use",
+	"tool_result",
+	"thinking",
+	"error",
+	"result",
+] as const
+
+export const rooCliEventTypeSchema = z.enum(rooCliEventTypes)
+
+export type RooCliEventType = z.infer<typeof rooCliEventTypeSchema>
+
+export const rooCliControlSubtypes = ["ack", "done", "error"] as const
+
+export const rooCliControlSubtypeSchema = z.enum(rooCliControlSubtypes)
+
+export type RooCliControlSubtype = z.infer<typeof rooCliControlSubtypeSchema>
+
+export const rooCliQueueItemSchema = z.object({
+	id: z.string().min(1),
+	text: z.string().optional(),
+	imageCount: z.number().optional(),
+	timestamp: z.number().optional(),
+})
+
+export type RooCliQueueItem = z.infer<typeof rooCliQueueItemSchema>
+
+export const rooCliToolUseSchema = z.object({
+	name: z.string(),
+	input: z.record(z.unknown()).optional(),
+})
+
+export type RooCliToolUse = z.infer<typeof rooCliToolUseSchema>
+
+export const rooCliToolResultSchema = z.object({
+	name: z.string(),
+	output: z.string().optional(),
+	error: z.string().optional(),
+})
+
+export type RooCliToolResult = z.infer<typeof rooCliToolResultSchema>
+
+export const rooCliCostSchema = z.object({
+	totalCost: z.number().optional(),
+	inputTokens: z.number().optional(),
+	outputTokens: z.number().optional(),
+	cacheWrites: z.number().optional(),
+	cacheReads: z.number().optional(),
+})
+
+export type RooCliCost = z.infer<typeof rooCliCostSchema>
+
+export const rooCliStreamEventSchema = z
+	.object({
+		type: rooCliEventTypeSchema.optional(),
+		subtype: z.string().optional(),
+		requestId: z.string().optional(),
+		command: rooCliCommandNameSchema.optional(),
+		taskId: z.string().optional(),
+		code: z.string().optional(),
+		content: z.string().optional(),
+		success: z.boolean().optional(),
+		id: z.number().optional(),
+		done: z.boolean().optional(),
+		queueDepth: z.number().optional(),
+		queue: z.array(rooCliQueueItemSchema).optional(),
+		schemaVersion: z.number().optional(),
+		protocol: z.string().optional(),
+		capabilities: z.array(z.string()).optional(),
+		tool_use: rooCliToolUseSchema.optional(),
+		tool_result: rooCliToolResultSchema.optional(),
+		cost: rooCliCostSchema.optional(),
+	})
+	.passthrough()
+
+export type RooCliStreamEvent = z.infer<typeof rooCliStreamEventSchema>
+
+export const rooCliControlEventSchema = rooCliStreamEventSchema.extend({
+	type: z.literal("control"),
+	subtype: rooCliControlSubtypeSchema,
+	requestId: z.string().min(1),
+})
+
+export type RooCliControlEvent = z.infer<typeof rooCliControlEventSchema>
+
+export const rooCliFinalOutputSchema = z.object({
+	type: z.literal("result"),
+	success: z.boolean(),
+	content: z.string().optional(),
+	cost: rooCliCostSchema.optional(),
+	events: z.array(rooCliStreamEventSchema),
+})
+
+export type RooCliFinalOutput = z.infer<typeof rooCliFinalOutputSchema>

+ 1 - 0
packages/types/src/index.ts

@@ -1,4 +1,5 @@
 export * from "./api.js"
+export * from "./cli.js"
 export * from "./cloud.js"
 export * from "./codebase-index.js"
 export * from "./context-management.js"