Skip to content

Commit f24d47d

Browse files
committed
fix(fireworks): resolve catalog models from plugin.json via core
1 parent 301213a commit f24d47d

7 files changed

Lines changed: 130 additions & 58 deletions

File tree

extensions/fireworks/index.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -144,19 +144,19 @@ describe("fireworks provider plugin", () => {
144144
expect(resolved?.reasoning).toBe(false);
145145
});
146146

147-
it("disables reasoning metadata for Fireworks Kimi k2.6 dynamic models", async () => {
147+
it("defers manifest catalog models to core static-catalog resolution", async () => {
148148
const provider = await registerSingleProviderPlugin(fireworksPlugin);
149-
const resolved = provider.resolveDynamicModel?.(
150-
createProviderDynamicModelContext({
151-
provider: "fireworks",
152-
modelId: "accounts/fireworks/models/kimi-k2p6",
153-
models: [createFireworksDefaultRuntimeModel({ reasoning: false })],
154-
}),
155-
);
156-
157-
expect(resolved?.provider).toBe("fireworks");
158-
expect(resolved?.id).toBe("accounts/fireworks/models/kimi-k2p6");
159-
expect(resolved?.reasoning).toBe(false);
149+
for (const modelId of [FIREWORKS_K2_6_MODEL_ID, FIREWORKS_DEFAULT_MODEL_ID]) {
150+
const resolved = provider.resolveDynamicModel?.(
151+
createProviderDynamicModelContext({
152+
provider: "fireworks",
153+
modelId,
154+
models: [createFireworksDefaultRuntimeModel({ reasoning: false })],
155+
}),
156+
);
157+
158+
expect(resolved).toBeUndefined();
159+
}
160160
});
161161

162162
it("exposes off-only thinking policy for Fireworks Kimi models", async () => {

extensions/fireworks/index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,20 @@ import {
77
normalizeModelCompat,
88
OPENAI_COMPATIBLE_REPLAY_HOOKS,
99
} from "openclaw/plugin-sdk/provider-model-shared";
10-
import { isFireworksKimiModelId } from "./model-id.js";
10+
import { isFireworksGlmModelId, isFireworksKimiModelId } from "./model-id.js";
1111
import { applyFireworksConfig, FIREWORKS_DEFAULT_MODEL_REF } from "./onboard.js";
1212
import {
1313
buildFireworksProvider,
1414
FIREWORKS_BASE_URL,
1515
FIREWORKS_DEFAULT_CONTEXT_WINDOW,
1616
FIREWORKS_DEFAULT_MAX_TOKENS,
1717
FIREWORKS_DEFAULT_MODEL_ID,
18+
FIREWORKS_MANIFEST_PROVIDER,
1819
} from "./provider-catalog.js";
1920
import { wrapFireworksProviderStream } from "./stream.js";
2021
import { resolveFireworksThinkingProfile } from "./thinking-policy.js";
2122

2223
const PROVIDER_ID = "fireworks";
23-
function isFireworksGlmModelId(modelId: string): boolean {
24-
const normalized = modelId.trim().toLowerCase();
25-
const lastSegment = normalized.split("/").pop() ?? normalized;
26-
return /^glm[-_.]/.test(lastSegment);
27-
}
2824

2925
function resolveFireworksDynamicInput(modelId: string): Array<"text" | "image"> {
3026
return isFireworksGlmModelId(modelId) ? ["text"] : ["text", "image"];
@@ -35,6 +31,11 @@ function resolveFireworksDynamicModel(ctx: ProviderResolveDynamicModelContext) {
3531
if (!modelId) {
3632
return undefined;
3733
}
34+
35+
if (FIREWORKS_MANIFEST_PROVIDER.models.some((entry) => entry.id === modelId)) {
36+
return undefined;
37+
}
38+
3839
const isKimiModel = isFireworksKimiModelId(modelId);
3940
const input = resolveFireworksDynamicInput(modelId);
4041

extensions/fireworks/model-id.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
// Fireworks plugin module implements model id behavior.
2+
export function isFireworksGlmModelId(modelId: string): boolean {
3+
const normalized = modelId.trim().toLowerCase();
4+
const lastSegment = normalized.split("/").pop() ?? normalized;
5+
return /^glm[-_.]/.test(lastSegment);
6+
}
7+
28
export function isFireworksKimiModelId(modelId: string): boolean {
39
const normalized = modelId.trim().toLowerCase();
410
const lastSegment = normalized.split("/").pop() ?? normalized;

extensions/fireworks/openclaw.plugin.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
{
3838
"id": "accounts/fireworks/models/kimi-k2p6",
3939
"name": "Kimi K2.6",
40+
"reasoning": false,
4041
"input": ["text", "image"],
4142
"contextWindow": 262144,
4243
"maxTokens": 262144,
@@ -50,6 +51,7 @@
5051
{
5152
"id": "accounts/fireworks/routers/kimi-k2p5-turbo",
5253
"name": "Kimi K2.5 Turbo (Fire Pass)",
54+
"reasoning": false,
5355
"input": ["text", "image"],
5456
"contextWindow": 256000,
5557
"maxTokens": 256000,

extensions/fireworks/provider-catalog.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {
66
} from "openclaw/plugin-sdk/provider-model-shared";
77
import manifest from "./openclaw.plugin.json" with { type: "json" };
88

9-
const FIREWORKS_MANIFEST_PROVIDER = buildManifestModelProviderConfig({
9+
export const FIREWORKS_MANIFEST_PROVIDER = buildManifestModelProviderConfig({
1010
providerId: "fireworks",
1111
catalog: manifest.modelCatalog.providers.fireworks,
1212
});
@@ -31,16 +31,8 @@ export const FIREWORKS_DEFAULT_MAX_TOKENS = FIREWORKS_DEFAULT_MODEL.maxTokens;
3131
export const FIREWORKS_K2_6_CONTEXT_WINDOW = FIREWORKS_K2_6_MODEL.contextWindow;
3232
export const FIREWORKS_K2_6_MAX_TOKENS = FIREWORKS_K2_6_MODEL.maxTokens;
3333

34-
function cloneFireworksCatalogModel(model: ModelDefinitionConfig): ModelDefinitionConfig {
35-
return {
36-
...model,
37-
input: [...model.input],
38-
cost: { ...model.cost },
39-
};
40-
}
41-
4234
export function buildFireworksCatalogModels(): ModelDefinitionConfig[] {
43-
return FIREWORKS_MANIFEST_PROVIDER.models.map(cloneFireworksCatalogModel);
35+
return FIREWORKS_MANIFEST_PROVIDER.models.map((model) => structuredClone(model));
4436
}
4537

4638
export function buildFireworksProvider(): ModelProviderConfig {

extensions/fireworks/stream.test.ts

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function capturePayload(params: {
1515
}): Record<string, unknown> {
1616
let captured: Record<string, unknown> = {};
1717
const baseStreamFn: StreamFn = (_model, _context, options) => {
18-
const payload = { ...params.initialPayload };
18+
const payload: Record<string, unknown> = { ...params.initialPayload };
1919
options?.onPayload?.(payload, _model);
2020
captured = payload;
2121
return {} as ReturnType<StreamFn>;
@@ -74,39 +74,32 @@ describe("createFireworksKimiThinkingDisabledWrapper", () => {
7474
).toEqual({ thinking: { type: "disabled" } });
7575
});
7676

77-
it("strips reasoning fields when disabling Fireworks Kimi thinking", () => {
78-
const k2p5Payload = capturePayload({
79-
provider: "fireworks",
80-
api: "openai-completions",
81-
modelId: "accounts/fireworks/models/kimi-k2p5",
82-
initialPayload: {
83-
reasoning_effort: "low",
84-
reasoning: { effort: "low" },
85-
reasoningEffort: "low",
86-
},
87-
});
88-
const k2p6Payload = capturePayload({
89-
provider: "fireworks",
90-
api: "openai-completions",
91-
modelId: "accounts/fireworks/models/kimi-k2p6",
92-
initialPayload: {
93-
reasoning_effort: "low",
94-
reasoning: { effort: "low" },
95-
reasoningEffort: "low",
96-
},
97-
});
98-
99-
expect(k2p5Payload).toEqual({ thinking: { type: "disabled" } });
100-
expect(k2p6Payload).toEqual({ thinking: { type: "disabled" } });
77+
it("strips reasoning fields so dynamic Kimi models cannot re-enable visible CoT", () => {
78+
// kimi-k2p5 is not a manifest row, so its reasoning:false is not guaranteed
79+
// upstream; the wrapper must still drop any reasoning fields locally.
80+
for (const modelId of [
81+
"accounts/fireworks/models/kimi-k2p5",
82+
"accounts/fireworks/models/kimi-k2p6",
83+
]) {
84+
expect(
85+
capturePayload({
86+
provider: "fireworks",
87+
api: "openai-completions",
88+
modelId,
89+
initialPayload: {
90+
reasoning_effort: "low",
91+
reasoning: { effort: "low" },
92+
reasoningEffort: "low",
93+
},
94+
}),
95+
).toEqual({ thinking: { type: "disabled" } });
96+
}
10197
});
10298

103-
it("passes sanitized payloads to caller onPayload hooks", () => {
99+
it("passes the thinking-disabled payload to caller onPayload hooks", () => {
104100
let callbackPayload: Record<string, unknown> = {};
105101
const baseStreamFn: StreamFn = (_model, _context, options) => {
106-
const payload = {
107-
reasoning_effort: "high",
108-
reasoning: { effort: "high" },
109-
};
102+
const payload = {};
110103
options?.onPayload?.(payload, _model);
111104
return {} as ReturnType<StreamFn>;
112105
};

src/agents/embedded-agent-runner/model.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,84 @@ describe("resolveModel", () => {
633633
expect(discoverModels).not.toHaveBeenCalled();
634634
});
635635

636+
it("resolves a deferred Fireworks manifest id from the bundled static catalog", async () => {
637+
resolveBundledStaticCatalogModelMock.mockReturnValueOnce({
638+
provider: "fireworks",
639+
id: "accounts/fireworks/models/kimi-k2p6",
640+
name: "Kimi K2.6",
641+
api: "openai-completions",
642+
baseUrl: "https://api.fireworks.ai/inference/v1",
643+
reasoning: false,
644+
input: ["text", "image"],
645+
cost: { input: 0.95, output: 4, cacheRead: 0, cacheWrite: 0 },
646+
contextWindow: 262144,
647+
maxTokens: 262144,
648+
});
649+
650+
const result = await resolveModelAsync(
651+
"fireworks",
652+
"accounts/fireworks/models/kimi-k2p6",
653+
"/tmp/agent",
654+
undefined,
655+
{
656+
allowBundledStaticCatalogFallback: true,
657+
runtimeHooks: createRuntimeHooks(),
658+
skipAgentDiscovery: true,
659+
},
660+
);
661+
662+
expectRecordFields(expectResolvedModel(result), {
663+
provider: "fireworks",
664+
id: "accounts/fireworks/models/kimi-k2p6",
665+
api: "openai-completions",
666+
baseUrl: "https://api.fireworks.ai/inference/v1",
667+
contextWindow: 262144,
668+
maxTokens: 262144,
669+
});
670+
expect(resolveBundledStaticCatalogModelMock).toHaveBeenCalledWith(
671+
expect.objectContaining({
672+
provider: "fireworks",
673+
modelId: "accounts/fireworks/models/kimi-k2p6",
674+
}),
675+
);
676+
});
677+
678+
it("prefers user openclaw.json config over the Fireworks manifest for the same id", () => {
679+
const cfg = {
680+
models: {
681+
providers: {
682+
fireworks: {
683+
api: "openai-completions",
684+
baseUrl: "https://api.fireworks.ai/inference/v1",
685+
models: [
686+
{
687+
...makeModel("accounts/fireworks/models/kimi-k2p6"),
688+
name: "Kimi K2.6 (user override)",
689+
contextWindow: 300_000,
690+
maxTokens: 300_000,
691+
},
692+
],
693+
},
694+
},
695+
},
696+
} as unknown as OpenClawConfig;
697+
698+
const result = resolveModelForTest(
699+
"fireworks",
700+
"accounts/fireworks/models/kimi-k2p6",
701+
"/tmp/agent",
702+
cfg,
703+
);
704+
705+
expectRecordFields(expectResolvedModel(result), {
706+
provider: "fireworks",
707+
id: "accounts/fireworks/models/kimi-k2p6",
708+
contextWindow: 300_000,
709+
maxTokens: 300_000,
710+
});
711+
expect(resolveBundledStaticCatalogModelMock).not.toHaveBeenCalled();
712+
});
713+
636714
it("keeps provider dynamic metadata for runtime-preferred models", async () => {
637715
resolveBundledStaticCatalogModelMock.mockReturnValueOnce({
638716
provider: "openai",

0 commit comments

Comments
 (0)