import type { ModelInfo } from './types'; import { homedir } from 'os'; import { join } from 'path'; // The Claude harness runs the `claude` CLI, so its tiers are a fixed set. const CLAUDE_CODE_MODELS: ModelInfo[] = [ { id: 'claude-code/opus', name: 'opus', provider: 'claude-code', contextWindow: 200000, maxTokens: 16000, reasoning: true, images: true }, { id: 'claude-code/sonnet', name: 'sonnet', provider: 'claude-code', contextWindow: 200000, maxTokens: 16000, reasoning: true, images: true }, { id: 'claude-code/haiku', name: 'haiku', provider: 'claude-code', contextWindow: 200000, maxTokens: 8192, reasoning: false, images: true }, ]; const OPENCODE_BIN = process.env.OPENCODE_BIN || join(homedir(), '.opencode', 'bin', 'opencode'); // Curated OpenCode models to surface in the picker (the full `opencode models` catalog is ~58 entries). const OPENCODE_ALLOWLIST = new Set(['opencode/big-pickle', 'opencode/claude-haiku-4-5']); // Cache the OpenCode catalog; it's stable for a session and `opencode models` costs a subprocess. let openCodeCache: ModelInfo[] | null = null; export function invalidateModelCache(): void { openCodeCache = null; } // Enumerate OpenCode models via `opencode models` (ids are `providerID/modelID`, e.g. // `opencode/claude-opus-4-8`). Metadata (context/tokens/modalities) is left at neutral defaults for // now — it can be enriched later from a serve's GET /config/providers. async function listOpenCodeModels(): Promise { if (openCodeCache) return openCodeCache; try { const proc = Bun.spawn([OPENCODE_BIN, 'models'], { stdout: 'pipe', stderr: 'ignore' }); const out = await new Response(proc.stdout).text(); await proc.exited; const models: ModelInfo[] = out // eslint-disable-next-line no-control-regex .replace(/\x1b\[[0-9;]*m/g, '') .split('\n') .map((line) => line.trim()) .filter((line) => OPENCODE_ALLOWLIST.has(line)) .map((full) => { const slash = full.indexOf('/'); return { id: full, name: full.slice(slash + 1), provider: full.slice(0, slash), contextWindow: 200000, maxTokens: 8192, reasoning: false, images: true, } satisfies ModelInfo; }); openCodeCache = models; return models; } catch { return []; } } export async function listChatModels(): Promise { const openCode = await listOpenCodeModels(); return [...CLAUDE_CODE_MODELS, ...openCode]; }