From e7e43bd0a605430af9d80acc5283f15318e7cba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 17 Apr 2026 09:51:38 -0400 Subject: [PATCH] fix(fireworks): route Fireworks models through resolveModel everywhere MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pi-ai's getModel/getModels does not know about the Fireworks providers (fireworks, fireworks-firepass); both live under our local model-resolver because Fireworks speaks the Anthropic-compat endpoint with Bearer auth at https://api.fireworks.ai/inference, not pi-ai's built-in catalog. Three call sites were still hitting pi-ai directly and failing silently for Fireworks: 1. connector-model-policy.ts — listProviderModels() caught the pi-ai throw and returned []. The "Fire Pass (CEO runtime)" card in the Team Models onboarding step therefore showed "暂无模型 / 已启用 0/0", blocking users on Fire Pass from continuing onboarding. 2. worker/worker-runner.ts — getModel() threw mid-run when the selected connector was Fireworks, so the worker could never actually execute an agent turn against Fire Pass or the curated Fireworks model set. Now uses resolveModel() + the Fireworks-specific Bearer header from resolveExtraStreamHeaders() so streamSimple hits the correct endpoint with the correct auth. 3. agent-runtime/cost-writer.ts — cost observation silently wrote amount_usd=null for Fireworks runs because getModel() threw inside the try/catch. Now resolves via resolveModel() so the Fireworks curated contextWindow/maxTokens are visible to calculateCost(). Verification: pnpm typecheck 0 / pnpm lint 0-0 / pnpm build:verify 0-0 / pnpm test:unit 508/508. --- src/main/agent-runtime/cost-writer.ts | 5 +++-- src/main/connector-model-policy.ts | 16 +++++++++------- src/main/worker/worker-runner.ts | 15 ++++++++++++--- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/main/agent-runtime/cost-writer.ts b/src/main/agent-runtime/cost-writer.ts index 86b4a277..4acd9747 100644 --- a/src/main/agent-runtime/cost-writer.ts +++ b/src/main/agent-runtime/cost-writer.ts @@ -1,6 +1,7 @@ -import { calculateCost, getModel, type Usage } from "@mariozechner/pi-ai"; +import { calculateCost, type Usage } from "@mariozechner/pi-ai"; import type { AppDatabase } from "@main/database"; import { getConnectorDefinition } from "@main/connectors"; +import { resolveModel } from "@main/model-resolver"; export interface CostWriterDeps { db: AppDatabase; @@ -29,7 +30,7 @@ export class CostWriter { try { const definition = getConnectorDefinition(input.connectorId); if (definition.provider) { - const model = getModel(definition.provider as never, input.modelId as never); + const model = resolveModel(definition.provider, input.modelId); if (model) { const cost = calculateCost(model, input.usage); amountUsd = Number.isFinite(cost.total) ? cost.total : null; diff --git a/src/main/connector-model-policy.ts b/src/main/connector-model-policy.ts index 76d01b3d..a28c8f5e 100644 --- a/src/main/connector-model-policy.ts +++ b/src/main/connector-model-policy.ts @@ -1,6 +1,6 @@ -import { getModels } from "@mariozechner/pi-ai"; import type { ConnectorId } from "@shared/types"; import { getConnectorDefinition } from "./connectors"; +import { resolveModels } from "./model-resolver"; interface ConnectorModelPolicyDeps { getConnector: (id: ConnectorId) => { id: ConnectorId; label?: string | null; model: string | null }; @@ -26,13 +26,15 @@ function uniqueModels(models: Array<{ id: string; label: string }>) { return Array.from(byId.values()); } +/** + * Returns the models available for a given pi-ai provider id, going through + * `resolveModels` so that providers not in pi-ai's built-in catalog (currently + * the two Fireworks connectors, which serve `accounts/fireworks/routers/...` + * and curated `accounts/fireworks/models/...` ids) are represented in the UI + * model picker instead of collapsing to an empty list. + */ function listProviderModels(provider: string): Array<{ id: string; label: string }> { - try { - const models = getModels(provider as never); - return models.map((model) => ({ id: model.id, label: model.name ?? model.id })); - } catch { - return []; - } + return resolveModels(provider).map((model) => ({ id: model.id, label: model.name })); } export function getConnectorModelPolicy( diff --git a/src/main/worker/worker-runner.ts b/src/main/worker/worker-runner.ts index 06f3d262..f5cd6ee8 100644 --- a/src/main/worker/worker-runner.ts +++ b/src/main/worker/worker-runner.ts @@ -1,7 +1,8 @@ import { Agent } from "@mariozechner/pi-agent-core"; import type { AgentMessage } from "@mariozechner/pi-agent-core"; -import { getModel, streamSimple } from "@mariozechner/pi-ai"; +import { streamSimple } from "@mariozechner/pi-ai"; import { createCodingTools } from "@mariozechner/pi-coding-agent"; +import { resolveModel, resolveExtraStreamHeaders } from "../model-resolver"; import type { RunStatus } from "@shared/types"; import type { ChildMessage, RunJob } from "./worker-helpers"; import { applyEnvOverrides, buildRedactor } from "./worker-helpers"; @@ -73,8 +74,12 @@ export class WorkerRunner { const restoreEnv = applyEnvOverrides(prepared.extraEnv); try { - const model = getModel(prepared.provider as never, job.model as never); + const model = resolveModel(prepared.provider, job.model); + if (!model) { + throw new Error(`Unknown model ${job.model} for provider ${prepared.provider}.`); + } resolvedModelId = model.id; + const extraHeaders = resolveExtraStreamHeaders(prepared.provider, prepared.apiKey); const cwd = job.workspacePath ?? process.cwd(); const tools = createCodingTools(cwd) as never; @@ -87,7 +92,11 @@ export class WorkerRunner { messages: prepared.priorMessages, }, streamFn: (m, messages, options) => - streamSimple(m, messages, { ...options, apiKey: prepared.apiKey }), + streamSimple(m, messages, { + ...options, + apiKey: prepared.apiKey, + headers: { ...(options?.headers ?? {}), ...(extraHeaders ?? {}) }, + }), beforeToolCall: async (context) => { const verdict = await rpc.invoke(job.runId, "beforeToolCall", { agentId: job.agentId,