105 lines
3.4 KiB
TypeScript
105 lines
3.4 KiB
TypeScript
// Helpers specific to Opencode CLI output/argv handling.
|
|
|
|
// Preferred binary name for Opencode CLI invocations.
|
|
export const OPENCODE_BIN = "opencode";
|
|
|
|
export const OPENCODE_IDENTITY_PREFIX =
|
|
"You are Openclawd running on the user's Mac via clawdis. Your scratchpad is /Users/steipete/openclawd; this is your folder and you can add what you like in markdown files and/or images. You don't need to be concise, but WhatsApp replies must stay under ~1500 characters. Media you can send: images ≤6MB, audio/video ≤16MB, documents ≤100MB. The prompt may include a media path and an optional Transcript: section—use them when present. If a prompt is a heartbeat poll and nothing needs attention, reply with exactly HEARTBEAT_OK and nothing else; for any alert, do not include HEARTBEAT_OK.";
|
|
|
|
export type OpencodeJsonParseResult = {
|
|
text?: string;
|
|
parsed: unknown[];
|
|
valid: boolean;
|
|
meta?: {
|
|
durationMs?: number;
|
|
cost?: number;
|
|
tokens?: {
|
|
input?: number;
|
|
output?: number;
|
|
};
|
|
};
|
|
};
|
|
|
|
export function parseOpencodeJson(raw: string): OpencodeJsonParseResult {
|
|
const lines = raw.split(/\n+/).filter((s) => s.trim());
|
|
const parsed: unknown[] = [];
|
|
let text = "";
|
|
let valid = false;
|
|
let startTime: number | undefined;
|
|
let endTime: number | undefined;
|
|
let cost = 0;
|
|
let inputTokens = 0;
|
|
let outputTokens = 0;
|
|
|
|
for (const line of lines) {
|
|
try {
|
|
const event = JSON.parse(line);
|
|
parsed.push(event);
|
|
if (event && typeof event === "object") {
|
|
// Opencode emits a stream of events.
|
|
if (event.type === "step_start") {
|
|
valid = true;
|
|
if (typeof event.timestamp === "number") {
|
|
if (startTime === undefined || event.timestamp < startTime) {
|
|
startTime = event.timestamp;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (event.type === "text" && event.part?.text) {
|
|
text += event.part.text;
|
|
valid = true;
|
|
}
|
|
|
|
if (event.type === "step_finish") {
|
|
valid = true;
|
|
if (typeof event.timestamp === "number") {
|
|
endTime = event.timestamp;
|
|
}
|
|
if (event.part) {
|
|
if (typeof event.part.cost === "number") {
|
|
cost += event.part.cost;
|
|
}
|
|
if (event.part.tokens) {
|
|
inputTokens += event.part.tokens.input || 0;
|
|
outputTokens += event.part.tokens.output || 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
// ignore non-JSON lines
|
|
}
|
|
}
|
|
|
|
const meta: OpencodeJsonParseResult["meta"] = {};
|
|
if (startTime !== undefined && endTime !== undefined) {
|
|
meta.durationMs = endTime - startTime;
|
|
}
|
|
if (cost > 0) meta.cost = cost;
|
|
if (inputTokens > 0 || outputTokens > 0) {
|
|
meta.tokens = { input: inputTokens, output: outputTokens };
|
|
}
|
|
|
|
return {
|
|
text: text || undefined,
|
|
parsed,
|
|
valid: valid && parsed.length > 0,
|
|
meta: Object.keys(meta).length > 0 ? meta : undefined,
|
|
};
|
|
}
|
|
|
|
export function summarizeOpencodeMetadata(
|
|
meta: OpencodeJsonParseResult["meta"],
|
|
): string | undefined {
|
|
if (!meta) return undefined;
|
|
const parts: string[] = [];
|
|
if (meta.durationMs !== undefined)
|
|
parts.push(`duration=${meta.durationMs}ms`);
|
|
if (meta.cost !== undefined) parts.push(`cost=$${meta.cost.toFixed(4)}`);
|
|
if (meta.tokens) {
|
|
parts.push(`tokens=${meta.tokens.input}+${meta.tokens.output}`);
|
|
}
|
|
return parts.length ? parts.join(", ") : undefined;
|
|
}
|