freezmo/lib/providers.ts
Martin Dvorak 8651674cf5 First-run gate offers free Ollama vs paid providers; add Google Gemini
The gate (user-key installs) now headlines Ollama with a "Free" badge and
adds an "or - Use a paid provider instead" option that opens the same
provider rows as Settings, framed as billed by the provider directly.
Saving a provider key there lists its models and switches study
generation to it automatically, so the gate steps aside with no Ollama
key at all.

Gemini joins the registry via its OpenAI-compatible endpoint
(generativelanguage.googleapis.com/v1beta/openai); the privacy policy
names it (en+cs).

Providers suite grows to 20 checks: the gate scenario runs against a
second user-key-mode app instance (free badge, paid option, provider
rows, pass-through, generation switch). All other gates green:
contrast 0 findings, mobile 126/126, chat 102/102, wizard 95/95,
bubbles 34/34, regression 44/44, i18n 13/13, native 33/33.
Version 0.4.9, SW -> freezmo-v73.
2026-10-14 07:09:05 +00:00

97 lines
2.9 KiB
TypeScript

/**
* Cloud model providers the user can bring their own key for. Every one of
* them speaks the OpenAI Chat Completions shape (Anthropic ships an official
* compatibility layer at the same path), so one adapter covers them all —
* plus a "custom" entry for any other OpenAI-compatible endpoint.
*
* Shared by the client (key inputs, pickers) and the server (routing): no
* secrets live here.
*/
export type ProviderInfo = {
id: string;
label: string;
/** OpenAI-compatible API base. Empty for the custom provider. */
baseUrl: string;
/** Placeholder shown in the key input. */
keyHint: string;
/** Where to create a key. */
docs: string;
/**
* Models offered before the live listing arrives (and on providers whose
* /models needs a paid tier). Free-tier ids first where they exist.
*/
models: string[];
};
export const PROVIDERS: ProviderInfo[] = [
{
id: "openai",
label: "OpenAI",
baseUrl: "https://api.openai.com/v1",
keyHint: "sk-…",
docs: "https://platform.openai.com/api-keys",
models: ["gpt-4o-mini", "gpt-4o", "o4-mini"],
},
{
id: "anthropic",
label: "Anthropic (Claude)",
baseUrl: "https://api.anthropic.com/v1",
keyHint: "sk-ant-…",
docs: "https://console.anthropic.com/settings/keys",
models: ["claude-sonnet-4-5", "claude-haiku-4-5", "claude-3-5-haiku-latest"],
},
{
id: "deepseek",
label: "DeepSeek",
baseUrl: "https://api.deepseek.com/v1",
keyHint: "sk-…",
docs: "https://platform.deepseek.com/api_keys",
models: ["deepseek-chat", "deepseek-reasoner"],
},
{
id: "groq",
label: "Groq",
baseUrl: "https://api.groq.com/openai/v1",
keyHint: "gsk_…",
docs: "https://console.groq.com/keys",
models: ["llama-3.3-70b-versatile", "llama-3.1-8b-instant", "openai/gpt-oss-120b"],
},
{
id: "openrouter",
label: "OpenRouter",
baseUrl: "https://openrouter.ai/api/v1",
keyHint: "sk-or-…",
docs: "https://openrouter.ai/keys",
models: [
"meta-llama/llama-3.3-70b-instruct:free",
"deepseek/deepseek-chat-v3-0324:free",
"qwen/qwen3-235b-a22b:free",
],
},
{
id: "gemini",
label: "Google Gemini",
baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
keyHint: "AIza…",
docs: "https://aistudio.google.com/app/apikey",
models: ["gemini-2.5-flash", "gemini-2.5-pro", "gemini-2.0-flash"],
},
{
id: "custom",
label: "Custom (OpenAI-compatible)",
baseUrl: "",
keyHint: "key…",
docs: "",
models: [],
},
];
export function providerInfo(id: string): ProviderInfo | undefined {
return PROVIDERS.find((provider) => provider.id === id);
}
/** Headers the browser sends so the server can reach the picked provider. */
export const PROVIDER_HEADER = "x-freezmo-provider";
export const PROVIDER_KEY_HEADER = "x-freezmo-provider-key";
export const PROVIDER_BASE_HEADER = "x-freezmo-provider-base";