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",