fix(ai): validated Fireworks login against control-plane catalog
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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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<typeof loginFireworks>[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<string, string> | 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/,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user