From 0bb0a58466fbe390b23b0c3398f54f7be2b77ef1 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Wed, 22 Apr 2026 01:08:57 +0200 Subject: [PATCH] feat(ai): add Fireworks provider support closes #3519 --- packages/ai/CHANGELOG.md | 4 + packages/ai/README.md | 2 + packages/ai/scripts/generate-models.ts | 27 ++ packages/ai/src/env-api-keys.ts | 1 + packages/ai/src/models.generated.ts | 308 ++++++++++++++++++ packages/ai/src/types.ts | 1 + packages/ai/test/fireworks-models.test.ts | 49 +++ packages/coding-agent/CHANGELOG.md | 4 + packages/coding-agent/README.md | 1 + packages/coding-agent/docs/providers.md | 1 + packages/coding-agent/src/cli/args.ts | 1 + .../coding-agent/src/core/model-resolver.ts | 1 + 12 files changed, 400 insertions(+) create mode 100644 packages/ai/test/fireworks-models.test.ts diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 2236da53..e837cf2c 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- Added Fireworks provider support via Fireworks' Anthropic-compatible Messages API, including built-in models sourced from models.dev and `FIREWORKS_API_KEY` auth ([#3519](https://github.com/badlogic/pi-mono/issues/3519)) + ### Fixed - Hardened Anthropic streaming against malformed tool-call JSON by owning SSE parsing with defensive JSON repair, replacing the deprecated `fine-grained-tool-streaming` beta header with per-tool `eager_input_streaming`, and updating stale test model references ([#3175](https://github.com/badlogic/pi-mono/issues/3175)) diff --git a/packages/ai/README.md b/packages/ai/README.md index 735cca13..b2d6d594 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -66,6 +66,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an - **Amazon Bedrock** - **OpenCode Zen** - **OpenCode Go** +- **Fireworks** (uses Anthropic-compatible API) - **Kimi For Coding** (Moonshot AI, uses Anthropic-compatible API) - **Any OpenAI-compatible API**: Ollama, vLLM, LM Studio, etc. @@ -1025,6 +1026,7 @@ In Node.js environments, you can set environment variables to avoid passing API | Groq | `GROQ_API_KEY` | | Cerebras | `CEREBRAS_API_KEY` | | xAI | `XAI_API_KEY` | +| Fireworks | `FIREWORKS_API_KEY` | | OpenRouter | `OPENROUTER_API_KEY` | | Vercel AI Gateway | `AI_GATEWAY_API_KEY` | | zAI | `ZAI_API_KEY` | diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index 0647d46e..95cfe1b1 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -468,6 +468,33 @@ async function loadModelsDevData(): Promise[]> { } } + // Process Fireworks models + if (data["fireworks-ai"]?.models) { + for (const [modelId, model] of Object.entries(data["fireworks-ai"].models)) { + const m = model as ModelsDevModel; + if (m.tool_call !== true) continue; + + models.push({ + id: modelId, + name: m.name || modelId, + api: "anthropic-messages", + provider: "fireworks", + // Fireworks Anthropic-compatible API - SDK appends /v1/messages + baseUrl: "https://api.fireworks.ai/inference", + reasoning: m.reasoning === true, + input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"], + cost: { + input: m.cost?.input || 0, + output: m.cost?.output || 0, + cacheRead: m.cost?.cache_read || 0, + cacheWrite: m.cost?.cache_write || 0, + }, + contextWindow: m.limit?.context || 4096, + maxTokens: m.limit?.output || 4096, + }); + } + } + // Process OpenCode models (Zen and Go) // API mapping based on provider.npm field: // - @ai-sdk/openai → openai-responses diff --git a/packages/ai/src/env-api-keys.ts b/packages/ai/src/env-api-keys.ts index 95e91417..278acbe6 100644 --- a/packages/ai/src/env-api-keys.ts +++ b/packages/ai/src/env-api-keys.ts @@ -123,6 +123,7 @@ export function getEnvApiKey(provider: any): string | undefined { minimax: "MINIMAX_API_KEY", "minimax-cn": "MINIMAX_CN_API_KEY", huggingface: "HF_TOKEN", + fireworks: "FIREWORKS_API_KEY", opencode: "OPENCODE_API_KEY", "opencode-go": "OPENCODE_API_KEY", "kimi-coding": "KIMI_API_KEY", diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index 28d6ce84..389181fd 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -2698,6 +2698,314 @@ export const MODELS = { maxTokens: 40000, } satisfies Model<"openai-completions">, }, + "fireworks": { + "accounts/fireworks/models/deepseek-v3p1": { + id: "accounts/fireworks/models/deepseek-v3p1", + name: "DeepSeek V3.1", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 0.56, + output: 1.68, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 163840, + maxTokens: 163840, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/deepseek-v3p2": { + id: "accounts/fireworks/models/deepseek-v3p2", + name: "DeepSeek V3.2", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 0.56, + output: 1.68, + cacheRead: 0.28, + cacheWrite: 0, + }, + contextWindow: 160000, + maxTokens: 160000, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/glm-4p5": { + id: "accounts/fireworks/models/glm-4p5", + name: "GLM 4.5", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 0.55, + output: 2.19, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 131072, + maxTokens: 131072, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/glm-4p5-air": { + id: "accounts/fireworks/models/glm-4p5-air", + name: "GLM 4.5 Air", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 0.22, + output: 0.88, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 131072, + maxTokens: 131072, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/glm-4p7": { + id: "accounts/fireworks/models/glm-4p7", + name: "GLM 4.7", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 0.6, + output: 2.2, + cacheRead: 0.3, + cacheWrite: 0, + }, + contextWindow: 198000, + maxTokens: 198000, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/glm-5": { + id: "accounts/fireworks/models/glm-5", + name: "GLM 5", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 1, + output: 3.2, + cacheRead: 0.5, + cacheWrite: 0, + }, + contextWindow: 202752, + maxTokens: 131072, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/glm-5p1": { + id: "accounts/fireworks/models/glm-5p1", + name: "GLM 5.1", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 1.4, + output: 4.4, + cacheRead: 0.26, + cacheWrite: 0, + }, + contextWindow: 202800, + maxTokens: 131072, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/gpt-oss-120b": { + id: "accounts/fireworks/models/gpt-oss-120b", + name: "GPT OSS 120B", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 0.15, + output: 0.6, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 131072, + maxTokens: 32768, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/gpt-oss-20b": { + id: "accounts/fireworks/models/gpt-oss-20b", + name: "GPT OSS 20B", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 0.05, + output: 0.2, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 131072, + maxTokens: 32768, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/kimi-k2-instruct": { + id: "accounts/fireworks/models/kimi-k2-instruct", + name: "Kimi K2 Instruct", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: false, + input: ["text"], + cost: { + input: 1, + output: 3, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 128000, + maxTokens: 16384, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/kimi-k2-thinking": { + id: "accounts/fireworks/models/kimi-k2-thinking", + name: "Kimi K2 Thinking", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 0.6, + output: 2.5, + cacheRead: 0.3, + cacheWrite: 0, + }, + contextWindow: 256000, + maxTokens: 256000, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/kimi-k2p5": { + id: "accounts/fireworks/models/kimi-k2p5", + name: "Kimi K2.5", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0.6, + output: 3, + cacheRead: 0.1, + cacheWrite: 0, + }, + contextWindow: 256000, + maxTokens: 256000, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/kimi-k2p6": { + id: "accounts/fireworks/models/kimi-k2p6", + name: "Kimi K2.6", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0.95, + output: 4, + cacheRead: 0.16, + cacheWrite: 0, + }, + contextWindow: 262000, + maxTokens: 262000, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/minimax-m2p1": { + id: "accounts/fireworks/models/minimax-m2p1", + name: "MiniMax-M2.1", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 0.3, + output: 1.2, + cacheRead: 0.03, + cacheWrite: 0, + }, + contextWindow: 200000, + maxTokens: 200000, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/minimax-m2p5": { + id: "accounts/fireworks/models/minimax-m2p5", + name: "MiniMax-M2.5", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 0.3, + output: 1.2, + cacheRead: 0.03, + cacheWrite: 0, + }, + contextWindow: 196608, + maxTokens: 196608, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/minimax-m2p7": { + id: "accounts/fireworks/models/minimax-m2p7", + name: "MiniMax-M2.7", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text"], + cost: { + input: 0.3, + output: 1.2, + cacheRead: 0.03, + cacheWrite: 0, + }, + contextWindow: 196608, + maxTokens: 196608, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/models/qwen3p6-plus": { + id: "accounts/fireworks/models/qwen3p6-plus", + name: "Qwen 3.6 Plus", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0.5, + output: 3, + cacheRead: 0.1, + cacheWrite: 0, + }, + contextWindow: 128000, + maxTokens: 8192, + } satisfies Model<"anthropic-messages">, + "accounts/fireworks/routers/kimi-k2p5-turbo": { + id: "accounts/fireworks/routers/kimi-k2p5-turbo", + name: "Kimi K2.5 Turbo (firepass)", + api: "anthropic-messages", + provider: "fireworks", + baseUrl: "https://api.fireworks.ai/inference", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 256000, + maxTokens: 256000, + } satisfies Model<"anthropic-messages">, + }, "github-copilot": { "claude-haiku-4.5": { id: "claude-haiku-4.5", diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index cb3669eb..2ba2e71c 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -37,6 +37,7 @@ export type KnownProvider = | "minimax" | "minimax-cn" | "huggingface" + | "fireworks" | "opencode" | "opencode-go" | "kimi-coding"; diff --git a/packages/ai/test/fireworks-models.test.ts b/packages/ai/test/fireworks-models.test.ts new file mode 100644 index 00000000..04c793ef --- /dev/null +++ b/packages/ai/test/fireworks-models.test.ts @@ -0,0 +1,49 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { getEnvApiKey } from "../src/env-api-keys.js"; +import { getModel } from "../src/models.js"; + +const originalFireworksApiKey = process.env.FIREWORKS_API_KEY; + +afterEach(() => { + if (originalFireworksApiKey === undefined) { + delete process.env.FIREWORKS_API_KEY; + } else { + process.env.FIREWORKS_API_KEY = originalFireworksApiKey; + } +}); + +describe("Fireworks models", () => { + it("registers the default Kimi K2.6 model via Anthropic-compatible Messages API", () => { + const model = getModel("fireworks", "accounts/fireworks/models/kimi-k2p6"); + + expect(model).toBeDefined(); + expect(model.api).toBe("anthropic-messages"); + expect(model.provider).toBe("fireworks"); + expect(model.baseUrl).toBe("https://api.fireworks.ai/inference"); + expect(model.reasoning).toBe(true); + expect(model.input).toEqual(["text", "image"]); + expect(model.contextWindow).toBe(262000); + expect(model.maxTokens).toBe(262000); + expect(model.cost).toEqual({ + input: 0.95, + output: 4, + cacheRead: 0.16, + cacheWrite: 0, + }); + }); + + it("registers the Fire Pass turbo router model", () => { + const model = getModel("fireworks", "accounts/fireworks/routers/kimi-k2p5-turbo"); + + expect(model).toBeDefined(); + expect(model.api).toBe("anthropic-messages"); + expect(model.baseUrl).toBe("https://api.fireworks.ai/inference"); + expect(model.input).toEqual(["text", "image"]); + }); + + it("resolves FIREWORKS_API_KEY from the environment", () => { + process.env.FIREWORKS_API_KEY = "test-fireworks-key"; + + expect(getEnvApiKey("fireworks")).toBe("test-fireworks-key"); + }); +}); diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 2aa38267..72cc09c2 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- Added built-in Fireworks provider support, including `FIREWORKS_API_KEY` setup/docs and the default Fireworks model `accounts/fireworks/models/kimi-k2p6` ([#3519](https://github.com/badlogic/pi-mono/issues/3519)) + ### Fixed - Added configurable interactive inline image width via `terminal.imageWidthCells` and `/settings`, so tool-output images are no longer hard-capped to 60 terminal cells ([#3508](https://github.com/badlogic/pi-mono/issues/3508)) diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index a2f2045d..88e38316 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -120,6 +120,7 @@ For each built-in provider, pi maintains a list of tool-capable models, updated - OpenCode Zen - OpenCode Go - Hugging Face +- Fireworks - Kimi For Coding - MiniMax diff --git a/packages/coding-agent/docs/providers.md b/packages/coding-agent/docs/providers.md index 888cf213..bcf4303f 100644 --- a/packages/coding-agent/docs/providers.md +++ b/packages/coding-agent/docs/providers.md @@ -67,6 +67,7 @@ pi | OpenCode Zen | `OPENCODE_API_KEY` | `opencode` | | OpenCode Go | `OPENCODE_API_KEY` | `opencode-go` | | Hugging Face | `HF_TOKEN` | `huggingface` | +| Fireworks | `FIREWORKS_API_KEY` | `fireworks` | | Kimi For Coding | `KIMI_API_KEY` | `kimi-coding` | | MiniMax | `MINIMAX_API_KEY` | `minimax` | | MiniMax (China) | `MINIMAX_CN_API_KEY` | `minimax-cn` | diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index a541c199..70f07ca8 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -303,6 +303,7 @@ ${chalk.bold("Environment Variables:")} GROQ_API_KEY - Groq API key CEREBRAS_API_KEY - Cerebras API key XAI_API_KEY - xAI Grok API key + FIREWORKS_API_KEY - Fireworks API key OPENROUTER_API_KEY - OpenRouter API key AI_GATEWAY_API_KEY - Vercel AI Gateway API key ZAI_API_KEY - ZAI API key diff --git a/packages/coding-agent/src/core/model-resolver.ts b/packages/coding-agent/src/core/model-resolver.ts index 0dad6e47..8da22f54 100644 --- a/packages/coding-agent/src/core/model-resolver.ts +++ b/packages/coding-agent/src/core/model-resolver.ts @@ -32,6 +32,7 @@ export const defaultModelPerProvider: Record = { minimax: "MiniMax-M2.7", "minimax-cn": "MiniMax-M2.7", huggingface: "moonshotai/Kimi-K2.5", + fireworks: "accounts/fireworks/models/kimi-k2p6", opencode: "claude-opus-4-6", "opencode-go": "kimi-k2.5", "kimi-coding": "kimi-for-coding",