From 9cb12da08cd6f855aeadf22d49440ea08334de3c Mon Sep 17 00:00:00 2001 From: roboomp Date: Mon, 22 Jun 2026 00:15:41 +0000 Subject: [PATCH] fix(ai): validated Fireworks login against control-plane catalog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The login validator pinged `/inference/v1/models`, which Fireworks serves from the per-account deployment registry and 500s with `Error listing deployed models` for accounts without active deployments. Valid `fw_…` keys were rejected during `/login`. Switched validation to the static control-plane `List Models` API (`GET /v1/accounts/fireworks/models?filter=supports_serverless=true&pageSize=1`), the same endpoint discovery already uses. Authentication no longer depends on the caller owning any deployments. Fixes #3219 --- packages/ai/CHANGELOG.md | 4 ++ packages/ai/src/registry/fireworks.ts | 8 +++- packages/ai/test/fireworks-login.test.ts | 61 ++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 packages/ai/test/fireworks-login.test.ts diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 2eb0679d4..08e2356f7 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed `/login fireworks` rejecting valid `fw_…` keys with `Fireworks API key validation failed (500): Error listing deployed models`. The validator pinged `/inference/v1/models`, which Fireworks serves from the per-account deployment registry and 500s for accounts without active deployments. Login now hits the static control-plane `List Models` catalog (`GET /v1/accounts/fireworks/models?filter=supports_serverless=true&pageSize=1`) — the same endpoint discovery already uses — so authentication no longer depends on the caller's deployment state. ([#3219](https://github.com/can1357/oh-my-pi/issues/3219)) + ## [16.1.11] - 2026-06-21 ### Fixed diff --git a/packages/ai/src/registry/fireworks.ts b/packages/ai/src/registry/fireworks.ts index e2e73e443..a8801c732 100644 --- a/packages/ai/src/registry/fireworks.ts +++ b/packages/ai/src/registry/fireworks.ts @@ -11,7 +11,13 @@ export const loginFireworks = createApiKeyLogin({ validation: { kind: "models-endpoint", provider: "Fireworks", - modelsUrl: "https://api.fireworks.ai/inference/v1/models", + // The OpenAI-compatible inference listing (`/inference/v1/models`) enumerates + // the caller's *deployed* models and returns `500 Error listing deployed models` + // for accounts without active deployments, which rejected valid `fw_…` keys + // during `/login`. The control-plane `List Models` API hits the static + // `fireworks` serverless catalog (same endpoint discovery uses) and only + // requires the key to authenticate, not to own any deployments. + modelsUrl: "https://api.fireworks.ai/v1/accounts/fireworks/models?filter=supports_serverless%3Dtrue&pageSize=1", }, }); diff --git a/packages/ai/test/fireworks-login.test.ts b/packages/ai/test/fireworks-login.test.ts new file mode 100644 index 000000000..a24d0aa47 --- /dev/null +++ b/packages/ai/test/fireworks-login.test.ts @@ -0,0 +1,61 @@ +/** + * Regression for issue #3219. + * + * The legacy validator pinged `https://api.fireworks.ai/inference/v1/models`, + * which Fireworks serves from the per-account deployment registry and 500s + * (`Error listing deployed models`) for accounts without active deployments. + * That rejected valid `fw_…` keys during `/login`. Validation now uses the + * static control-plane `List Models` API — the same endpoint discovery hits — + * so login only fails for keys that fail to authenticate, not for accounts in + * a "no deployments" state. + */ +import { describe, expect, it } from "bun:test"; + +import { loginFireworks } from "@oh-my-pi/pi-ai/registry/fireworks"; +import type { FetchImpl } from "@oh-my-pi/pi-catalog/types"; + +const CONTROL_PLANE_HOST = "api.fireworks.ai"; +const CONTROL_PLANE_PATH = "/v1/accounts/fireworks/models"; + +function makeController(fetchImpl: FetchImpl): Parameters[0] { + return { + fetch: fetchImpl, + onPrompt: async () => "fw_TESTKEY", + onAuth: () => {}, + onProgress: () => {}, + }; +} + +describe("loginFireworks", () => { + it("validates the API key against the control-plane List Models endpoint", async () => { + let capturedUrl = ""; + let capturedAuth = ""; + const fetchImpl: FetchImpl = async (input, init) => { + capturedUrl = typeof input === "string" ? input : input.toString(); + const header = (init?.headers as Record | undefined)?.Authorization; + capturedAuth = header ?? ""; + return new Response(JSON.stringify({ models: [] }), { status: 200 }); + }; + + const key = await loginFireworks(makeController(fetchImpl)); + + expect(key).toBe("fw_TESTKEY"); + expect(capturedUrl).not.toBe(""); + const url = new URL(capturedUrl); + expect(url.host).toBe(CONTROL_PLANE_HOST); + expect(url.pathname).toBe(CONTROL_PLANE_PATH); + expect(url.searchParams.get("filter")).toBe("supports_serverless=true"); + // The inference listing — which returned 500 on the reporter's account — must NOT be hit. + expect(url.pathname).not.toBe("/inference/v1/models"); + expect(capturedAuth).toBe("Bearer fw_TESTKEY"); + }); + + it("surfaces upstream auth failures with status and body", async () => { + const fetchImpl: FetchImpl = async () => + new Response("invalid api key", { status: 401, statusText: "Unauthorized" }); + + await expect(loginFireworks(makeController(fetchImpl))).rejects.toThrow( + /Fireworks API key validation failed \(401\): invalid api key/, + ); + }); +});