diff --git a/CHANGELOG.md b/CHANGELOG.md index 54b138350ef..ab69a37cd32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Docs: https://docs.openclaw.ai - Security/Browser: block non-network browser navigation protocols (including `file:`, `data:`, and `javascript:`) while preserving `about:blank`, preventing local file reads via browser tool navigation. This ships in the next npm release. Thanks @q1uf3ng for reporting. - Security/Exec: block shell startup-file env injection (`BASH_ENV`, `ENV`, `BASH_FUNC_*`, `LD_*`, `DYLD_*`) across config env ingestion, node-host inherited environment sanitization, and macOS exec host runtime to prevent pre-command execution from attacker-controlled environment variables. This ships in the next npm release. Thanks @tdjackey. - Security/Exec (Windows): canonicalize `cmd.exe /c` command text across validation, approval binding, and audit/event rendering to prevent trailing-argument approval mismatches in `system.run`. This ships in the next npm release. Thanks @tdjackey for reporting. +- Models/Kimi-Coding: add missing implicit provider template for `kimi-coding` with correct `anthropic-messages` API type and base URL, fixing 403 errors when using Kimi for Coding. (#22409) - Security/Gateway/Hooks: block `__proto__`, `constructor`, and `prototype` traversal in webhook template path resolution to prevent prototype-chain payload data leakage in `messageTemplate` rendering. (#22213) Thanks @SleuthCo. - Security/OpenClawKit/UI: prevent injected inbound user context metadata blocks from leaking into chat history in TUI, webchat, and macOS surfaces by stripping all untrusted metadata prefixes at display boundaries. (#22142) Thanks @Mellowambience, @vincentkoc. - Security/OpenClawKit/UI: strip inbound metadata blocks from user messages in TUI rendering while preserving user-authored content. (#22345) Thanks @kansodata, @vincentkoc. diff --git a/src/agents/models-config.providers.kimi-coding.test.ts b/src/agents/models-config.providers.kimi-coding.test.ts new file mode 100644 index 00000000000..ff0c010489b --- /dev/null +++ b/src/agents/models-config.providers.kimi-coding.test.ts @@ -0,0 +1,45 @@ +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { captureEnv } from "../test-utils/env.js"; +import { buildKimiCodingProvider, resolveImplicitProviders } from "./models-config.providers.js"; + +describe("kimi-coding implicit provider (#22409)", () => { + it("should include kimi-coding when KIMI_API_KEY is configured", async () => { + const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-")); + const envSnapshot = captureEnv(["KIMI_API_KEY"]); + process.env.KIMI_API_KEY = "test-key"; + + try { + const providers = await resolveImplicitProviders({ agentDir }); + expect(providers?.["kimi-coding"]).toBeDefined(); + expect(providers?.["kimi-coding"]?.api).toBe("anthropic-messages"); + expect(providers?.["kimi-coding"]?.baseUrl).toBe("https://api.kimi.com/coding/"); + } finally { + envSnapshot.restore(); + } + }); + + it("should build kimi-coding provider with anthropic-messages API", () => { + const provider = buildKimiCodingProvider(); + expect(provider.api).toBe("anthropic-messages"); + expect(provider.baseUrl).toBe("https://api.kimi.com/coding/"); + expect(provider.models).toBeDefined(); + expect(provider.models.length).toBeGreaterThan(0); + expect(provider.models[0].id).toBe("k2p5"); + }); + + it("should not include kimi-coding when no API key is configured", async () => { + const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-")); + const envSnapshot = captureEnv(["KIMI_API_KEY"]); + delete process.env.KIMI_API_KEY; + + try { + const providers = await resolveImplicitProviders({ agentDir }); + expect(providers?.["kimi-coding"]).toBeUndefined(); + } finally { + envSnapshot.restore(); + } + }); +}); diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 84b0c4303e5..e622e70ec7c 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -96,6 +96,17 @@ const MOONSHOT_DEFAULT_COST = { cacheWrite: 0, }; +const KIMI_CODING_BASE_URL = "https://api.kimi.com/coding/"; +const KIMI_CODING_DEFAULT_MODEL_ID = "k2p5"; +const KIMI_CODING_DEFAULT_CONTEXT_WINDOW = 262144; +const KIMI_CODING_DEFAULT_MAX_TOKENS = 32768; +const KIMI_CODING_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + const QWEN_PORTAL_BASE_URL = "https://portal.qwen.ai/v1"; const QWEN_PORTAL_OAUTH_PLACEHOLDER = "qwen-oauth"; const QWEN_PORTAL_DEFAULT_CONTEXT_WINDOW = 128000; @@ -483,6 +494,24 @@ function buildMoonshotProvider(): ProviderConfig { }; } +export function buildKimiCodingProvider(): ProviderConfig { + return { + baseUrl: KIMI_CODING_BASE_URL, + api: "anthropic-messages", + models: [ + { + id: KIMI_CODING_DEFAULT_MODEL_ID, + name: "Kimi for Coding", + reasoning: true, + input: ["text", "image"], + cost: KIMI_CODING_DEFAULT_COST, + contextWindow: KIMI_CODING_DEFAULT_CONTEXT_WINDOW, + maxTokens: KIMI_CODING_DEFAULT_MAX_TOKENS, + }, + ], + }; +} + function buildQwenPortalProvider(): ProviderConfig { return { baseUrl: QWEN_PORTAL_BASE_URL, @@ -687,6 +716,13 @@ export async function resolveImplicitProviders(params: { providers.moonshot = { ...buildMoonshotProvider(), apiKey: moonshotKey }; } + const kimiCodingKey = + resolveEnvApiKeyVarName("kimi-coding") ?? + resolveApiKeyFromProfiles({ provider: "kimi-coding", store: authStore }); + if (kimiCodingKey) { + providers["kimi-coding"] = { ...buildKimiCodingProvider(), apiKey: kimiCodingKey }; + } + const syntheticKey = resolveEnvApiKeyVarName("synthetic") ?? resolveApiKeyFromProfiles({ provider: "synthetic", store: authStore }); diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 6bc500cabb9..eead07996d6 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -4,6 +4,7 @@ import { HUGGINGFACE_MODEL_CATALOG, } from "../agents/huggingface-models.js"; import { + buildKimiCodingProvider, buildQianfanProvider, buildXiaomiProvider, QIANFAN_DEFAULT_MODEL_ID, @@ -61,6 +62,7 @@ import { buildXaiModelDefinition, QIANFAN_BASE_URL, QIANFAN_DEFAULT_MODEL_REF, + KIMI_CODING_MODEL_ID, KIMI_CODING_MODEL_REF, MOONSHOT_BASE_URL, MOONSHOT_CN_BASE_URL, @@ -206,19 +208,19 @@ export function applyKimiCodeProviderConfig(cfg: OpenClawConfig): OpenClawConfig const models = { ...cfg.agents?.defaults?.models }; models[KIMI_CODING_MODEL_REF] = { ...models[KIMI_CODING_MODEL_REF], - alias: models[KIMI_CODING_MODEL_REF]?.alias ?? "Kimi K2.5", + alias: models[KIMI_CODING_MODEL_REF]?.alias ?? "Kimi for Coding", }; - return { - ...cfg, - agents: { - ...cfg.agents, - defaults: { - ...cfg.agents?.defaults, - models, - }, - }, - }; + const defaultModel = buildKimiCodingProvider().models[0]; + + return applyProviderConfigWithDefaultModel(cfg, { + agentModels: models, + providerId: "kimi-coding", + api: "anthropic-messages", + baseUrl: "https://api.kimi.com/coding/", + defaultModel, + defaultModelId: KIMI_CODING_MODEL_ID, + }); } export function applyKimiCodeConfig(cfg: OpenClawConfig): OpenClawConfig {