diff --git a/.changeset/strong-ears-kick.md b/.changeset/strong-ears-kick.md new file mode 100644 index 0000000000..863e506cd2 --- /dev/null +++ b/.changeset/strong-ears-kick.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Stop sending the `prompt_cache_key` parameter to third-party OpenAI-compatible providers (NVIDIA NIM, Azure Foundry, etc.) that reject it with a 400 error. Only official OpenAI endpoints and Kimi now receive this parameter. \ No newline at end of file diff --git a/packages/agent-core/src/session/provider-manager.ts b/packages/agent-core/src/session/provider-manager.ts index 7fb313b461..03441f1715 100644 --- a/packages/agent-core/src/session/provider-manager.ts +++ b/packages/agent-core/src/session/provider-manager.ts @@ -17,6 +17,23 @@ import { } from '../config'; import { ErrorCodes, isKimiError, KimiError } from '../errors'; +/** + * Check whether `baseUrl` points to an official OpenAI API endpoint. + * `undefined` means the client defaults to OpenAI. Only official endpoints + * should receive the `prompt_cache_key` field, because strictly-validating + * OpenAI-compatible endpoints (NVIDIA, Azure Foundry, etc.) reject unknown + * parameters with HTTP 400. + */ +function isOfficialOpenAIBaseUrl(baseUrl: string | undefined): boolean { + if (baseUrl === undefined) return true; + try { + const hostname = new URL(baseUrl).hostname; + return hostname === 'api.openai.com' || hostname.endsWith('.api.openai.com'); + } catch { + return false; + } +} + export interface BearerTokenProvider { getAccessToken(options?: { readonly force?: boolean }): Promise; } @@ -327,9 +344,14 @@ function toKosongProviderConfig( offEffort, // Session affinity: route every request of this session through the // same provider-side prompt cache (the OpenAI analog of Anthropic - // `metadata.user_id` above). Undefined values are stripped at - // generate time, matching the `kimi` branch below. - generationKwargs: { prompt_cache_key: promptCacheKey }, + // `metadata.user_id` above). Only sent to official OpenAI API + // endpoints — strictly-validating OpenAI-compatible third-party + // endpoints (NVIDIA, Azure Foundry, etc.) reject unknown parameters. + ...(promptCacheKey !== undefined && isOfficialOpenAIBaseUrl( + modelBaseUrl ?? providerValue(provider.baseUrl, provider.env, 'OPENAI_BASE_URL'), + ) + ? { generationKwargs: { prompt_cache_key: promptCacheKey } } + : {}), ...defaultHeadersField({ ...envCustomHeaders, ...kimiUserAgentHeader(kimiRequestHeaders), @@ -342,7 +364,8 @@ function toKosongProviderConfig( model, baseUrl: modelBaseUrl ?? providerValue(provider.baseUrl, provider.env, 'KIMI_BASE_URL'), apiKey: providerApiKey(provider), - generationKwargs: { prompt_cache_key: promptCacheKey }, + // Kimi's API accepts prompt_cache_key on any endpoint. + ...(promptCacheKey !== undefined ? { generationKwargs: { prompt_cache_key: promptCacheKey } } : {}), ...defaultHeadersField({ ...envCustomHeaders, ...kimiRequestHeaders, @@ -372,7 +395,13 @@ function toKosongProviderConfig( offEffort, // Session affinity: same `prompt_cache_key` intent as the `openai` // branch; the Responses API accepts it as a top-level request field. - generationKwargs: { prompt_cache_key: promptCacheKey }, + // Only sent to official OpenAI API endpoints — third-party endpoints + // reject unknown parameters. + ...(promptCacheKey !== undefined && isOfficialOpenAIBaseUrl( + modelBaseUrl ?? providerValue(provider.baseUrl, provider.env, 'OPENAI_BASE_URL'), + ) + ? { generationKwargs: { prompt_cache_key: promptCacheKey } } + : {}), ...defaultHeadersField({ ...envCustomHeaders, ...kimiUserAgentHeader(kimiRequestHeaders),