Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/cli/src/agent/json-event-emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 15 additions & 10 deletions apps/cli/src/commands/cli/stdin-stream.ts
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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
Expand Down Expand Up @@ -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 }
Expand Down
72 changes: 21 additions & 51 deletions apps/cli/src/types/json-events.ts
Original file line number Diff line number Diff line change
@@ -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
*
Expand All @@ -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)
Expand All @@ -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.
Expand All @@ -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) */
Expand Down Expand Up @@ -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 */
Expand Down
80 changes: 80 additions & 0 deletions packages/types/src/__tests__/cli.test.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})
})
Loading
Loading