Files
bot-agents/src/agents/opencode.ts
T
Danny Kim c8ffb9cc6f
Agents / agent (pull_request) Has been skipped
Check / check (pull_request) Successful in 49s
Replace NodeJS dict aliases with maps and named field reads
2026-10-19 02:30:54 +09:00

95 lines
3.0 KiB
TypeScript

import { field, isObject } from "#common";
import { AgentAuthError } from "#profiles";
export function openCodeArgs(model: string, prompt: string): string[] {
return ["run", "--format", "json", "--auto", "--model", model, prompt];
}
type ChunkWriter = (chunk: Uint8Array) => void | Promise<void>;
interface OpenCodeOutput {
stdout: string;
stderr: string;
}
export async function collectOpenCodeOutput(
stdout: AsyncIterable<Uint8Array>,
stderr: AsyncIterable<Uint8Array>,
writeStdout: ChunkWriter,
writeStderr: ChunkWriter,
): Promise<OpenCodeOutput> {
const collect = async (
stream: AsyncIterable<Uint8Array>,
write: ChunkWriter,
): Promise<string> => {
const chunks: Uint8Array[] = [];
for await (const chunk of stream) {
chunks.push(chunk);
await write(chunk);
}
const length = chunks.reduce((total, chunk) => total + chunk.length, 0);
const joined = new Uint8Array(length);
let offset = 0;
for (const chunk of chunks) {
joined.set(chunk, offset);
offset += chunk.length;
}
return new TextDecoder().decode(joined);
};
const [output, error] = await Promise.all([
collect(stdout, writeStdout),
collect(stderr, writeStderr),
]);
return { stdout: output, stderr: error };
}
// OpenCode reports provider failures, including a rejected OpenRouter key
// (401 "User not found."), as a JSON error event on stdout and exits 1 with an
// empty stderr.
export function openCodeError(output: string): Error | null {
let failure: Error | null = null;
for (const line of output.split("\n")) {
if (line.trim() === "") continue;
let event: unknown;
try {
event = JSON.parse(line);
} catch {
continue;
}
const error = field(event, "error");
if (field(event, "type") !== "error" || !isObject(error)) {
continue;
}
const name = field(error, "name");
const data = field(error, "data");
const message = field(data, "message");
const statusCode = field(data, "statusCode");
const detail = `opencode ${typeof name === "string" ? name : "error"}${
typeof statusCode === "number" ? ` (API error ${statusCode})` : ""
}: ${typeof message === "string" ? message : "unknown error"}`;
failure =
statusCode === 401 || statusCode === 403
? new AgentAuthError("opencode sign-in failed", {
cause: new Error(detail),
})
: new Error(detail);
}
return failure;
}
export function parseOpenCodeEvents(output: string): string {
let result: string | null = null;
for (const line of output.split("\n")) {
if (line.trim() === "") continue;
const event: unknown = JSON.parse(line);
const part = field(event, "part");
if (field(event, "type") !== "text" || !isObject(part)) continue;
const text = field(part, "text");
if (typeof text === "string" && text.trim() !== "") result = text;
}
if (result === null) {
throw openCodeError(output) ?? new Error("OpenCode produced no text output");
}
return result;
}