'use strict';
const { createProfile } = require('ilink-bridge-profile');
const { spawn } = require('child_process');
const readline = require('readline');
const TIMEOUT_MS = 300_000;
function streamClaude(args, onChunk) {
return new Promise((resolve, reject) => {
const allArgs = ['--output-format', 'stream-json', '--dangerously-skip-permissions', ...args];
const child = spawn('claude', allArgs, { stdio: ['pipe', 'pipe', 'pipe'] });
const stderrChunks = [];
child.stderr.on('data', (d) => stderrChunks.push(d));
child.stdin.end();
let resultData = null;
let timedOut = false;
const timer = setTimeout(() => {
timedOut = true;
child.kill('SIGTERM');
reject(new Error(`claude timed out after ${TIMEOUT_MS / 1000}s`));
}, TIMEOUT_MS);
const rl = readline.createInterface({ input: child.stdout, crlfDelay: Infinity });
rl.on('line', (line) => {
if (!line.trim()) return;
let event;
try {
event = JSON.parse(line);
} catch {
return;
}
if (event.type === 'assistant') {
const blocks = event.message?.content ?? [];
const text = blocks
.filter((b) => b.type === 'text')
.map((b) => b.text ?? '')
.join('');
if (text) onChunk(text);
} else if (event.type === 'result') {
resultData = { result: event.result ?? '', sessionId: event.session_id ?? '' };
}
});
child.on('close', (code) => {
clearTimeout(timer);
if (timedOut) return;
rl.close();
if (resultData) {
resolve(resultData);
} else {
const stderr = Buffer.concat(stderrChunks).toString('utf8').trim();
reject(new Error(`claude exited with code ${code}, no result event\nstderr: ${stderr}`));
}
});
child.on('error', (err) => {
clearTimeout(timer);
reject(err);
});
});
}
createProfile(async (ctx) => {
const { message, sessionId, sendPartial } = ctx;
const model = process.env.CLAUDE_MODEL;
const baseArgs = ['--print'];
if (model) baseArgs.push('--model', model);
baseArgs.push('-p', message);
let newSessionId = '';
const tryStream = async (sid) => {
const args = [...baseArgs];
if (sid) args.push('--resume', sid);
const data = await streamClaude(args, (chunk) => sendPartial(chunk));
newSessionId = data.sessionId;
};
try {
await tryStream(sessionId);
} catch (err) {
if (sessionId) {
process.stderr.write(
`[claude-code] session ${sessionId} resume failed (${err.message}), retrying as new session\n`,
);
await tryStream('');
} else {
throw err;
}
}
return { response: '', sessionId: newSessionId || undefined };
});