Files
platform/src/servers/api/chat/list-models.ts
T
pastilhasandClaude Opus 4.8 3a2f3317b2 chat: curate OpenCode models to Big Pickle + Claude Haiku
The full `opencode models` catalog is ~58 entries; surface only opencode/big-pickle
and opencode/claude-haiku-4-5 in the picker for now via an allow-list. Easy to extend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 16:45:43 +00:00

64 lines
2.5 KiB
TypeScript

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<ModelInfo[]> {
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<ModelInfo[]> {
const openCode = await listOpenCodeModels();
return [...CLAUDE_CODE_MODELS, ...openCode];
}