From 9d9389bc6b7a0d780a3583ff1dae32ff5d9f370d Mon Sep 17 00:00:00 2001 From: Evgeni Obuchowski Date: Sat, 13 Jun 2026 07:48:46 +0200 Subject: [PATCH] fix(fireworks): resolve catalog model params from manifest (#90326) Resolve bundled Fireworks manifest models through core's static catalog so Kimi K2.6 keeps its 262,144-token context limit and nested model compatibility metadata. Keep the existing dynamic fallback for uncataloged Fireworks IDs and align bundled Kimi reasoning metadata with existing runtime behavior. Verified with focused tests, extension/core type checks, lint/format, full build, fresh autoreview, required CI, and a live Fireworks Kimi K2.6 embedded run using a real key. Co-authored-by: Evgeni Obuchowski --- extensions/fireworks/index.test.ts | 22 ++--- extensions/fireworks/index.ts | 7 ++ extensions/fireworks/openclaw.plugin.json | 2 + extensions/fireworks/provider-catalog.ts | 10 +- .../embedded-agent-runner/model.test.ts | 94 +++++++++++++++++++ 5 files changed, 117 insertions(+), 18 deletions(-) diff --git a/extensions/fireworks/index.test.ts b/extensions/fireworks/index.test.ts index 331b8a44e14a..0d473d5386f7 100644 --- a/extensions/fireworks/index.test.ts +++ b/extensions/fireworks/index.test.ts @@ -144,19 +144,19 @@ describe("fireworks provider plugin", () => { expect(resolved?.reasoning).toBe(false); }); - it("disables reasoning metadata for Fireworks Kimi k2.6 dynamic models", async () => { + it("defers manifest catalog models to core static-catalog resolution", async () => { const provider = await registerSingleProviderPlugin(fireworksPlugin); - const resolved = provider.resolveDynamicModel?.( - createProviderDynamicModelContext({ - provider: "fireworks", - modelId: "accounts/fireworks/models/kimi-k2p6", - models: [createFireworksDefaultRuntimeModel({ reasoning: false })], - }), - ); + for (const modelId of [FIREWORKS_K2_6_MODEL_ID, FIREWORKS_DEFAULT_MODEL_ID]) { + const resolved = provider.resolveDynamicModel?.( + createProviderDynamicModelContext({ + provider: "fireworks", + modelId, + models: [createFireworksDefaultRuntimeModel({ reasoning: false })], + }), + ); - expect(resolved?.provider).toBe("fireworks"); - expect(resolved?.id).toBe("accounts/fireworks/models/kimi-k2p6"); - expect(resolved?.reasoning).toBe(false); + expect(resolved).toBeUndefined(); + } }); it("exposes off-only thinking policy for Fireworks Kimi models", async () => { diff --git a/extensions/fireworks/index.ts b/extensions/fireworks/index.ts index 9cd9480fc7a1..a1ea6bf34532 100644 --- a/extensions/fireworks/index.ts +++ b/extensions/fireworks/index.ts @@ -15,11 +15,13 @@ import { FIREWORKS_DEFAULT_CONTEXT_WINDOW, FIREWORKS_DEFAULT_MAX_TOKENS, FIREWORKS_DEFAULT_MODEL_ID, + isFireworksCatalogModelId, } from "./provider-catalog.js"; import { wrapFireworksProviderStream } from "./stream.js"; import { resolveFireworksThinkingProfile } from "./thinking-policy.js"; const PROVIDER_ID = "fireworks"; + function isFireworksGlmModelId(modelId: string): boolean { const normalized = modelId.trim().toLowerCase(); const lastSegment = normalized.split("/").pop() ?? normalized; @@ -35,6 +37,11 @@ function resolveFireworksDynamicModel(ctx: ProviderResolveDynamicModelContext) { if (!modelId) { return undefined; } + + if (isFireworksCatalogModelId(modelId)) { + return undefined; + } + const isKimiModel = isFireworksKimiModelId(modelId); const input = resolveFireworksDynamicInput(modelId); diff --git a/extensions/fireworks/openclaw.plugin.json b/extensions/fireworks/openclaw.plugin.json index 4e5eaddf30e7..02e40fdff974 100644 --- a/extensions/fireworks/openclaw.plugin.json +++ b/extensions/fireworks/openclaw.plugin.json @@ -37,6 +37,7 @@ { "id": "accounts/fireworks/models/kimi-k2p6", "name": "Kimi K2.6", + "reasoning": false, "input": ["text", "image"], "contextWindow": 262144, "maxTokens": 262144, @@ -50,6 +51,7 @@ { "id": "accounts/fireworks/routers/kimi-k2p5-turbo", "name": "Kimi K2.5 Turbo (Fire Pass)", + "reasoning": false, "input": ["text", "image"], "contextWindow": 256000, "maxTokens": 256000, diff --git a/extensions/fireworks/provider-catalog.ts b/extensions/fireworks/provider-catalog.ts index 19e12505225a..99c257787b4a 100644 --- a/extensions/fireworks/provider-catalog.ts +++ b/extensions/fireworks/provider-catalog.ts @@ -31,16 +31,12 @@ export const FIREWORKS_DEFAULT_MAX_TOKENS = FIREWORKS_DEFAULT_MODEL.maxTokens; export const FIREWORKS_K2_6_CONTEXT_WINDOW = FIREWORKS_K2_6_MODEL.contextWindow; export const FIREWORKS_K2_6_MAX_TOKENS = FIREWORKS_K2_6_MODEL.maxTokens; -function cloneFireworksCatalogModel(model: ModelDefinitionConfig): ModelDefinitionConfig { - return { - ...model, - input: [...model.input], - cost: { ...model.cost }, - }; +export function isFireworksCatalogModelId(modelId: string): boolean { + return FIREWORKS_MANIFEST_PROVIDER.models.some((model) => model.id === modelId); } export function buildFireworksCatalogModels(): ModelDefinitionConfig[] { - return FIREWORKS_MANIFEST_PROVIDER.models.map(cloneFireworksCatalogModel); + return FIREWORKS_MANIFEST_PROVIDER.models.map((model) => structuredClone(model)); } export function buildFireworksProvider(): ModelProviderConfig { diff --git a/src/agents/embedded-agent-runner/model.test.ts b/src/agents/embedded-agent-runner/model.test.ts index ec950acea266..dc940272a0f8 100644 --- a/src/agents/embedded-agent-runner/model.test.ts +++ b/src/agents/embedded-agent-runner/model.test.ts @@ -688,6 +688,100 @@ describe("resolveModel", () => { expect(discoverModels).not.toHaveBeenCalled(); }); + it("resolves a deferred Fireworks manifest id from the bundled static catalog", async () => { + resolveBundledStaticCatalogModelMock.mockReturnValueOnce({ + provider: "fireworks", + id: "accounts/fireworks/models/kimi-k2p6", + name: "Kimi K2.6", + api: "openai-completions", + baseUrl: "https://api.fireworks.ai/inference/v1", + reasoning: false, + input: ["text", "image"], + cost: { input: 0.95, output: 4, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 262144, + }); + + const result = await resolveModelAsync( + "fireworks", + "accounts/fireworks/models/kimi-k2p6", + "/tmp/agent", + undefined, + { + allowBundledStaticCatalogFallback: true, + runtimeHooks: createRuntimeHooks(), + skipAgentDiscovery: true, + }, + ); + + expectRecordFields(expectResolvedModel(result), { + provider: "fireworks", + id: "accounts/fireworks/models/kimi-k2p6", + api: "openai-completions", + baseUrl: "https://api.fireworks.ai/inference/v1", + contextWindow: 262144, + maxTokens: 262144, + }); + expect(resolveBundledStaticCatalogModelMock).toHaveBeenCalledWith( + expect.objectContaining({ + provider: "fireworks", + modelId: "accounts/fireworks/models/kimi-k2p6", + }), + ); + }); + + it("prefers user openclaw.json config over the Fireworks manifest for the same id", () => { + resolveBundledStaticCatalogModelMock.mockReturnValue({ + ...makeModel("accounts/fireworks/models/kimi-k2p6"), + provider: "fireworks", + name: "Kimi K2.6", + api: "openai-completions", + baseUrl: "https://api.fireworks.ai/inference/v1", + input: ["text", "image"], + contextWindow: 262_144, + maxTokens: 262_144, + }); + const cfg = { + models: { + providers: { + fireworks: { + api: "openai-completions", + baseUrl: "https://api.fireworks.ai/inference/v1", + models: [ + { + ...makeModel("accounts/fireworks/models/kimi-k2p6"), + name: "Kimi K2.6 (user override)", + contextWindow: 300_000, + maxTokens: 300_000, + }, + ], + }, + }, + }, + } as unknown as OpenClawConfig; + + const result = resolveModelForTest( + "fireworks", + "accounts/fireworks/models/kimi-k2p6", + "/tmp/agent", + cfg, + ); + + expectRecordFields(expectResolvedModel(result), { + provider: "fireworks", + id: "accounts/fireworks/models/kimi-k2p6", + contextWindow: 300_000, + maxTokens: 300_000, + }); + expect(resolveBundledStaticCatalogModelMock).toHaveBeenCalledWith( + expect.objectContaining({ + provider: "fireworks", + modelId: "accounts/fireworks/models/kimi-k2p6", + cfg, + }), + ); + }); + it("keeps provider dynamic metadata for runtime-preferred models", async () => { resolveBundledStaticCatalogModelMock.mockReturnValueOnce({ provider: "openai",