UNPKG

next

Version:

The React Framework

228 lines (227 loc) • 9.2 kB
import { randomUUID } from 'crypto'; import spawn from 'next/dist/compiled/cross-spawn'; import createDebug from 'next/dist/compiled/debug'; const debug = createDebug('next:upgrade'); function record(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {}; } function string(value) { return typeof value === 'string' ? value : ''; } function efforts(value) { return Array.isArray(value) ? [ ...new Set(value.map(string).filter((level)=>level && level !== 'default')) ] : []; } function parseModels(value, parse) { if (!Array.isArray(value)) { throw new Error('unexpected model catalog format'); } return value.map((entry)=>parse(record(entry))).filter((model)=>model !== null); } export function getCodexModels(path, cwd, signal) { return discoverModels('codex', path, [ 'app-server' ], cwd, signal, (send, complete)=>{ const initializeId = randomUUID(); let listId; const models = []; const cursors = new Set(); const list = (cursor)=>{ listId = randomUUID(); send({ id: listId, method: 'model/list', params: { includeHidden: false, ...cursor ? { cursor } : {} } }); }; return { initialize () { send({ id: initializeId, method: 'initialize', params: { clientInfo: { name: 'next_upgrade', version: '1' } } }); }, receive (message) { if (message.id !== initializeId && (listId === undefined || message.id !== listId)) return; if (message.error) throw new Error('CLI returned a protocol error'); if (message.id === initializeId) { if (!message.result) throw new Error('missing initialization result'); send({ method: 'initialized' }); list(); return; } const response = record(message.result); models.push(...parseModels(response.data, (model)=>{ const id = string(model.model); if (!id || model.hidden === true) return null; return { id, label: string(model.displayName) || id, description: string(model.description), efforts: efforts(Array.isArray(model.supportedReasoningEfforts) ? model.supportedReasoningEfforts.map((level)=>record(level).reasoningEffort) : undefined), isDefault: model.isDefault === true }; })); const cursor = response.nextCursor; if (cursor !== null && cursor !== undefined) { if (typeof cursor !== 'string' || !cursor || cursors.has(cursor)) { throw new Error('invalid pagination cursor'); } cursors.add(cursor); list(cursor); return; } complete(models); } }; }); } export function getClaudeModels(path, cwd, signal) { return discoverModels('claude', path, [ '-p', '--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose', '--no-session-persistence' ], cwd, signal, (send, complete)=>{ const initializeId = randomUUID(); return { initialize () { send({ type: 'control_request', request_id: initializeId, request: { subtype: 'initialize' } }); }, receive (message) { if (message.type !== 'control_response') return; const response = record(message.response); if (response.request_id !== initializeId) return; if (response.subtype !== 'success') throw new Error('CLI returned an initialization error'); complete(parseModels(record(response.response).models, (model)=>{ const id = string(model.value); if (!id) return null; return { id, label: string(model.displayName) || id, description: string(model.description), efforts: model.supportsEffort === false ? [] : efforts(model.supportedEffortLevels), isDefault: id === 'default' }; })); } }; }); } export function getHarnessModels(name, path, cwd, signal) { return name === 'codex' ? getCodexModels(path, cwd, signal) : getClaudeModels(path, cwd, signal); } // null means discovery was cancelled, rather than an unavailable catalog. async function discoverModels(name, path, args, cwd, signal, createProtocol) { if (signal == null ? void 0 : signal.aborted) return null; let child; try { child = spawn(path, args, { cwd, stdio: 'pipe' }); } catch { debug('%s model discovery failed: could not start CLI', name); return []; } return new Promise((resolve)=>{ var _child_stdout, _child_stdout1, // Drain stderr, but never log raw CLI output (which may contain credentials). _child_stderr, _child_stdin; let buffer = ''; let bytes = 0; let stopping = false; let result = []; let killTimer; const stop = (value, reason)=>{ var _child_stdin; if (stopping) return; stopping = true; result = value; clearTimeout(timeout); if (reason) debug('%s model discovery failed: %s', name, reason); (_child_stdin = child.stdin) == null ? void 0 : _child_stdin.end(); child.kill('SIGTERM'); killTimer = setTimeout(()=>child.kill('SIGKILL'), 250); }; const timeout = setTimeout(()=>stop([], 'timed out'), 5000); const interrupt = ()=>stop(null); process.on('SIGINT', interrupt); process.on('SIGTERM', interrupt); process.on('SIGHUP', interrupt); const send = (message)=>{ var _child_stdin; if (!stopping) (_child_stdin = child.stdin) == null ? void 0 : _child_stdin.write(JSON.stringify(message) + '\n'); }; const protocol = createProtocol(send, (models)=>{ const unique = [ ...new Map(models.map((model)=>[ model.id, model ])).values() ]; stop(unique, unique.length ? undefined : 'no usable models'); }); signal == null ? void 0 : signal.addEventListener('abort', interrupt, { once: true }); const countOutput = (chunk)=>{ bytes += Buffer.byteLength(chunk); if (bytes > 20 * 1024 * 1024) stop([], 'output limit exceeded'); }; (_child_stdout = child.stdout) == null ? void 0 : _child_stdout.setEncoding('utf8'); (_child_stdout1 = child.stdout) == null ? void 0 : _child_stdout1.on('data', (chunk)=>{ countOutput(chunk); if (stopping) return; buffer += chunk; let newline; while(!stopping && (newline = buffer.indexOf('\n')) !== -1){ const line = buffer.slice(0, newline).trim(); buffer = buffer.slice(newline + 1); if (!line) continue; try { protocol.receive(record(JSON.parse(line))); } catch { stop([], 'invalid discovery response'); } } }); (_child_stderr = child.stderr) == null ? void 0 : _child_stderr.on('data', countOutput); (_child_stdin = child.stdin) == null ? void 0 : _child_stdin.on('error', ()=>stop([], 'could not write discovery request')); child.on('error', ()=>stop([], 'could not start CLI')); child.once('close', ()=>{ clearTimeout(timeout); clearTimeout(killTimer); process.removeListener('SIGINT', interrupt); process.removeListener('SIGTERM', interrupt); process.removeListener('SIGHUP', interrupt); signal == null ? void 0 : signal.removeEventListener('abort', interrupt); if (!stopping) debug('%s model discovery failed: CLI exited early', name); resolve(result); }); child.once('spawn', protocol.initialize); }); } //# sourceMappingURL=model-discovery.js.map