diff --git a/CHANGELOG.md b/CHANGELOG.md index f7c1fdb7f12..a511170d6d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Docs: https://docs.openclaw.ai - Cron: replay interrupted recurring jobs on the first gateway restart instead of waiting for a second restart. (#60583) Thanks @joelnishanth. - Plugins/media understanding: enable bundled Groq and Deepgram providers by default so configured transcription models work without extra plugin activation config. (#59982) Thanks @yxjsxy. - Plugins/Kimi Coding: parse tagged tool calls and keep Anthropic-native tool payloads so Kimi coding endpoints execute tools instead of echoing raw markup. (#60051, #60391) +- Tools/web_search (Kimi): when `tools.web.search.kimi.baseUrl` is unset, inherit native Moonshot chat `baseUrl` (`.ai` / `.cn`) so China console keys authenticate on the same host as chat. Fixes #44851. (#56769) Thanks @tonga54. - Plugins/marketplace: block remote marketplace symlink escapes without breaking ordinary local marketplace install paths. (#60556) Thanks @eleqtrizit. - Plugins/install: preserve unsafe override flags across linked plugin and hook-pack probes so local `--link` installs honor the documented override behavior. (#60624) Thanks @JerrettDavis. - Config/All Settings: keep the raw config view intact when sensitive fields are blank instead of corrupting or dropping the rendered snapshot. (#28214) Thanks @solodmd. diff --git a/docs/tools/kimi-search.md b/docs/tools/kimi-search.md index 6df1daf73e8..9a0ce80f2a5 100644 --- a/docs/tools/kimi-search.md +++ b/docs/tools/kimi-search.md @@ -63,6 +63,13 @@ When you choose **Kimi** during `openclaw onboard` or } ``` +If you use the China API host for chat (`models.providers.moonshot.baseUrl`: +`https://api.moonshot.cn/v1`), OpenClaw reuses that same host for Kimi +`web_search` when `tools.web.search.kimi.baseUrl` is omitted, so keys from +[platform.moonshot.cn](https://platform.moonshot.cn/) do not hit the +international endpoint by mistake (which often returns HTTP 401). Override +with `tools.web.search.kimi.baseUrl` when you need a different search base URL. + **Environment alternative:** set `KIMI_API_KEY` or `MOONSHOT_API_KEY` in the Gateway environment. For a gateway install, put it in `~/.openclaw/.env`. diff --git a/extensions/moonshot/src/kimi-web-search-provider.test.ts b/extensions/moonshot/src/kimi-web-search-provider.test.ts index 1da6f34afe6..2dfb8093cf9 100644 --- a/extensions/moonshot/src/kimi-web-search-provider.test.ts +++ b/extensions/moonshot/src/kimi-web-search-provider.test.ts @@ -1,3 +1,4 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/provider-onboard"; import { withEnv } from "openclaw/plugin-sdk/testing"; import { describe, expect, it } from "vitest"; import { __testing } from "./kimi-web-search-provider.js"; @@ -14,6 +15,40 @@ describe("kimi web search provider", () => { ); }); + it("inherits native Moonshot chat baseUrl when kimi baseUrl is unset", () => { + const cnConfig = { + models: { providers: { moonshot: { baseUrl: "https://api.moonshot.cn/v1" } } }, + } as unknown as OpenClawConfig; + const cnConfigWithTrailingSlash = { + models: { providers: { moonshot: { baseUrl: "https://api.moonshot.cn/v1/" } } }, + } as unknown as OpenClawConfig; + + expect(__testing.resolveKimiBaseUrl(undefined, cnConfig)).toBe("https://api.moonshot.cn/v1"); + expect(__testing.resolveKimiBaseUrl(undefined, cnConfigWithTrailingSlash)).toBe( + "https://api.moonshot.cn/v1", + ); + }); + + it("does not inherit non-native Moonshot baseUrl for web search", () => { + const proxyConfig = { + models: { providers: { moonshot: { baseUrl: "https://proxy.example/v1" } } }, + } as unknown as OpenClawConfig; + + expect(__testing.resolveKimiBaseUrl(undefined, proxyConfig)).toBe( + "https://api.moonshot.ai/v1", + ); + }); + + it("keeps explicit kimi baseUrl over models.providers.moonshot.baseUrl", () => { + const moonshotConfig = { + models: { providers: { moonshot: { baseUrl: "https://api.moonshot.cn/v1" } } }, + } as unknown as OpenClawConfig; + + expect( + __testing.resolveKimiBaseUrl({ baseUrl: "https://api.moonshot.ai/v1" }, moonshotConfig), + ).toBe("https://api.moonshot.ai/v1"); + }); + it("extracts unique citations from search results and tool call arguments", () => { expect( __testing.extractKimiCitations({ diff --git a/extensions/moonshot/src/kimi-web-search-provider.ts b/extensions/moonshot/src/kimi-web-search-provider.ts index d84ce7da3a8..8d9cbc39dd0 100644 --- a/extensions/moonshot/src/kimi-web-search-provider.ts +++ b/extensions/moonshot/src/kimi-web-search-provider.ts @@ -1,4 +1,5 @@ import { Type } from "@sinclair/typebox"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/provider-onboard"; import { buildSearchCacheKey, buildUnsupportedSearchFilterResponse, @@ -95,9 +96,28 @@ function resolveKimiModel(kimi?: KimiConfig): string { return model || DEFAULT_KIMI_SEARCH_MODEL; } -function resolveKimiBaseUrl(kimi?: KimiConfig): string { - const baseUrl = typeof kimi?.baseUrl === "string" ? kimi.baseUrl.trim() : ""; - return baseUrl || DEFAULT_KIMI_BASE_URL; +function trimTrailingSlashes(url: string): string { + return url.replace(/\/+$/, ""); +} + +function resolveKimiBaseUrl(kimi?: KimiConfig, openClawConfig?: OpenClawConfig): string { + const explicitBaseUrl = typeof kimi?.baseUrl === "string" ? kimi.baseUrl.trim() : ""; + if (explicitBaseUrl) { + return trimTrailingSlashes(explicitBaseUrl) || DEFAULT_KIMI_BASE_URL; + } + + const moonshotBaseUrl = openClawConfig?.models?.providers?.moonshot?.baseUrl; + if (typeof moonshotBaseUrl === "string") { + const normalizedMoonshotBaseUrl = trimTrailingSlashes(moonshotBaseUrl.trim()); + if ( + normalizedMoonshotBaseUrl && + isNativeMoonshotBaseUrl(normalizedMoonshotBaseUrl) + ) { + return normalizedMoonshotBaseUrl; + } + } + + return DEFAULT_KIMI_BASE_URL; } function extractKimiMessageText(message: KimiMessage | undefined): string | undefined { @@ -259,7 +279,8 @@ function createKimiSchema() { } function createKimiToolDefinition( - searchConfig?: SearchConfigRecord, + searchConfig: SearchConfigRecord | undefined, + openClawConfig: OpenClawConfig | undefined, ): WebSearchProviderToolDefinition { return { description: @@ -289,7 +310,7 @@ function createKimiToolDefinition( searchConfig?.maxResults ?? undefined; const model = resolveKimiModel(kimiConfig); - const baseUrl = resolveKimiBaseUrl(kimiConfig); + const baseUrl = resolveKimiBaseUrl(kimiConfig, openClawConfig); const cacheKey = buildSearchCacheKey([ "kimi", query, @@ -445,6 +466,7 @@ export function createKimiWebSearchProvider(): WebSearchProviderPlugin { "kimi", resolveProviderWebSearchPluginConfig(ctx.config, "moonshot"), ) as SearchConfigRecord | undefined, + ctx.config, ), }; }