Files
plan2code/plan2code-metrics/src/invoke-llm.ts
T
2026-03-01 12:24:11 -08:00

91 lines
2.6 KiB
TypeScript

/**
* invoke-llm.ts
* Unified LLM invocation for plan2code-metrics.
* Supports Claude Code (temp file → stdin) and Copilot CLI (stdin string).
* Mirrors the agent pattern from plan2code-loop.
*/
import { execa } from 'execa';
import { writeFileSync, unlinkSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
// ── Agent definitions ────────────────────────────────────────────────────────
export type AgentType = 'claude-code' | 'copilot-cli';
export interface AgentDef {
name: AgentType;
displayName: string;
command: string;
defaultModel: string;
}
export const AGENTS: Record<AgentType, AgentDef> = {
'claude-code': {
name: 'claude-code',
displayName: 'Claude Code',
command: 'claude',
defaultModel: 'default',
},
'copilot-cli': {
name: 'copilot-cli',
displayName: 'GitHub Copilot CLI',
command: 'copilot',
defaultModel: 'claude-sonnet-4',
},
};
// ── Invocation ───────────────────────────────────────────────────────────────
export interface InvokeLLMOptions {
prompt: string;
model: string;
agent: AgentType;
timeout?: number;
}
export async function invokeLLM(opts: InvokeLLMOptions): Promise<string> {
const { prompt, model, agent, timeout = 300_000 } = opts;
const def = AGENTS[agent];
if (agent === 'claude-code') {
// Write prompt to temp file — more reliable than stdin on Windows
const tempFile = join(tmpdir(), `plan2code-metrics-prompt-${Date.now()}.txt`);
writeFileSync(tempFile, prompt, 'utf-8');
try {
const args: string[] = [
'--print',
'--dangerously-skip-permissions',
];
// Only add --model if not using default
if (model && model !== 'default') {
args.push('--model', model);
}
const result = await execa(def.command, args, {
inputFile: tempFile,
timeout,
});
return result.stdout;
} finally {
try { unlinkSync(tempFile); } catch { /* ignore cleanup errors */ }
}
} else {
// Copilot CLI: pipe prompt via stdin string
const args: string[] = [];
// Only add --model if not using default
if (model && model !== 'default') {
args.push('--model', model);
}
args.push('--allow-all-tools', '-s');
const result = await execa(def.command, args, {
input: prompt,
timeout,
});
return result.stdout;
}
}