Skip to content

Commit 976e7bc

Browse files
authored
Merge branch 'main' into vincentkoc-code/fix-ollama-kimi-tool-routing
2 parents 255a739 + 86135d5 commit 976e7bc

14 files changed

+1192
-82
lines changed

CHANGELOG.md

Lines changed: 32 additions & 31 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- runner: infer canonical tool names from malformed `toolCallId` variants (e.g. `functionsread3`, `functionswrite4`) when allowlist is present, preventing `Tool not found` regressions in strict routers.

src/agents/models-config.merge.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,40 @@ describe("models-config merge helpers", () => {
6666
});
6767
});
6868

69+
it("preserves implicit provider headers when explicit config adds extra headers", () => {
70+
const merged = mergeProviderModels(
71+
{
72+
api: "anthropic-messages",
73+
headers: { "User-Agent": "claude-code/0.1.0" },
74+
models: [
75+
{
76+
id: "k2p5",
77+
name: "Kimi for Coding",
78+
input: ["text", "image"],
79+
reasoning: true,
80+
},
81+
],
82+
} as ProviderConfig,
83+
{
84+
api: "anthropic-messages",
85+
headers: { "X-Kimi-Tenant": "tenant-a" },
86+
models: [
87+
{
88+
id: "k2p5",
89+
name: "Kimi for Coding",
90+
input: ["text", "image"],
91+
reasoning: true,
92+
},
93+
],
94+
} as ProviderConfig,
95+
);
96+
97+
expect(merged.headers).toEqual({
98+
"User-Agent": "claude-code/0.1.0",
99+
"X-Kimi-Tenant": "tenant-a",
100+
});
101+
});
102+
69103
it("replaces stale baseUrl when model api surface changes", () => {
70104
const merged = mergeWithExistingProviderSecrets({
71105
nextProviders: {

src/agents/models-config.merge.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,27 @@ export function mergeProviderModels(
3939
): ProviderConfig {
4040
const implicitModels = Array.isArray(implicit.models) ? implicit.models : [];
4141
const explicitModels = Array.isArray(explicit.models) ? explicit.models : [];
42+
const implicitHeaders =
43+
implicit.headers && typeof implicit.headers === "object" && !Array.isArray(implicit.headers)
44+
? implicit.headers
45+
: undefined;
46+
const explicitHeaders =
47+
explicit.headers && typeof explicit.headers === "object" && !Array.isArray(explicit.headers)
48+
? explicit.headers
49+
: undefined;
4250
if (implicitModels.length === 0) {
43-
return { ...implicit, ...explicit };
51+
return {
52+
...implicit,
53+
...explicit,
54+
...(implicitHeaders || explicitHeaders
55+
? {
56+
headers: {
57+
...implicitHeaders,
58+
...explicitHeaders,
59+
},
60+
}
61+
: {}),
62+
};
4463
}
4564

4665
const implicitById = new Map(
@@ -93,6 +112,14 @@ export function mergeProviderModels(
93112
return {
94113
...implicit,
95114
...explicit,
115+
...(implicitHeaders || explicitHeaders
116+
? {
117+
headers: {
118+
...implicitHeaders,
119+
...explicitHeaders,
120+
},
121+
}
122+
: {}),
96123
models: mergedModels,
97124
};
98125
}

src/agents/models-config.providers.kimi-coding.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe("kimi-coding implicit provider (#22409)", () => {
2626
const provider = buildKimiCodingProvider();
2727
expect(provider.api).toBe("anthropic-messages");
2828
expect(provider.baseUrl).toBe("https://api.kimi.com/coding/");
29+
expect(provider.headers).toEqual({ "User-Agent": "claude-code/0.1.0" });
2930
expect(provider.models).toBeDefined();
3031
expect(provider.models.length).toBeGreaterThan(0);
3132
expect(provider.models[0].id).toBe("k2p5");
@@ -65,4 +66,33 @@ describe("kimi-coding implicit provider (#22409)", () => {
6566
envSnapshot.restore();
6667
}
6768
});
69+
70+
it("merges explicit kimi-coding headers on top of the built-in user agent", async () => {
71+
const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-"));
72+
const envSnapshot = captureEnv(["KIMI_API_KEY"]);
73+
process.env.KIMI_API_KEY = "test-key";
74+
75+
try {
76+
const providers = await resolveImplicitProvidersForTest({
77+
agentDir,
78+
explicitProviders: {
79+
"kimi-coding": {
80+
baseUrl: "https://api.kimi.com/coding/",
81+
api: "anthropic-messages",
82+
headers: {
83+
"User-Agent": "custom-kimi-client/1.0",
84+
"X-Kimi-Tenant": "tenant-a",
85+
},
86+
models: buildKimiCodingProvider().models,
87+
},
88+
},
89+
});
90+
expect(providers?.["kimi-coding"]?.headers).toEqual({
91+
"User-Agent": "custom-kimi-client/1.0",
92+
"X-Kimi-Tenant": "tenant-a",
93+
});
94+
} finally {
95+
envSnapshot.restore();
96+
}
97+
});
6898
});

src/agents/models-config.providers.static.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const MOONSHOT_DEFAULT_COST = {
9595
};
9696

9797
const KIMI_CODING_BASE_URL = "https://api.kimi.com/coding/";
98+
const KIMI_CODING_USER_AGENT = "claude-code/0.1.0";
9899
const KIMI_CODING_DEFAULT_MODEL_ID = "k2p5";
99100
const KIMI_CODING_DEFAULT_CONTEXT_WINDOW = 262144;
100101
const KIMI_CODING_DEFAULT_MAX_TOKENS = 32768;
@@ -308,6 +309,9 @@ export function buildKimiCodingProvider(): ProviderConfig {
308309
return {
309310
baseUrl: KIMI_CODING_BASE_URL,
310311
api: "anthropic-messages",
312+
headers: {
313+
"User-Agent": KIMI_CODING_USER_AGENT,
314+
},
311315
models: [
312316
{
313317
id: KIMI_CODING_DEFAULT_MODEL_ID,

src/agents/models-config.providers.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,12 +667,24 @@ const SIMPLE_IMPLICIT_PROVIDER_LOADERS: ImplicitProviderLoader[] = [
667667
};
668668
}),
669669
withApiKey("kimi-coding", async ({ apiKey, explicitProvider }) => {
670+
const builtInProvider = buildKimiCodingProvider();
670671
const explicitBaseUrl = explicitProvider?.baseUrl;
672+
const explicitHeaders = isRecord(explicitProvider?.headers)
673+
? (explicitProvider.headers as ProviderConfig["headers"])
674+
: undefined;
671675
return {
672-
...buildKimiCodingProvider(),
676+
...builtInProvider,
673677
...(typeof explicitBaseUrl === "string" && explicitBaseUrl.trim()
674678
? { baseUrl: explicitBaseUrl.trim() }
675679
: {}),
680+
...(explicitHeaders
681+
? {
682+
headers: {
683+
...builtInProvider.headers,
684+
...explicitHeaders,
685+
},
686+
}
687+
: {}),
676688
apiKey,
677689
};
678690
}),

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,43 @@ describe("resolveModel", () => {
915915
});
916916
});
917917

918+
it("lets provider config override registry-found kimi user agent headers", () => {
919+
mockDiscoveredModel({
920+
provider: "kimi-coding",
921+
modelId: "k2p5",
922+
templateModel: {
923+
...buildForwardCompatTemplate({
924+
id: "k2p5",
925+
name: "Kimi for Coding",
926+
provider: "kimi-coding",
927+
api: "anthropic-messages",
928+
baseUrl: "https://api.kimi.com/coding/",
929+
}),
930+
headers: { "User-Agent": "claude-code/0.1.0" },
931+
},
932+
});
933+
934+
const cfg = {
935+
models: {
936+
providers: {
937+
"kimi-coding": {
938+
headers: {
939+
"User-Agent": "custom-kimi-client/1.0",
940+
"X-Kimi-Tenant": "tenant-a",
941+
},
942+
},
943+
},
944+
},
945+
} as unknown as OpenClawConfig;
946+
947+
const result = resolveModel("kimi-coding", "k2p5", "/tmp/agent", cfg);
948+
expect(result.error).toBeUndefined();
949+
expect((result.model as unknown as { headers?: Record<string, string> }).headers).toEqual({
950+
"User-Agent": "custom-kimi-client/1.0",
951+
"X-Kimi-Tenant": "tenant-a",
952+
});
953+
});
954+
918955
it("does not override when no provider config exists", () => {
919956
mockDiscoveredModel({
920957
provider: "anthropic",

0 commit comments

Comments
 (0)