/** * OpenCode v2 Plugin: Auto-discover models from OpenAI-compatible endpoints. * * Config-driven: iterates over the providers defined in opencode.jsonc and * discovers models for every provider whose `package` contains "openai-compatible" * AND which has opted in via `settings.discover_models === true` * (compat: `settings.modelsDiscovery.enabled === true`). * * The plugin never creates providers itself — it only augments existing ones. * Discovered models are merged into the provider's existing models (dedup by id). */ import { Plugin, Model } from "@opencode/plugin"; interface Target { id: string; baseURL: string; apiKey: string; } function isOpenAICompatible(pkg: unknown): boolean { return typeof pkg === "string" && pkg.toLowerCase().includes("openai-compatible"); } function hasOptIn(settings: Record): boolean { if (settings.discover_models === true) return true; const md = settings.modelsDiscovery; if (md && typeof md === "object" && (md as Record).enabled === true) { return true; } return false; } async function fetchModels( baseURL: string, apiKey: string ): Promise { const headers: Record = { "Content-Type": "application/json" }; if (apiKey) { headers["Authorization"] = `Bearer ${apiKey}`; } const res = await fetch(`${baseURL}/models`, { headers }); if (!res.ok) { throw new Error(`HTTP ${res.status} from ${baseURL}/models`); } const json = await res.json(); const rawModels = (json.data || []) as Array>; return rawModels.map((m) => { const modelId = typeof m.id === "string" ? m.id : String(m.id); const contextLength = typeof m.context_length === "number" ? m.context_length : 131072; const maxTokens = typeof m.max_tokens === "number" ? m.max_tokens : 32768; const inputMods = Array.isArray(m.input_modalities) ? (m.input_modalities as string[]) : ["text"]; const outputMods = Array.isArray(m.output_modalities) ? (m.output_modalities as string[]) : ["text"]; return { id: modelId, modelID: modelId, providerID: "", // filled in by caller name: modelId, capabilities: { tools: true, input: inputMods, output: outputMods, }, variants: [], cost: [{ input: 0, output: 0, cache: { read: 0, write: 0 } }], limit: { context: contextLength, output: Math.min(maxTokens, contextLength), }, time: { released: Math.floor(Date.now() / 1000), }, status: "active" as const, enabled: true, } as unknown as Model.Info; }); } export default Plugin.define({ id: "opencode.openai-compatible-model-discovery", async setup(ctx) { // 1. Collect discovery targets from configured providers (sync). const targets: Target[] = []; try { await ctx.provider.transform((editor) => { for (const rec of editor.list()) { const info = rec.provider as Record; if (!isOpenAICompatible(info.package)) continue; const settings = info.settings && typeof info.settings === "object" ? (info.settings as Record) : {}; if (!hasOptIn(settings)) continue; const baseURL = typeof settings.baseURL === "string" ? settings.baseURL : undefined; if (!baseURL) { console.warn( `[openai-discovery] Provider "${info.id}" has no settings.baseURL — skipping` ); continue; } const apiKey = typeof settings.apiKey === "string" ? settings.apiKey : ""; targets.push({ id: String(info.id), baseURL, apiKey }); } }); } catch (error) { console.error( `[openai-discovery] Failed to read providers: ${error instanceof Error ? error.message : String(error)}` ); return; } if (targets.length === 0) { console.log( "[openai-discovery] No opted-in openai-compatible providers found — nothing to do" ); return; } // 2. Discover + merge per target (isolated errors). for (const target of targets) { try { const models = await fetchModels(target.baseURL, target.apiKey); // stamp providerID for (const m of models) { (m as { providerID: string }).providerID = target.id; } await ctx.provider.transform((editor) => { const source = editor.get(target.id); if (!source) { console.warn( `[openai-discovery] Provider "${target.id}" no longer exists — skipping merge` ); return; } const existing = [...source.models.values()]; const existingIDs = new Set(existing.map((model) => String(model.id))); const merged = [ ...existing, ...models.filter((m) => !existingIDs.has(String(m.id))), ]; editor.models.set(target.id, merged); }); console.log( `[openai-discovery] Registered ${models.length} models in provider "${target.id}"` ); } catch (error) { console.error( `[openai-discovery] Failed to discover models for "${target.id}": ${error instanceof Error ? error.message : String(error)}` ); } } }, });