Moonshot: reuse native base URL for Kimi web search

This commit is contained in:
Gaston Rodriguez 2026-03-29 00:38:34 -03:00 committed by Peter Steinberger
parent e5d03f734a
commit b6b1d5dd6c
4 changed files with 70 additions and 5 deletions

View File

@ -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.

View File

@ -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`.

View File

@ -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({

View File

@ -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,
),
};
}