Route Fireworks through pi-ai's native openai-completions API

The shim resolved Fireworks models as api="anthropic-messages" pointed
at Fireworks' /inference baseUrl. That forced pi-ai's Anthropic
provider path, which unconditionally ships Claude-proprietary beta
headers (fine-grained-tool-streaming-2025-05-14,
anthropic-dangerous-direct-browser-access, claude-code identity on the
OAuth branch). Fireworks' Anthropic-compat endpoint accepts the call
but routinely returns a stream that closes with zero tokens emitted —
pi's agent loop interprets the empty stream as a successful run with no
assistant output, runs complete "succeeded" with no tool calls, no
text, no error, and the operator sees the CEO "do nothing" on every
heartbeat.

Fireworks ships a proper OpenAI-compatible endpoint at
https://api.fireworks.ai/inference/v1. pi-ai's streamSimpleOpenAICompletions
already handles arbitrary baseUrl + Bearer auth through the OpenAI
SDK — no beta-header games, no Claude identity spoofing, no auth
header duplication. Switch the resolved Model to
api="openai-completions" with the /v1 baseUrl and let the OpenAI path
do its job. resolveExtraStreamHeaders now returns undefined for every
caller (the OpenAI SDK sets Authorization from apiKey natively).

Also correct the Fire Pass model id from the routers alias to the
canonical `accounts/fireworks/models/kimi-k2.5-turbo` so it matches
the connector-definitions default and the model selection validator
accepts it without an explicit override.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-19 20:30:18 -04:00
1 parent 0c67779012
commit fdde562d55
1 file changed
+24 -20
+24 -20
View File
@@ -3,19 +3,29 @@
* that pi-ai's catalog does not ship out of the box.
*
* Today this only covers the two Fireworks connectors (`fireworks` and
* `fireworks-firepass`), which both speak the Anthropic-compatible endpoint
* at `https://api.fireworks.ai/inference` and require Bearer auth. For every
* other provider we delegate to pi-ai's `getModel` / `getModels`.
* `fireworks-firepass`). Fireworks is not a built-in pi-ai provider, so we
* route them through pi-ai's native `openai-completions` API against
* Fireworks' OpenAI-compatible endpoint. The earlier shim pointed the
* Anthropic SDK at Fireworks' `/v1/messages` path, which forced pi-ai to
* ship Claude-proprietary beta headers (`fine-grained-tool-streaming-*`,
* `anthropic-dangerous-direct-browser-access`, etc.) — Fireworks tolerates
* them inconsistently and the resulting streams frequently close with no
* tokens emitted, producing runs that report "succeeded" but did
* absolutely nothing. The OpenAI-compatible endpoint at
* `https://api.fireworks.ai/inference/v1` does not suffer the same
* problem and is the path Fireworks documents for tool-using clients.
*/
import log from "electron-log/main.js";
import { getModel, getModels, type Api, type Model } from "@mariozechner/pi-ai";
const FIREWORKS_BASE_URL = "https://api.fireworks.ai/inference";
// Fireworks' OpenAI-compatible root. The OpenAI SDK appends
// `/chat/completions`, `/models`, etc. to this baseURL.
const FIREWORKS_OPENAI_BASE_URL = "https://api.fireworks.ai/inference/v1";
const FIREWORKS_DEFAULT_CONTEXT = 128_000;
const FIREWORKS_DEFAULT_MAX_TOKENS = 8192;
export const FIREWORKS_FIREPASS_MODEL_ID = "accounts/fireworks/routers/kimi-k2p5-turbo";
export const FIREWORKS_FIREPASS_MODEL_ID = "accounts/fireworks/models/kimi-k2.5-turbo";
export const FIREWORKS_FIREPASS_MODEL_NAME = "Kimi K2.5 Turbo (Fire Pass)";
const FIREWORKS_CURATED_MODELS: Array<{ id: string; name: string; contextWindow?: number }> = [
@@ -26,13 +36,13 @@ const FIREWORKS_CURATED_MODELS: Array<{ id: string; name: string; contextWindow?
{ id: "accounts/fireworks/models/llama-v3p3-70b-instruct", name: "Llama 3.3 70B Instruct", contextWindow: 131_072 },
];
function buildFireworksModel(modelId: string, name?: string, contextWindow?: number): Model<"anthropic-messages"> {
function buildFireworksModel(modelId: string, name?: string, contextWindow?: number): Model<"openai-completions"> {
return {
id: modelId,
name: name ?? modelId,
api: "anthropic-messages",
api: "openai-completions",
provider: "fireworks",
baseUrl: FIREWORKS_BASE_URL,
baseUrl: FIREWORKS_OPENAI_BASE_URL,
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
@@ -41,10 +51,6 @@ function buildFireworksModel(modelId: string, name?: string, contextWindow?: num
};
}
function isFireworksProvider(provider: string): boolean {
return provider === "fireworks" || provider === "fireworks-firepass";
}
export function resolveModel(provider: string, modelId: string): Model<Api> | null {
if (provider === "fireworks-firepass") {
return buildFireworksModel(FIREWORKS_FIREPASS_MODEL_ID, FIREWORKS_FIREPASS_MODEL_NAME, 262_144);
@@ -81,14 +87,12 @@ export function resolveModels(provider: string): Array<{ id: string; name: strin
* Extra HTTP headers that must be merged into the streamSimple call for the
* given provider. Returns undefined when no extra headers are required.
*
* Fireworks' Anthropic-compat endpoint authenticates via `Authorization: Bearer`,
* not `x-api-key`. pi-ai's anthropic provider only sends `x-api-key`, so we
* inject the bearer header at the call site.
* Fireworks' OpenAI-compatible endpoint authenticates via the OpenAI
* SDK's native `apiKey` constructor — it already sends
* `Authorization: Bearer <apiKey>`. No per-provider header injection is
* needed; the helper stays in place as an extension point for future
* providers that do require hand-built headers.
*/
export function resolveExtraStreamHeaders(provider: string, apiKey: string): Record<string, string> | undefined {
if (!apiKey) return undefined;
if (isFireworksProvider(provider)) {
return { Authorization: `Bearer ${apiKey}` };
}
export function resolveExtraStreamHeaders(_provider: string, _apiKey: string): Record<string, string> | undefined {
return undefined;
}