diff --git a/docs/cli/index.md b/docs/cli/index.md index 9ce439d05..b39654929 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -307,6 +307,7 @@ Options: - `--openrouter-api-key ` - `--ai-gateway-api-key ` - `--moonshot-api-key ` +- `--pollinations-api-key ` - `--kimi-code-api-key ` - `--gemini-api-key ` - `--zai-api-key ` diff --git a/docs/docs.json b/docs/docs.json index a463479aa..0382d2c21 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -93,6 +93,14 @@ "source": "/moonshot/", "destination": "/providers/moonshot" }, + { + "source": "/pollinations", + "destination": "/providers/pollinations" + }, + { + "source": "/pollinations/", + "destination": "/providers/pollinations" + }, { "source": "/openrouter", "destination": "/providers/openrouter" @@ -1010,6 +1018,7 @@ "providers/anthropic", "bedrock", "providers/moonshot", + "providers/pollinations", "providers/minimax", "providers/vercel-ai-gateway", "providers/openrouter", diff --git a/docs/providers/index.md b/docs/providers/index.md index c18ad70fb..1564e1a62 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -39,6 +39,7 @@ See [Venice AI](/providers/venice). - [OpenRouter](/providers/openrouter) - [Vercel AI Gateway](/providers/vercel-ai-gateway) - [Moonshot AI (Kimi + Kimi Code)](/providers/moonshot) +- [Pollinations.ai](/providers/pollinations) - [OpenCode Zen](/providers/opencode) - [Amazon Bedrock](/bedrock) - [Z.AI](/providers/zai) diff --git a/docs/providers/pollinations.md b/docs/providers/pollinations.md new file mode 100644 index 000000000..0790db46c --- /dev/null +++ b/docs/providers/pollinations.md @@ -0,0 +1,90 @@ +--- +summary: "Configure Pollinations.ai (Free/Open Source API)" +--- + +# Pollinations.ai + +Pollinations.ai provides AI API access with OpenAI-compatible endpoints. It supports various models including OpenAI's GPT series, Qwen, and Mistral. + +## Usage + +You can configure Pollinations interactively during onboarding: + +```bash +moltbot onboard --auth-choice pollinations +``` + +Or configure it manually in your `moltbot.json` config file. + +## Configuration + +Pollinations uses a standard OpenAI-compatible endpoint. While it may offer a limited free tier, obtaining an API key is recommended for reliable access. + +### Config snippet + +```javascript +// moltbot.json (partial) +{ + env: { + // Set your Pollinations API key here (sk-...) + POLLINATIONS_API_KEY: "sk-..." + }, + agents: { + defaults: { + model: { primary: "pollinations/openai" }, + // Optional: alias for easier switching + models: { + "pollinations/openai": { alias: "Pollinations GPT-5 Mini" } + } + } + }, + models: { + providers: { + pollinations: { + baseUrl: "https://gen.pollinations.ai/v1", + api: "openai-completions", + apiKey: "${POLLINATIONS_API_KEY}", + models: [ + { + id: "openai", + name: "OpenAI GPT-5 Mini", + contextWindow: 128000, + maxTokens: 8192 + }, + // ... other models + ] + } + } + } +} +``` + +## Available Models + +Pollinations offers a wide range of models. Common ones include: + +- `pollinations/openai`: **GPT-5 Mini** (0.15/0.60 Pollen/M) +- `pollinations/openai-large`: **GPT-5.2** (1.75/14.0 Pollen/M) +- `pollinations/openai-fast`: **GPT-5 Nano** (0.06/0.44 Pollen/M) +- `pollinations/claude`: **Claude Sonnet 4.5** (3.0/15.0 Pollen/M) +- `pollinations/claude-large`: **Claude Opus 4.5** (5.0/25.0 Pollen/M) +- `pollinations/claude-fast`: **Claude Haiku 4.5** (1.0/5.0 Pollen/M) +- `pollinations/gemini`: **Gemini 3 Flash** (0.5/3.0 Pollen/M) +- `pollinations/gemini-large`: **Gemini 3 Pro** (2.0/12.0 Pollen/M) +- `pollinations/gemini-fast`: **Gemini 2.5 Flash Lite** (0.1/0.4 Pollen/M) +- `pollinations/qwen-coder`: **Qwen3 Coder 30B** (0.06/0.22 Pollen/M) +- `pollinations/mistral`: **Mistral Small 3.2 24B** (0.15/0.35 Pollen/M) +- `pollinations/deepseek`: **DeepSeek V3.2** (0.57/1.68 Pollen/M) +- `pollinations/grok`: **xAI Grok 4 Fast** (0.2/0.5 Pollen/M) +- `pollinations/perplexity-fast`: **Perplexity Sonar** (1.0/1.0 Pollen/M) +- `pollinations/nova-fast`: **Amazon Nova Micro** (0.04/0.15 Pollen/M) + +*Costs are per million tokens (Input/Output). Prices subject to change.* + +## Notes + +- **API Key**: An API key is required. Get one at [enter.pollinations.ai](https://enter.pollinations.ai/). +- **Cost**: Usage consumes "Pollen" credits, each pollen is equal to 1$ (Temporarily for beta 1 pollen is 0.5$). +- **Privacy**: See [pollinations.ai](https://pollinations.ai) for privacy details. + + diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 96e4e4ae6..ea39217d3 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -285,6 +285,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { venice: "VENICE_API_KEY", mistral: "MISTRAL_API_KEY", opencode: "OPENCODE_API_KEY", + pollinations: "POLLINATIONS_API_KEY", }; const envVar = envMap[normalized]; if (!envVar) return null; diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index a176dac8a..f503ecded 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -53,6 +53,16 @@ const KIMI_CODE_DEFAULT_COST = { cacheWrite: 0, }; +const POLLINATIONS_BASE_URL = "https://gen.pollinations.ai/v1"; +const POLLINATIONS_DEFAULT_CONTEXT_WINDOW = 128000; +const POLLINATIONS_DEFAULT_MAX_TOKENS = 8192; +const POLLINATIONS_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + const QWEN_PORTAL_BASE_URL = "https://portal.qwen.ai/v1"; const QWEN_PORTAL_OAUTH_PLACEHOLDER = "qwen-oauth"; const QWEN_PORTAL_DEFAULT_CONTEXT_WINDOW = 128000; @@ -333,6 +343,186 @@ function buildQwenPortalProvider(): ProviderConfig { }; } +function buildPollinationsProvider(): ProviderConfig { + return { + baseUrl: POLLINATIONS_BASE_URL, + api: "openai-completions", + models: [ + { + id: "openai", + name: "OpenAI GPT-5 Mini", + reasoning: false, + input: ["text"], + cost: { input: 0.15, output: 0.6, cacheRead: 0.04, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "openai-large", + name: "OpenAI GPT-5.2", + reasoning: false, + input: ["text"], + cost: { input: 1.75, output: 14.0, cacheRead: 0.18, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "openai-fast", + name: "OpenAI GPT-5 Nano", + reasoning: false, + input: ["text"], + cost: { input: 0.06, output: 0.44, cacheRead: 0.01, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "claude", + name: "Anthropic Claude Sonnet 4.5", + reasoning: false, + input: ["text"], + cost: { input: 3.0, output: 15.0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "claude-large", + name: "Anthropic Claude Opus 4.5", + reasoning: false, + input: ["text"], + cost: { input: 5.0, output: 25.0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "claude-fast", + name: "Anthropic Claude Haiku 4.5", + reasoning: false, + input: ["text"], + cost: { input: 1.0, output: 5.0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "gemini", + name: "Google Gemini 3 Flash", + reasoning: false, + input: ["text"], + cost: { input: 0.5, output: 3.0, cacheRead: 0.05, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "gemini-large", + name: "Google Gemini 3 Pro", + reasoning: false, + input: ["text"], + cost: { input: 2.0, output: 12.0, cacheRead: 0.2, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "gemini-fast", + name: "Google Gemini 2.5 Flash Lite", + reasoning: false, + input: ["text"], + cost: { input: 0.1, output: 0.4, cacheRead: 0.01, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "qwen-coder", + name: "Qwen3 Coder 30B", + reasoning: false, + input: ["text"], + cost: { input: 0.06, output: 0.22, cacheRead: 0, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "mistral", + name: "Mistral Small 3.2 24B", + reasoning: false, + input: ["text"], + cost: { input: 0.15, output: 0.35, cacheRead: 0, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "deepseek", + name: "DeepSeek V3.2", + reasoning: true, + input: ["text"], + cost: { input: 0.57, output: 1.68, cacheRead: 0.29, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "grok", + name: "xAI Grok 4 Fast", + reasoning: false, + input: ["text"], + cost: { input: 0.2, output: 0.5, cacheRead: 0.2, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "perplexity-fast", + name: "Perplexity Sonar", + reasoning: false, + input: ["text"], + cost: { input: 1.0, output: 1.0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "perplexity-reasoning", + name: "Perplexity Sonar Reasoning", + reasoning: true, + input: ["text"], + cost: { input: 2.0, output: 8.0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "kimi", + name: "Moonshot Kimi K2.5", + reasoning: true, + input: ["text"], + cost: { input: 0.6, output: 3.0, cacheRead: 0.1, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "glm", + name: "Z.ai GLM-4.7", + reasoning: true, + input: ["text"], + cost: { input: 0.6, output: 2.21, cacheRead: 0.3, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "minimax", + name: "MiniMax M2.1", + reasoning: true, + input: ["text"], + cost: { input: 0.3, output: 1.2, cacheRead: 0.15, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + { + id: "nova-fast", + name: "Amazon Nova Micro", + reasoning: false, + input: ["text"], + cost: { input: 0.04, output: 0.15, cacheRead: 0, cacheWrite: 0 }, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }, + ], + }; +} + function buildSyntheticProvider(): ProviderConfig { return { baseUrl: SYNTHETIC_BASE_URL, @@ -410,6 +600,20 @@ export async function resolveImplicitProviders(params: { }; } + const pollinationsKey = + resolveEnvApiKeyVarName("pollinations") ?? + resolveApiKeyFromProfiles({ provider: "pollinations", store: authStore }); + // Always add Pollinations if explicitly asked, or if we have a profile. + // Since it's free, maybe we should add it by default? + // For now, let's follow the pattern: add if key/profile exists OR maybe if it's just enabled via config (not implemented here yet). + // But since it doesn't strictly need a key, users might just select it. + // The 'resolveImplicitProviders' logic seems to depend on keys or profiles being present. + // We can treat it like Ollama: only if configured/detected. + // If the user selects it in onboarding, they will get a profile (maybe with dummy key). + if (pollinationsKey) { + providers.pollinations = { ...buildPollinationsProvider(), apiKey: pollinationsKey }; + } + // Ollama provider - only add if explicitly configured const ollamaKey = resolveEnvApiKeyVarName("ollama") ?? diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts index 8f31635f0..95b3e711a 100644 --- a/src/cli/program/register.onboard.ts +++ b/src/cli/program/register.onboard.ts @@ -52,7 +52,7 @@ export function registerOnboardCommand(program: Command) { .option("--mode ", "Wizard mode: local|remote") .option( "--auth-choice ", - "Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip", + "Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|pollinations|skip", ) .option( "--token-provider ", @@ -75,6 +75,7 @@ export function registerOnboardCommand(program: Command) { .option("--minimax-api-key ", "MiniMax API key") .option("--synthetic-api-key ", "Synthetic API key") .option("--venice-api-key ", "Venice API key") + .option("--pollinations-api-key ", "Pollinations API key") .option("--opencode-zen-api-key ", "OpenCode Zen API key") .option("--gateway-port ", "Gateway port") .option("--gateway-bind ", "Gateway bind: loopback|tailnet|lan|auto|custom") @@ -125,6 +126,7 @@ export function registerOnboardCommand(program: Command) { minimaxApiKey: opts.minimaxApiKey as string | undefined, syntheticApiKey: opts.syntheticApiKey as string | undefined, veniceApiKey: opts.veniceApiKey as string | undefined, + pollinationsApiKey: opts.pollinationsApiKey as string | undefined, opencodeZenApiKey: opts.opencodeZenApiKey as string | undefined, gatewayPort: typeof gatewayPort === "number" && Number.isFinite(gatewayPort) diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index 6b49ff17b..692c7586d 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -20,7 +20,8 @@ export type AuthChoiceGroupId = | "minimax" | "synthetic" | "venice" - | "qwen"; + | "qwen" + | "pollinations"; export type AuthChoiceGroup = { value: AuthChoiceGroupId; @@ -59,6 +60,12 @@ const AUTH_CHOICE_GROUP_DEFS: { hint: "OAuth", choices: ["qwen-portal"], }, + { + value: "pollinations", + label: "Pollinations.ai", + hint: "API Key", + choices: ["pollinations"], + }, { value: "synthetic", label: "Synthetic", @@ -165,6 +172,11 @@ export function buildAuthChoiceOptions(params: { }); options.push({ value: "zai-api-key", label: "Z.AI (GLM 4.7) API key" }); options.push({ value: "qwen-portal", label: "Qwen OAuth" }); + options.push({ + value: "pollinations", + label: "Pollinations.ai", + hint: "Daily free tokens", + }); options.push({ value: "copilot-proxy", label: "Copilot Proxy (local)", diff --git a/src/commands/auth-choice.apply.api-providers.ts b/src/commands/auth-choice.apply.api-providers.ts index 8be02008b..747c250e6 100644 --- a/src/commands/auth-choice.apply.api-providers.ts +++ b/src/commands/auth-choice.apply.api-providers.ts @@ -21,6 +21,8 @@ import { applyOpencodeZenProviderConfig, applyOpenrouterConfig, applyOpenrouterProviderConfig, + applyPollinationsConfig, + applyPollinationsProviderConfig, applySyntheticConfig, applySyntheticProviderConfig, applyVeniceConfig, @@ -31,6 +33,7 @@ import { KIMI_CODE_MODEL_REF, MOONSHOT_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF, + POLLINATIONS_DEFAULT_MODEL_REF, SYNTHETIC_DEFAULT_MODEL_REF, VENICE_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, @@ -39,6 +42,7 @@ import { setMoonshotApiKey, setOpencodeZenApiKey, setOpenrouterApiKey, + setPollinationsApiKey, setSyntheticApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, @@ -85,6 +89,8 @@ export async function applyAuthChoiceApiProviders( authChoice = "venice-api-key"; } else if (params.opts.tokenProvider === "opencode") { authChoice = "opencode-zen"; + } else if (params.opts.tokenProvider === "pollinations") { + authChoice = "pollinations"; } } @@ -579,5 +585,63 @@ export async function applyAuthChoiceApiProviders( return { config: nextConfig, agentModelOverride }; } + if (authChoice === "pollinations") { + let hasCredential = false; + + if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "pollinations") { + await setPollinationsApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir); + hasCredential = true; + } + + if (!hasCredential) { + await params.prompter.note( + [ + "Pollinations.ai provides AI API access.", + "Get your API key at: https://pollinations.ai/", + ].join("\n"), + "Pollinations.ai", + ); + } + + const envKey = resolveEnvApiKey("pollinations"); + if (envKey) { + const useExisting = await params.prompter.confirm({ + message: `Use existing POLLINATIONS_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, + initialValue: true, + }); + if (useExisting) { + await setPollinationsApiKey(envKey.apiKey, params.agentDir); + hasCredential = true; + } + } + if (!hasCredential) { + const key = await params.prompter.text({ + message: "Enter Pollinations API key", + validate: validateApiKeyInput, + }); + await setPollinationsApiKey(normalizeApiKeyInput(String(key)), params.agentDir); + } + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "pollinations:default", + provider: "pollinations", + mode: "api_key", + }); + { + const applied = await applyDefaultModelChoice({ + config: nextConfig, + setDefaultModel: params.setDefaultModel, + defaultModel: POLLINATIONS_DEFAULT_MODEL_REF, + applyDefaultConfig: applyPollinationsConfig, + applyProviderConfig: applyPollinationsProviderConfig, + noteDefault: POLLINATIONS_DEFAULT_MODEL_REF, + noteAgentModel, + prompter: params.prompter, + }); + nextConfig = applied.config; + agentModelOverride = applied.agentModelOverride ?? agentModelOverride; + } + return { config: nextConfig, agentModelOverride }; + } + return null; } diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 921ee01d1..82c7dc72e 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -25,6 +25,10 @@ import { MOONSHOT_BASE_URL, MOONSHOT_DEFAULT_MODEL_ID, MOONSHOT_DEFAULT_MODEL_REF, + POLLINATIONS_BASE_URL, + POLLINATIONS_DEFAULT_MODEL_REF, + POLLINATIONS_MODEL_CATALOG, + buildPollinationsModelDefinition, } from "./onboard-auth.models.js"; export function applyZaiConfig(cfg: MoltbotConfig): MoltbotConfig { @@ -411,6 +415,81 @@ export function applyVeniceConfig(cfg: MoltbotConfig): MoltbotConfig { }; } +export function applyPollinationsProviderConfig(cfg: MoltbotConfig): MoltbotConfig { + const models = { ...cfg.agents?.defaults?.models }; + + // Add the default model + models[POLLINATIONS_DEFAULT_MODEL_REF] = { + ...models[POLLINATIONS_DEFAULT_MODEL_REF], + alias: models[POLLINATIONS_DEFAULT_MODEL_REF]?.alias ?? "Pollinations GPT-5 Mini", + }; + + const providers = { ...cfg.models?.providers }; + const existingProvider = providers.pollinations; + const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : []; + const pollinationsModels = Object.keys(POLLINATIONS_MODEL_CATALOG).map((id) => + buildPollinationsModelDefinition({ id }), + ); + const mergedModels = [ + ...existingModels, + ...pollinationsModels.filter( + (model) => !existingModels.some((existing) => existing.id === model.id), + ), + ]; + const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record< + string, + unknown + > as { apiKey?: string }; + // Pollinations doesn't technically require a key but we might support it if users want to pass one. + const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined; + const normalizedApiKey = resolvedApiKey?.trim(); + + providers.pollinations = { + ...existingProviderRest, + baseUrl: POLLINATIONS_BASE_URL, + api: "openai-completions", + ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}), + models: mergedModels.length > 0 ? mergedModels : pollinationsModels, + }; + + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...cfg.agents?.defaults, + models, + }, + }, + models: { + mode: cfg.models?.mode ?? "merge", + providers, + }, + }; +} + +export function applyPollinationsConfig(cfg: MoltbotConfig): MoltbotConfig { + const next = applyPollinationsProviderConfig(cfg); + const existingModel = next.agents?.defaults?.model; + return { + ...next, + agents: { + ...next.agents, + defaults: { + ...next.agents?.defaults, + model: { + ...(existingModel && "fallbacks" in (existingModel as Record) + ? { + fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks, + } + : undefined), + primary: POLLINATIONS_DEFAULT_MODEL_REF, + }, + }, + }, + }; +} + export function applyAuthProfileConfig( cfg: MoltbotConfig, params: { diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index b2fb58542..a9ec968ff 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -112,6 +112,19 @@ export async function setVeniceApiKey(key: string, agentDir?: string) { }); } +export async function setPollinationsApiKey(key: string, agentDir?: string) { + // Write to resolved agent dir so gateway finds credentials on startup. + upsertAuthProfile({ + profileId: "pollinations:default", + credential: { + type: "api_key", + provider: "pollinations", + key, + }, + agentDir: resolveAuthAgentDir(agentDir), + }); +} + export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7"; export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto"; export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5"; diff --git a/src/commands/onboard-auth.models.ts b/src/commands/onboard-auth.models.ts index de5a4edaa..087612c00 100644 --- a/src/commands/onboard-auth.models.ts +++ b/src/commands/onboard-auth.models.ts @@ -20,6 +20,12 @@ export const KIMI_CODE_MAX_TOKENS = 32768; export const KIMI_CODE_HEADERS = { "User-Agent": "KimiCLI/0.77" } as const; export const KIMI_CODE_COMPAT = { supportsDeveloperRole: false } as const; +export const POLLINATIONS_BASE_URL = "https://gen.pollinations.ai/v1"; +export const POLLINATIONS_DEFAULT_MODEL_ID = "openai"; +export const POLLINATIONS_DEFAULT_MODEL_REF = `pollinations/${POLLINATIONS_DEFAULT_MODEL_ID}`; +export const POLLINATIONS_DEFAULT_CONTEXT_WINDOW = 128000; +export const POLLINATIONS_DEFAULT_MAX_TOKENS = 8192; + // Pricing: MiniMax doesn't publish public rates. Override in models.json for accurate costs. export const MINIMAX_API_COST = { input: 15, @@ -51,6 +57,12 @@ export const KIMI_CODE_DEFAULT_COST = { cacheRead: 0, cacheWrite: 0, }; +export const POLLINATIONS_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; const MINIMAX_MODEL_CATALOG = { "MiniMax-M2.1": { name: "MiniMax M2.1", reasoning: false }, @@ -116,3 +128,26 @@ export function buildKimiCodeModelDefinition(): ModelDefinitionConfig { compat: KIMI_CODE_COMPAT, }; } + +export const POLLINATIONS_MODEL_CATALOG = { + openai: { name: "Pollinations (OpenAI Default)", reasoning: false }, + "openai-large": { name: "Pollinations Large (GPT-4o)", reasoning: false }, + "openai-fast": { name: "Pollinations Fast (GPT-4o-mini)", reasoning: false }, + "qwen-coder": { name: "Pollinations Qwen Coder", reasoning: false }, + mistral: { name: "Pollinations Mistral", reasoning: false }, +} as const; + +type PollinationsCatalogId = keyof typeof POLLINATIONS_MODEL_CATALOG; + +export function buildPollinationsModelDefinition(params: { id: string }): ModelDefinitionConfig { + const catalog = POLLINATIONS_MODEL_CATALOG[params.id as PollinationsCatalogId]; + return { + id: params.id, + name: catalog?.name ?? `Pollinations ${params.id}`, + reasoning: catalog?.reasoning ?? false, + input: ["text"], + cost: POLLINATIONS_DEFAULT_COST, + contextWindow: POLLINATIONS_DEFAULT_CONTEXT_WINDOW, + maxTokens: POLLINATIONS_DEFAULT_MAX_TOKENS, + }; +} diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index b122d89cf..6ac3f6bce 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -11,6 +11,8 @@ export { applyMoonshotProviderConfig, applyOpenrouterConfig, applyOpenrouterProviderConfig, + applyPollinationsConfig, + applyPollinationsProviderConfig, applySyntheticConfig, applySyntheticProviderConfig, applyVeniceConfig, @@ -41,6 +43,7 @@ export { setMoonshotApiKey, setOpencodeZenApiKey, setOpenrouterApiKey, + setPollinationsApiKey, setSyntheticApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, @@ -54,6 +57,7 @@ export { buildMinimaxApiModelDefinition, buildMinimaxModelDefinition, buildMoonshotModelDefinition, + buildPollinationsModelDefinition, DEFAULT_MINIMAX_BASE_URL, KIMI_CODE_BASE_URL, KIMI_CODE_MODEL_ID, @@ -64,4 +68,7 @@ export { MOONSHOT_BASE_URL, MOONSHOT_DEFAULT_MODEL_ID, MOONSHOT_DEFAULT_MODEL_REF, + POLLINATIONS_BASE_URL, + POLLINATIONS_DEFAULT_MODEL_ID, + POLLINATIONS_DEFAULT_MODEL_REF, } from "./onboard-auth.models.js"; diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index 7d952730c..8f989e1c2 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -14,6 +14,7 @@ import { applyMoonshotConfig, applyOpencodeZenConfig, applyOpenrouterConfig, + applyPollinationsConfig, applySyntheticConfig, applyVeniceConfig, applyVercelAiGatewayConfig, @@ -25,6 +26,7 @@ import { setMoonshotApiKey, setOpencodeZenApiKey, setOpenrouterApiKey, + setPollinationsApiKey, setSyntheticApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, @@ -309,6 +311,25 @@ export async function applyNonInteractiveAuthChoice(params: { return applyVeniceConfig(nextConfig); } + if (authChoice === "pollinations") { + const resolved = await resolveNonInteractiveApiKey({ + provider: "pollinations", + cfg: baseConfig, + flagValue: opts.pollinationsApiKey, + flagName: "--pollinations-api-key", + envVar: "POLLINATIONS_API_KEY", + runtime, + }); + if (!resolved) return null; + if (resolved.source !== "profile") await setPollinationsApiKey(resolved.key); + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "pollinations:default", + provider: "pollinations", + mode: "api_key", + }); + return applyPollinationsConfig(nextConfig); + } + if ( authChoice === "minimax-cloud" || authChoice === "minimax-api" || diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index aa1d9afe0..12026d69e 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -31,6 +31,7 @@ export type AuthChoice = | "github-copilot" | "copilot-proxy" | "qwen-portal" + | "pollinations" | "skip"; export type GatewayAuthChoice = "token" | "password"; export type ResetScope = "config" | "config+creds+sessions" | "full"; @@ -70,6 +71,7 @@ export type OnboardOptions = { minimaxApiKey?: string; syntheticApiKey?: string; veniceApiKey?: string; + pollinationsApiKey?: string; opencodeZenApiKey?: string; gatewayPort?: number; gatewayBind?: GatewayBind;