mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
151 lines
3.2 KiB
TypeScript
151 lines
3.2 KiB
TypeScript
import path from "path"
|
|
import { fileURLToPath } from "url"
|
|
import readline from "readline"
|
|
|
|
import { execa } from "execa"
|
|
|
|
export type StreamEvent = {
|
|
type?: string
|
|
subtype?: string
|
|
requestId?: string
|
|
command?: string
|
|
content?: string
|
|
code?: string
|
|
success?: boolean
|
|
done?: boolean
|
|
id?: number
|
|
queueDepth?: number
|
|
queue?: Array<{ id?: string; text?: string; imageCount?: number; timestamp?: number }>
|
|
tool_use?: {
|
|
name?: string
|
|
input?: Record<string, unknown>
|
|
}
|
|
tool_result?: {
|
|
name?: string
|
|
output?: string
|
|
}
|
|
}
|
|
|
|
export type StreamCommand = {
|
|
command: "start" | "message" | "cancel" | "ping" | "shutdown"
|
|
requestId: string
|
|
prompt?: string
|
|
}
|
|
|
|
export interface StreamCaseContext {
|
|
readonly cliRoot: string
|
|
readonly timeoutMs: number
|
|
nextRequestId(prefix: string): string
|
|
sendCommand(command: StreamCommand): void
|
|
}
|
|
|
|
export interface RunStreamCaseOptions {
|
|
timeoutMs?: number
|
|
onEvent: (event: StreamEvent, context: StreamCaseContext) => void
|
|
onTimeoutMessage?: (context: StreamCaseContext) => string
|
|
}
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
const defaultCliRoot = path.resolve(__dirname, "../../..")
|
|
|
|
function parseEvent(line: string): StreamEvent | null {
|
|
const trimmed = line.trim()
|
|
|
|
if (!trimmed.startsWith("{")) {
|
|
return null
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(trimmed) as StreamEvent
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export async function runStreamCase(options: RunStreamCaseOptions): Promise<void> {
|
|
const cliRoot = process.env.ROO_CLI_ROOT ? path.resolve(process.env.ROO_CLI_ROOT) : defaultCliRoot
|
|
const timeoutMs = options.timeoutMs ?? 120_000
|
|
|
|
const child = execa(
|
|
"pnpm",
|
|
["dev", "--print", "--stdin-prompt-stream", "--provider", "roo", "--output-format", "stream-json"],
|
|
{
|
|
cwd: cliRoot,
|
|
stdin: "pipe",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
reject: false,
|
|
forceKillAfterDelay: 2_000,
|
|
},
|
|
)
|
|
|
|
child.stderr?.on("data", (chunk) => {
|
|
process.stderr.write(chunk)
|
|
})
|
|
|
|
let requestCounter = 0
|
|
|
|
const context: StreamCaseContext = {
|
|
cliRoot,
|
|
timeoutMs,
|
|
nextRequestId(prefix: string): string {
|
|
requestCounter += 1
|
|
return `${prefix}-${Date.now()}-${requestCounter}`
|
|
},
|
|
sendCommand(command: StreamCommand): void {
|
|
if (child.stdin?.destroyed) {
|
|
return
|
|
}
|
|
|
|
child.stdin.write(`${JSON.stringify(command)}\n`)
|
|
},
|
|
}
|
|
|
|
let handlerError: Error | null = null
|
|
let timedOut = false
|
|
|
|
const timeout = setTimeout(() => {
|
|
timedOut = true
|
|
const message = options.onTimeoutMessage?.(context) ?? "timed out waiting for stream scenario completion"
|
|
handlerError = new Error(message)
|
|
child.kill("SIGTERM")
|
|
}, timeoutMs)
|
|
|
|
const rl = readline.createInterface({
|
|
input: child.stdout!,
|
|
crlfDelay: Infinity,
|
|
})
|
|
|
|
rl.on("line", (line) => {
|
|
process.stdout.write(`${line}\n`)
|
|
|
|
const event = parseEvent(line)
|
|
|
|
if (!event) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
options.onEvent(event, context)
|
|
} catch (error) {
|
|
handlerError = error instanceof Error ? error : new Error(String(error))
|
|
child.kill("SIGTERM")
|
|
}
|
|
})
|
|
|
|
const result = await child
|
|
clearTimeout(timeout)
|
|
rl.close()
|
|
|
|
if (handlerError) {
|
|
throw handlerError
|
|
}
|
|
|
|
if (timedOut) {
|
|
throw new Error("stream scenario timed out")
|
|
}
|
|
|
|
if (result.exitCode !== 0) {
|
|
throw new Error(`CLI exited with non-zero code: ${result.exitCode}`)
|
|
}
|
|
}
|