From 168161148c6e2eeabef93513724f2e6ed03024d3 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Sun, 26 Apr 2026 01:59:35 +0000 Subject: [PATCH] feat: fetch model list from opencode CLI instead of hardcoded static list Reads available models dynamically via 'opencode models' command to populate the Paperclip UI model selector. Falls back to previous static list on any error (missing CLI, timeout, parse failure). Fixes FAR-94: 0 models shown in adapter UI. Co-Authored-By: Paperclip --- src/server/models.ts | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/server/models.ts b/src/server/models.ts index 4b4bcbc..e0f8702 100644 --- a/src/server/models.ts +++ b/src/server/models.ts @@ -1,15 +1,33 @@ import type { AdapterModel } from "@paperclipai/adapter-utils"; +import { exec } from "child_process"; +import { promisify } from "util"; -const MODELS: AdapterModel[] = [ - { id: "anthropic/claude-opus-4-7", label: "Claude Opus 4.7" }, - { id: "anthropic/claude-sonnet-4-6", label: "Claude Sonnet 4.6" }, - { id: "anthropic/claude-haiku-4-5", label: "Claude Haiku 4.5" }, - { id: "openai/gpt-4o", label: "GPT-4o" }, - { id: "openai/gpt-4o-mini", label: "GPT-4o mini" }, - { id: "google/gemini-2.5-pro", label: "Gemini 2.5 Pro" }, - { id: "google/gemini-2.5-flash", label: "Gemini 2.5 Flash" }, -]; +const execAsync = promisify(exec); export async function listK8sModels(): Promise { - return MODELS; -} + try { + const result = await execAsync("opencode models", { timeout: 30_000 }); + const output = result.stdout; + const lines = output.split("\n").map((l) => l.trim()).filter(Boolean); + const models: AdapterModel[] = []; + for (const line of lines) { + if (!line) continue; + const parts = line.split("/"); + const id = line; + const label = parts[parts.length - 1].replace(/-/g, " ").replace(/_/g, " "); + models.push({ id, label }); + } + return models; + } catch { + const fallback: AdapterModel[] = [ + { id: "anthropic/claude-opus-4-7", label: "Claude Opus 4.7" }, + { id: "anthropic/claude-sonnet-4-6", label: "Claude Sonnet 4.6" }, + { id: "anthropic/claude-haiku-4-5", label: "Claude Haiku 4.5" }, + { id: "openai/gpt-4o", label: "GPT-4o" }, + { id: "openai/gpt-4o-mini", label: "GPT-4o mini" }, + { id: "google/gemini-2.5-pro", label: "Gemini 2.5 Pro" }, + { id: "google/gemini-2.5-flash", label: "Gemini 2.5 Flash" }, + ]; + return fallback; + } +} \ No newline at end of file