(options: RunStreamCaseOptions)
| 64 | } |
| 65 | |
| 66 | export async function runStreamCase(options: RunStreamCaseOptions): Promise<void> { |
| 67 | const cliRoot = process.env.ROO_CLI_ROOT ? path.resolve(process.env.ROO_CLI_ROOT) : defaultCliRoot |
| 68 | const timeoutMs = options.timeoutMs ?? 120_000 |
| 69 | |
| 70 | const child = execa( |
| 71 | "pnpm", |
| 72 | ["dev", "--print", "--stdin-prompt-stream", "--provider", "openrouter", "--output-format", "stream-json"], |
| 73 | { |
| 74 | cwd: cliRoot, |
| 75 | stdin: "pipe", |
| 76 | stdout: "pipe", |
| 77 | stderr: "pipe", |
| 78 | reject: false, |
| 79 | forceKillAfterDelay: 2_000, |
| 80 | }, |
| 81 | ) |
| 82 | |
| 83 | child.stderr?.on("data", (chunk) => { |
| 84 | process.stderr.write(chunk) |
| 85 | }) |
| 86 | |
| 87 | let requestCounter = 0 |
| 88 | |
| 89 | const context: StreamCaseContext = { |
| 90 | cliRoot, |
| 91 | timeoutMs, |
| 92 | nextRequestId(prefix: string): string { |
| 93 | requestCounter += 1 |
| 94 | return `${prefix}-${Date.now()}-${requestCounter}` |
| 95 | }, |
| 96 | sendCommand(command: StreamCommand): void { |
| 97 | if (child.stdin?.destroyed) { |
| 98 | return |
| 99 | } |
| 100 | |
| 101 | child.stdin.write(`${JSON.stringify(command)}\n`) |
| 102 | }, |
| 103 | } |
| 104 | |
| 105 | let handlerError: Error | null = null |
| 106 | let timedOut = false |
| 107 | |
| 108 | const timeout = setTimeout(() => { |
| 109 | timedOut = true |
| 110 | const message = options.onTimeoutMessage?.(context) ?? "timed out waiting for stream scenario completion" |
| 111 | handlerError = new Error(message) |
| 112 | child.kill("SIGTERM") |
| 113 | }, timeoutMs) |
| 114 | |
| 115 | const rl = readline.createInterface({ |
| 116 | input: child.stdout!, |
| 117 | crlfDelay: Infinity, |
| 118 | }) |
| 119 | |
| 120 | rl.on("line", (line) => { |
| 121 | process.stdout.write(`${line}\n`) |
| 122 | |
| 123 | const event = parseEvent(line) |
no test coverage detected