feat(ai): add Fireworks provider support closes #3519

This commit is contained in:
Mario Zechner
2026-04-22 01:08:57 +02:00
parent 92b08abd3b
commit 0bb0a58466
12 changed files with 400 additions and 0 deletions

View File

@@ -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))

View File

@@ -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` |

View File

@@ -468,6 +468,33 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
}
}
// 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

View File

@@ -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",

View File

@@ -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",

View File

@@ -37,6 +37,7 @@ export type KnownProvider =
| "minimax"
| "minimax-cn"
| "huggingface"
| "fireworks"
| "opencode"
| "opencode-go"
| "kimi-coding";

View File

@@ -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");
});
});

View File

@@ -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))

View File

@@ -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

View File

@@ -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` |

View File

@@ -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

View File

@@ -32,6 +32,7 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
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",