mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-11-01 02:11:54 +00:00
114 lines
4.2 KiB
JavaScript
114 lines
4.2 KiB
JavaScript
#!/usr/bin/env node
|
|
// Reduces a Codex protocol stream to the lifecycle facts TORCH may retain.
|
|
// Raw turns, images, and stderr are drained but never copied into parent state.
|
|
import { spawn } from 'node:child_process';
|
|
|
|
const MAX_PROTOCOL_LINE_CHARS = 32 * 1024;
|
|
const [command, ...args] = process.argv.slice(2);
|
|
if (!command) process.exit(64);
|
|
|
|
const child = spawn(command, args, { cwd: process.cwd(), stdio: ['ignore', 'pipe', 'pipe'] });
|
|
let pending = '';
|
|
let droppingOversizedLine = false;
|
|
let threadStarted = null;
|
|
let terminal = null;
|
|
let childSpawnError = null;
|
|
let forwardedSignal = null;
|
|
let pendingWrites = 0;
|
|
let finalOutcome = null;
|
|
|
|
function safeCode(value) {
|
|
return typeof value === 'string' && /^[A-Z][A-Z0-9_.-]{0,79}$/.test(value) ? value : null;
|
|
}
|
|
|
|
function safeFailureCategory(value) {
|
|
if (typeof value !== 'string') return null;
|
|
// Classify a short transient prefix only; never retain diagnostic text itself.
|
|
const text = value.slice(0, 512).toLowerCase();
|
|
if (/(?:requested\s+)?model\b|unsupported\s+model|model\s+.*(?:not supported|unavailable)/.test(text)) return 'model-rejection';
|
|
if (/\b(?:auth(?:entication|orization)?|unauthorized|forbidden|credential)\b/.test(text)) return 'authentication';
|
|
if (/\b(?:quota|rate[ -]?limit|usage[ -]?limit|limit exceeded)\b/.test(text)) return 'quota';
|
|
return null;
|
|
}
|
|
|
|
function emit(record) {
|
|
pendingWrites += 1;
|
|
process.stdout.write(`${JSON.stringify(record)}\n`, () => {
|
|
pendingWrites -= 1;
|
|
finishWhenFlushed();
|
|
});
|
|
}
|
|
|
|
function finishWhenFlushed() {
|
|
if (!finalOutcome || pendingWrites) return;
|
|
const { code, signal } = finalOutcome;
|
|
finalOutcome = null;
|
|
if (signal) exitWithSignal(signal);
|
|
else process.exitCode = Number.isInteger(code) && code >= 0 ? code : 1;
|
|
}
|
|
|
|
function inspectLine(line) {
|
|
let record;
|
|
try { record = JSON.parse(line); } catch { return; }
|
|
if (!threadStarted && record?.type === 'thread.started'
|
|
&& typeof (record.thread_id ?? record.threadId) === 'string'
|
|
&& (record.thread_id ?? record.threadId).length <= 256) {
|
|
threadStarted = record.thread_id ?? record.threadId;
|
|
// Emit this durable identity before a later interruption can suppress close.
|
|
emit({ type: 'thread.started', thread_id: threadStarted });
|
|
}
|
|
if (record?.type === 'turn.completed') terminal = { type: 'turn.completed' };
|
|
if (record?.type === 'turn.failed' || record?.type === 'turn.error' || record?.type === 'error') {
|
|
const error = record.error && typeof record.error === 'object' ? record.error : record;
|
|
const code = safeCode(error.code ?? record.code);
|
|
const category = code ? null : safeFailureCategory(error.message ?? record.message);
|
|
terminal = { type: 'turn.failed', ...(category ? { category } : {}), ...(code ? { code } : {}) };
|
|
}
|
|
}
|
|
|
|
function consume(chunk) {
|
|
let cursor = 0;
|
|
while (cursor < chunk.length) {
|
|
const newline = chunk.indexOf('\n', cursor);
|
|
const end = newline < 0 ? chunk.length : newline;
|
|
const fragment = chunk.slice(cursor, end);
|
|
if (!droppingOversizedLine) {
|
|
if (pending.length + fragment.length > MAX_PROTOCOL_LINE_CHARS) {
|
|
pending = '';
|
|
droppingOversizedLine = true;
|
|
} else pending += fragment;
|
|
}
|
|
if (newline < 0) return;
|
|
if (!droppingOversizedLine) inspectLine(pending.trim());
|
|
pending = '';
|
|
droppingOversizedLine = false;
|
|
cursor = newline + 1;
|
|
}
|
|
}
|
|
|
|
child.stdout.setEncoding('utf8');
|
|
child.stdout.on('data', consume);
|
|
child.stderr.on('data', () => {});
|
|
child.on('error', (error) => { childSpawnError = safeCode(error?.code) ?? 'EXECUTOR_SPAWN_ERROR'; });
|
|
|
|
const forwardedSignals = ['SIGTERM', 'SIGINT', 'SIGHUP'];
|
|
for (const signal of forwardedSignals) {
|
|
process.on(signal, () => {
|
|
forwardedSignal ??= signal;
|
|
if (!child.killed) child.kill(signal);
|
|
});
|
|
}
|
|
|
|
function exitWithSignal(signal) {
|
|
for (const name of forwardedSignals) process.removeAllListeners(name);
|
|
process.kill(process.pid, signal);
|
|
}
|
|
|
|
child.on('close', (code, signal) => {
|
|
if (pending && !droppingOversizedLine) inspectLine(pending.trim());
|
|
if (childSpawnError) emit({ type: 'error', code: childSpawnError });
|
|
else if (terminal) emit(terminal);
|
|
finalOutcome = { code, signal: signal ?? forwardedSignal };
|
|
finishWhenFlushed();
|
|
});
|