From fdde562d553a7e2ced6468053d8e290c1642037d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sun, 19 Apr 2026 20:30:18 -0400 Subject: [PATCH] Route Fireworks through pi-ai's native openai-completions API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/main/model-resolver.ts | 44 +++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/main/model-resolver.ts b/src/main/model-resolver.ts index 3aea7494..89dc45ab 100644 --- a/src/main/model-resolver.ts +++ b/src/main/model-resolver.ts @@ -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 | 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 `. 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 | undefined { - if (!apiKey) return undefined; - if (isFireworksProvider(provider)) { - return { Authorization: `Bearer ${apiKey}` }; - } +export function resolveExtraStreamHeaders(_provider: string, _apiKey: string): Record | undefined { return undefined; }