diff --git a/entry b/entry index b443e72..d3f647d 100755 --- a/entry +++ b/entry @@ -131987,21 +131987,42 @@ var providers = { "big-pickle": { displayName: "Big Pickle", resolve: "opencode/big-pickle", - recommended: true + recommended: true, + envVars: [], + isFree: true }, "claude-opus": { displayName: "Claude Opus", resolve: "opencode/claude-opus-4-6" }, "claude-sonnet": { displayName: "Claude Sonnet", resolve: "opencode/claude-sonnet-4-6" }, "claude-haiku": { displayName: "Claude Haiku", resolve: "opencode/claude-haiku-4-5" }, "gpt-codex": { displayName: "GPT Codex", resolve: "opencode/gpt-5.3-codex" }, + "gpt-codex-mini": { displayName: "GPT Codex Mini", resolve: "opencode/gpt-5.1-codex-mini" }, "gemini-pro": { displayName: "Gemini Pro", resolve: "opencode/gemini-3.1-pro" }, "gemini-flash": { displayName: "Gemini Flash", resolve: "opencode/gemini-3-flash" }, "kimi-k2": { displayName: "Kimi K2", resolve: "opencode/kimi-k2.5" }, - "gpt-5-nano": { displayName: "GPT-5 Nano", resolve: "opencode/gpt-5-nano" }, + "gpt-5-nano": { + displayName: "GPT Nano", + resolve: "opencode/gpt-5-nano", + envVars: [], + isFree: true + }, "mimo-v2-flash-free": { displayName: "MiMo V2 Flash", - resolve: "opencode/mimo-v2-flash-free" + resolve: "opencode/mimo-v2-flash-free", + envVars: [], + isFree: true }, - "minimax-m2.5-free": { displayName: "MiniMax M2.5", resolve: "opencode/minimax-m2.5-free" } + "minimax-m2.5-free": { + displayName: "MiniMax M2.5", + resolve: "opencode/minimax-m2.5-free", + envVars: [], + isFree: true + }, + "nemotron-3-super-free": { + displayName: "Nemotron 3 Super", + resolve: "opencode/nemotron-3-super-free", + envVars: [], + isFree: true + } } }), openrouter: provider({ @@ -132049,7 +132070,8 @@ var modelAliases = Object.entries(providers).flatMap( provider: providerKey, displayName: def.displayName, resolve: def.resolve, - recommended: def.recommended ?? false + recommended: def.recommended ?? false, + isFree: def.isFree ?? false })) ); function resolveModelSlug(slug) { diff --git a/models.test.ts b/models.test.ts index 2e6e9a2..14e3062 100644 --- a/models.test.ts +++ b/models.test.ts @@ -47,6 +47,18 @@ describe("getModelEnvVars", () => { it("returns empty array for unknown provider", () => { expect(getModelEnvVars("unknown/model")).toEqual([]); }); + + it("returns empty env vars for free opencode models", () => { + expect(getModelEnvVars("opencode/big-pickle")).toEqual([]); + expect(getModelEnvVars("opencode/gpt-5-nano")).toEqual([]); + expect(getModelEnvVars("opencode/mimo-v2-flash-free")).toEqual([]); + expect(getModelEnvVars("opencode/minimax-m2.5-free")).toEqual([]); + expect(getModelEnvVars("opencode/nemotron-3-super-free")).toEqual([]); + }); + + it("still requires OPENCODE_API_KEY for non-free opencode models", () => { + expect(getModelEnvVars("opencode/claude-opus")).toEqual(["OPENCODE_API_KEY"]); + }); }); describe("resolveModelSlug", () => { diff --git a/models.ts b/models.ts index c66280a..581e911 100644 --- a/models.ts +++ b/models.ts @@ -18,6 +18,8 @@ export interface ModelAlias { resolve: string; /** top-tier pick for this provider — preferred during auto-select */ recommended: boolean; + /** whether this alias is free and requires no API key */ + isFree: boolean; } interface ModelDef { @@ -25,6 +27,8 @@ interface ModelDef { /** concrete models.dev specifier, e.g. "anthropic/claude-opus-4-6" */ resolve: string; recommended?: boolean; + envVars?: readonly string[]; + isFree?: boolean; } export interface ProviderConfig { @@ -110,20 +114,41 @@ export const providers = { displayName: "Big Pickle", resolve: "opencode/big-pickle", recommended: true, + envVars: [], + isFree: true, }, "claude-opus": { displayName: "Claude Opus", resolve: "opencode/claude-opus-4-6" }, "claude-sonnet": { displayName: "Claude Sonnet", resolve: "opencode/claude-sonnet-4-6" }, "claude-haiku": { displayName: "Claude Haiku", resolve: "opencode/claude-haiku-4-5" }, "gpt-codex": { displayName: "GPT Codex", resolve: "opencode/gpt-5.3-codex" }, + "gpt-codex-mini": { displayName: "GPT Codex Mini", resolve: "opencode/gpt-5.1-codex-mini" }, "gemini-pro": { displayName: "Gemini Pro", resolve: "opencode/gemini-3.1-pro" }, "gemini-flash": { displayName: "Gemini Flash", resolve: "opencode/gemini-3-flash" }, "kimi-k2": { displayName: "Kimi K2", resolve: "opencode/kimi-k2.5" }, - "gpt-5-nano": { displayName: "GPT-5 Nano", resolve: "opencode/gpt-5-nano" }, + "gpt-5-nano": { + displayName: "GPT Nano", + resolve: "opencode/gpt-5-nano", + envVars: [], + isFree: true, + }, "mimo-v2-flash-free": { displayName: "MiMo V2 Flash", resolve: "opencode/mimo-v2-flash-free", + envVars: [], + isFree: true, + }, + "minimax-m2.5-free": { + displayName: "MiniMax M2.5", + resolve: "opencode/minimax-m2.5-free", + envVars: [], + isFree: true, + }, + "nemotron-3-super-free": { + displayName: "Nemotron 3 Super", + resolve: "opencode/nemotron-3-super-free", + envVars: [], + isFree: true, }, - "minimax-m2.5-free": { displayName: "MiniMax M2.5", resolve: "opencode/minimax-m2.5-free" }, }, }), openrouter: provider({ @@ -183,8 +208,18 @@ export function getModelProvider(slug: string): string { } export function getModelEnvVars(slug: string): string[] { - const p = getModelProvider(slug); - return (providers as Record)[p]?.envVars.slice() ?? []; + const parsed = parseModel(slug); + const providerConfig = (providers as Record)[parsed.provider]; + if (!providerConfig) { + return []; + } + + const modelConfig = providerConfig.models[parsed.model]; + if (modelConfig?.envVars) { + return modelConfig.envVars.slice(); + } + + return providerConfig.envVars.slice(); } // ── derived flat list ────────────────────────────────────────────────────────── @@ -197,6 +232,7 @@ export const modelAliases: ModelAlias[] = Object.entries(providers).flatMap( displayName: def.displayName, resolve: def.resolve, recommended: def.recommended ?? false, + isFree: def.isFree ?? false, })) );