From bb909d360b37715c32c7aab1997a3f4c6e1e91e9 Mon Sep 17 00:00:00 2001 From: KO Ho Tin Date: Sun, 26 Jul 2026 17:01:24 +0800 Subject: [PATCH 1/5] fix(agent-core): only send prompt_cache_key to the official OpenAI endpoint Since 0.29.0 every OpenAI-compatible provider received the session prompt_cache_key in the request body. Strictly-validating endpoints reject the unknown parameter with a 400, breaking custom providers. Gate the field on the effective base URL targeting api.openai.com (or being unset, which the client defaults there), in both the v1 provider config resolution and the v2 OpenAI chat-completions/responses bases. Vendors that support the field (e.g. Kimi) keep encoding it through their own branch or cacheKey trait hook. Fixes #2166 --- .../prompt-cache-key-official-openai-only.md | 5 ++ .../provider/bases/openai/openai-common.ts | 17 +++++ .../provider/bases/openai/openai-legacy.ts | 10 ++- .../provider/bases/openai/openai-responses.ts | 6 +- .../test/kosong/provider/composition.test.ts | 30 +++++++++ .../src/session/provider-manager.ts | 49 ++++++++++---- .../test/harness/runtime-provider.test.ts | 65 +++++++++++++++++++ 7 files changed, 169 insertions(+), 13 deletions(-) create mode 100644 .changeset/prompt-cache-key-official-openai-only.md diff --git a/.changeset/prompt-cache-key-official-openai-only.md b/.changeset/prompt-cache-key-official-openai-only.md new file mode 100644 index 0000000000..0b0f429fb2 --- /dev/null +++ b/.changeset/prompt-cache-key-official-openai-only.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Stop sending the OpenAI prompt cache key to custom OpenAI-compatible endpoints, which reject the unknown field with a 400 error; the key is still sent to the official OpenAI API. diff --git a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-common.ts b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-common.ts index ac3ddeea1f..e14146ca39 100644 --- a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-common.ts +++ b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-common.ts @@ -281,3 +281,20 @@ export function isOpenAIReasoningModel(normalizedModelName: string): boolean { export function hasModelPrefix(modelName: string, prefixes: readonly string[]): boolean { return prefixes.some((prefix) => modelName.startsWith(prefix)); } + +/** + * `prompt_cache_key` is an official-OpenAI request field: strictly-validating + * OpenAI-compatible endpoints reject unknown parameters with a 400, so the + * bases only fall back to it when the effective base URL targets + * `api.openai.com` (or is unset, which the client defaults there). + */ +export function isOfficialOpenAIBaseUrl(baseUrl: string | undefined): boolean { + if (baseUrl === undefined) { + return true; + } + try { + return new URL(baseUrl).hostname === 'api.openai.com'; + } catch { + return false; + } +} diff --git a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.ts b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.ts index 5d4bd99e33..54f8a40c48 100644 --- a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.ts +++ b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.ts @@ -60,6 +60,7 @@ import { extractUsage, hasModelPrefix, isFunctionToolCall, + isOfficialOpenAIBaseUrl, isOpenAIReasoningModel, normalizeOpenAIFinishReason, OPENAI_REASONING_CAPABILITY, @@ -665,7 +666,14 @@ export class OpenAILegacyChatProvider implements ChatProvider { if (options?.cacheKey !== undefined) { const hooked = this._hooks?.cacheKey?.(options.cacheKey); - kwargs = { ...kwargs, ...(hooked ?? { prompt_cache_key: options.cacheKey }) }; + if (hooked !== undefined) { + kwargs = { ...kwargs, ...hooked }; + } else if (isOfficialOpenAIBaseUrl(this._baseUrl)) { + // The bare `prompt_cache_key` fallback is official-OpenAI only: + // strictly-validating compatible endpoints 400 on unknown fields + // (#2166); vendors that support it encode it via their cacheKey hook. + kwargs = { ...kwargs, prompt_cache_key: options.cacheKey }; + } } if (options?.sampling?.temperature !== undefined) { diff --git a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.ts b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.ts index 89e3219de8..1cd884644d 100644 --- a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.ts +++ b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.ts @@ -49,6 +49,7 @@ import { hasModelPrefix, isMediaPart, isOpenAIInsufficientQuotaCode, + isOfficialOpenAIBaseUrl, isOpenAIReasoningModel, OPENAI_REASONING_CAPABILITY, OPENAI_VISION_TOOL_CAPABILITY, @@ -1094,7 +1095,10 @@ export class OpenAIResponsesChatProvider implements ChatProvider { let kwargs: Record = { ...this._generationKwargs }; - if (options?.cacheKey !== undefined) { + // Per-turn intent overlays in the fixed contract order. The + // `prompt_cache_key` overlay is official-OpenAI only: strictly-validating + // compatible endpoints reject unknown fields with a 400 (#2166). + if (options?.cacheKey !== undefined && isOfficialOpenAIBaseUrl(this._baseUrl)) { kwargs = { ...kwargs, prompt_cache_key: options.cacheKey }; } if (options?.sampling?.temperature !== undefined) { diff --git a/packages/agent-core-v2/test/kosong/provider/composition.test.ts b/packages/agent-core-v2/test/kosong/provider/composition.test.ts index 5204c96bc4..2c71cce7c4 100644 --- a/packages/agent-core-v2/test/kosong/provider/composition.test.ts +++ b/packages/agent-core-v2/test/kosong/provider/composition.test.ts @@ -684,6 +684,36 @@ describe('per-turn intent wire encoding (behavior probes)', () => { expect(body['prompt_cache_key']).toBe('session-probe'); }); + it('omits prompt_cache_key on OpenAI-compatible custom endpoints (chat completions + responses)', async () => { + // Strictly-validating OpenAI-compatible servers reject the unknown + // `prompt_cache_key` field with a 400, so the bare fallback stays + // official-endpoint only (#2166). + const legacy = registry.createChatProvider({ + protocol: 'openai', + modelName: 'gpt-4o', + apiKey: 'sk-probe', + baseUrl: 'https://openai-compatible.example.test/v1', + }); + const legacyBody = await captureOpenAIBody(legacy, { cacheKey: 'session-probe' }); + expect(legacyBody).not.toHaveProperty('prompt_cache_key'); + + const responses = new OpenAIResponsesChatProvider({ + model: 'gpt-4.1', + apiKey: 'sk-probe', + baseUrl: 'https://openai-compatible.example.test/v1', + }); + const responsesBody = await captureResponsesBody(responses, { cacheKey: 'session-probe' }); + expect(responsesBody).not.toHaveProperty('prompt_cache_key'); + }); + + it('keeps prompt_cache_key on the official OpenAI Responses endpoint', async () => { + const provider = new OpenAIResponsesChatProvider({ model: 'gpt-4.1', apiKey: 'sk-probe' }); + + const body = await captureResponsesBody(provider, { cacheKey: 'session-probe' }); + + expect(body['prompt_cache_key']).toBe('session-probe'); + }); + it('encodes cacheKey on Anthropic as metadata.user_id', async () => { const provider = registry.createChatProvider({ protocol: 'anthropic', diff --git a/packages/agent-core/src/session/provider-manager.ts b/packages/agent-core/src/session/provider-manager.ts index 7fb313b461..fc62c909ed 100644 --- a/packages/agent-core/src/session/provider-manager.ts +++ b/packages/agent-core/src/session/provider-manager.ts @@ -314,28 +314,34 @@ function toKosongProviderConfig( ), }; } - case 'openai': + case 'openai': { + // A per-model endpoint (catalog gateway override) wins over the + // provider-level base URL, same as the Anthropic branch. + const baseUrl = + modelBaseUrl ?? providerValue(provider.baseUrl, provider.env, 'OPENAI_BASE_URL'); return { type: 'openai', model, - // A per-model endpoint (catalog gateway override) wins over the - // provider-level base URL, same as the Anthropic branch. - baseUrl: - modelBaseUrl ?? providerValue(provider.baseUrl, provider.env, 'OPENAI_BASE_URL'), + baseUrl, apiKey: providerApiKey(provider), reasoningKey, 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 }, + // generate time, matching the `kimi` branch below. Official-endpoint + // only: strictly-validating OpenAI-compatible servers reject the + // unknown `prompt_cache_key` field with a 400 (#2166). + generationKwargs: { + prompt_cache_key: isOfficialOpenAIBaseUrl(baseUrl) ? promptCacheKey : undefined, + }, ...defaultHeadersField({ ...envCustomHeaders, ...kimiUserAgentHeader(kimiRequestHeaders), ...provider.customHeaders, }), }; + } case 'kimi': return { type: 'kimi', @@ -362,23 +368,27 @@ function toKosongProviderConfig( ...provider.customHeaders, }), }; - case 'openai_responses': + case 'openai_responses': { + const baseUrl = + modelBaseUrl ?? providerValue(provider.baseUrl, provider.env, 'OPENAI_BASE_URL'); return { type: 'openai_responses', model, - baseUrl: - modelBaseUrl ?? providerValue(provider.baseUrl, provider.env, 'OPENAI_BASE_URL'), + baseUrl, apiKey: providerApiKey(provider), 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 }, + generationKwargs: { + prompt_cache_key: isOfficialOpenAIBaseUrl(baseUrl) ? promptCacheKey : undefined, + }, ...defaultHeadersField({ ...envCustomHeaders, ...kimiUserAgentHeader(kimiRequestHeaders), ...provider.customHeaders, }), }; + } case 'vertexai': { // Resolve the effective endpoint once (config `base_url` or the // GOOGLE_VERTEX_BASE_URL env fallback) and use it for BOTH forwarding and @@ -478,6 +488,23 @@ function vertexAILocation( return envValue(provider.env, 'GOOGLE_CLOUD_LOCATION') ?? locationFromVertexAIBaseUrl(baseUrl); } +/** + * `prompt_cache_key` is an official-OpenAI request field: strictly-validating + * OpenAI-compatible endpoints reject unknown parameters with a 400, so session + * cache affinity is only requested when the effective base URL targets + * `api.openai.com` (or is unset, which the transport defaults there). + */ +function isOfficialOpenAIBaseUrl(baseUrl: string | undefined): boolean { + if (baseUrl === undefined) { + return true; + } + try { + return new URL(baseUrl).hostname === 'api.openai.com'; + } catch { + return false; + } +} + function providerValue( configured: string | undefined, env: Record | undefined, diff --git a/packages/agent-core/test/harness/runtime-provider.test.ts b/packages/agent-core/test/harness/runtime-provider.test.ts index 96afbf4f15..838ce7f078 100644 --- a/packages/agent-core/test/harness/runtime-provider.test.ts +++ b/packages/agent-core/test/harness/runtime-provider.test.ts @@ -969,6 +969,71 @@ describe('ProviderManager prompt cache key', () => { } }); + it('omits the prompt cache key for OpenAI providers on custom base URLs', () => { + for (const type of ['openai', 'openai_responses'] as const) { + const manager = new ProviderManager({ + promptCacheKey: 'session-test', + config: { + defaultModel: 'gpt-alias', + providers: { + openai: { + type, + apiKey: 'sk-compat', + baseUrl: 'https://openai-compatible.example.test/v1', + }, + }, + models: { + 'gpt-alias': { + provider: 'openai', + model: 'gpt-runtime', + maxContextSize: 200000, + }, + }, + }, + }); + const resolved = manager.resolveProviderConfig('gpt-alias'); + + // Strictly-validating OpenAI-compatible endpoints reject the unknown + // `prompt_cache_key` field with a 400, so it must stay official-only. + const kwargs = (resolved.provider as { generationKwargs?: Record }) + .generationKwargs; + expect(kwargs?.['prompt_cache_key']).toBeUndefined(); + } + }); + + it('keeps the prompt cache key when the base URL targets the official OpenAI API', () => { + for (const type of ['openai', 'openai_responses'] as const) { + const manager = new ProviderManager({ + promptCacheKey: 'session-test', + config: { + defaultModel: 'gpt-alias', + providers: { + openai: { + type, + apiKey: 'sk-openai', + baseUrl: 'https://api.openai.com/v1', + }, + }, + models: { + 'gpt-alias': { + provider: 'openai', + model: 'gpt-runtime', + maxContextSize: 200000, + }, + }, + }, + }); + const resolved = manager.resolveProviderConfig('gpt-alias'); + + expect(resolved.provider).toMatchObject({ + type, + generationKwargs: { + prompt_cache_key: 'session-test', + }, + }); + } + }); + it('reads the current config when constructed with a function', () => { let sharedConfig: KimiConfig = { providers: {} }; const manager = new ProviderManager({ From 547e56fcc5e1ef5c65fde01277a193cee2f04362 Mon Sep 17 00:00:00 2001 From: KO Ho Tin Date: Sun, 26 Jul 2026 17:38:59 +0800 Subject: [PATCH 2/5] fix(agent-core): treat regional OpenAI endpoints as official for prompt cache affinity Data-residency endpoints (eu.api.openai.com, us.api.openai.com) are official OpenAI hosts and accept prompt_cache_key; match any *.api.openai.com hostname instead of the apex only. --- .../src/kosong/provider/bases/openai/openai-common.ts | 6 ++++-- packages/agent-core/src/session/provider-manager.ts | 6 ++++-- packages/agent-core/test/harness/runtime-provider.test.ts | 8 ++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-common.ts b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-common.ts index e14146ca39..94a2649910 100644 --- a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-common.ts +++ b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-common.ts @@ -286,14 +286,16 @@ export function hasModelPrefix(modelName: string, prefixes: readonly string[]): * `prompt_cache_key` is an official-OpenAI request field: strictly-validating * OpenAI-compatible endpoints reject unknown parameters with a 400, so the * bases only fall back to it when the effective base URL targets - * `api.openai.com` (or is unset, which the client defaults there). + * `api.openai.com` or a regional variant like `eu.api.openai.com` (or is + * unset, which the client defaults there). */ export function isOfficialOpenAIBaseUrl(baseUrl: string | undefined): boolean { if (baseUrl === undefined) { return true; } try { - return new URL(baseUrl).hostname === 'api.openai.com'; + const hostname = new URL(baseUrl).hostname; + return hostname === 'api.openai.com' || hostname.endsWith('.api.openai.com'); } catch { return false; } diff --git a/packages/agent-core/src/session/provider-manager.ts b/packages/agent-core/src/session/provider-manager.ts index fc62c909ed..6dc2c376ac 100644 --- a/packages/agent-core/src/session/provider-manager.ts +++ b/packages/agent-core/src/session/provider-manager.ts @@ -492,14 +492,16 @@ function vertexAILocation( * `prompt_cache_key` is an official-OpenAI request field: strictly-validating * OpenAI-compatible endpoints reject unknown parameters with a 400, so session * cache affinity is only requested when the effective base URL targets - * `api.openai.com` (or is unset, which the transport defaults there). + * `api.openai.com` or a regional variant like `eu.api.openai.com` (or is + * unset, which the transport defaults there). */ function isOfficialOpenAIBaseUrl(baseUrl: string | undefined): boolean { if (baseUrl === undefined) { return true; } try { - return new URL(baseUrl).hostname === 'api.openai.com'; + const hostname = new URL(baseUrl).hostname; + return hostname === 'api.openai.com' || hostname.endsWith('.api.openai.com'); } catch { return false; } diff --git a/packages/agent-core/test/harness/runtime-provider.test.ts b/packages/agent-core/test/harness/runtime-provider.test.ts index 838ce7f078..6fe038ec89 100644 --- a/packages/agent-core/test/harness/runtime-provider.test.ts +++ b/packages/agent-core/test/harness/runtime-provider.test.ts @@ -1002,7 +1002,11 @@ describe('ProviderManager prompt cache key', () => { }); it('keeps the prompt cache key when the base URL targets the official OpenAI API', () => { - for (const type of ['openai', 'openai_responses'] as const) { + for (const [type, baseUrl] of [ + ['openai', 'https://api.openai.com/v1'], + ['openai_responses', 'https://api.openai.com/v1'], + ['openai', 'https://eu.api.openai.com/v1'], + ] as const) { const manager = new ProviderManager({ promptCacheKey: 'session-test', config: { @@ -1011,7 +1015,7 @@ describe('ProviderManager prompt cache key', () => { openai: { type, apiKey: 'sk-openai', - baseUrl: 'https://api.openai.com/v1', + baseUrl, }, }, models: { From dd5d5007d8f41ba90794cea2537332eff73081fe Mon Sep 17 00:00:00 2001 From: KO Ho Tin Date: Sun, 26 Jul 2026 17:51:55 +0800 Subject: [PATCH 3/5] docs(agent-core-v2): keep endpoint-gate commentary in module headers The package convention allows comments only in the top-of-file block; move the prompt_cache_key gating notes there. --- .../provider/bases/openai/openai-common.ts | 19 +++++++++---------- .../provider/bases/openai/openai-legacy.ts | 6 +++--- .../provider/bases/openai/openai-responses.ts | 8 +++----- .../test/kosong/provider/composition.test.ts | 3 --- 4 files changed, 15 insertions(+), 21 deletions(-) diff --git a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-common.ts b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-common.ts index 94a2649910..8a1a19a036 100644 --- a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-common.ts +++ b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-common.ts @@ -1,9 +1,15 @@ /** * `kosong/provider` domain — shared OpenAI-family wire mechanics. * - * The shared pieces: content-part and tool conversion, usage extraction, - * finish-reason normalization, the capability constants, and the error - * converter. + * Everything the Chat Completions and Responses bases share: content-part and + * tool conversion, usage extraction, finish-reason normalization, the + * capability constants, the error converter, and the official-endpoint gate + * for the OpenAI-only `prompt_cache_key` request field + * (`isOfficialOpenAIBaseUrl`): the bases only fall back to that field when + * the effective base URL targets `api.openai.com` or a regional + * `*.api.openai.com` variant (or is unset, which the client defaults there), + * because strictly-validating OpenAI-compatible endpoints reject unknown + * parameters with a 400. * * `convertOpenAIError`'s FIRST line is the contract's `throwIfAbortError` * guard: a user cancellation (SDK `APIUserAbortError`, bare `AbortError`, the @@ -282,13 +288,6 @@ export function hasModelPrefix(modelName: string, prefixes: readonly string[]): return prefixes.some((prefix) => modelName.startsWith(prefix)); } -/** - * `prompt_cache_key` is an official-OpenAI request field: strictly-validating - * OpenAI-compatible endpoints reject unknown parameters with a 400, so the - * bases only fall back to it when the effective base URL targets - * `api.openai.com` or a regional variant like `eu.api.openai.com` (or is - * unset, which the client defaults there). - */ export function isOfficialOpenAIBaseUrl(baseUrl: string | undefined): boolean { if (baseUrl === undefined) { return true; diff --git a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.ts b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.ts index 54f8a40c48..3407f45e3f 100644 --- a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.ts +++ b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.ts @@ -9,6 +9,9 @@ * * Per-turn intent assembly (`_resolveRequestKwargs`) applies overlays in the * fixed contract order: cacheKey → sampling → thinking → maxCompletionTokens. + * The bare `prompt_cache_key` cacheKey fallback is gated by + * `isOfficialOpenAIBaseUrl`; vendors that support the field on other hosts + * encode it through their `cacheKey` hook. * The context-window clamp on the completion budget (floor 1) runs BEFORE any * hook and cannot be skipped; the 128k ceiling clamp can be taken over by the * `withMaxCompletionTokens` hook. @@ -669,9 +672,6 @@ export class OpenAILegacyChatProvider implements ChatProvider { if (hooked !== undefined) { kwargs = { ...kwargs, ...hooked }; } else if (isOfficialOpenAIBaseUrl(this._baseUrl)) { - // The bare `prompt_cache_key` fallback is official-OpenAI only: - // strictly-validating compatible endpoints 400 on unknown fields - // (#2166); vendors that support it encode it via their cacheKey hook. kwargs = { ...kwargs, prompt_cache_key: options.cacheKey }; } } diff --git a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.ts b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.ts index 1cd884644d..cbd9950150 100644 --- a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.ts +++ b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.ts @@ -3,8 +3,8 @@ * * Speaks the Responses wire format: `input` items, `instructions`, * `reasoning` blocks with encrypted content, and the native - * `prompt_cache_key` field (a cache key is encoded directly — no hook - * needed). Per-turn intents are encoded inline in the fixed contract order; + * needed — and only for official `api.openai.com` hosts, via + * `isOfficialOpenAIBaseUrl`). Per-turn intents are encoded inline in the fixed contract order; * the base's only hook surface is the trait-composed `convertError` option, * consulted with each raw failure exactly once — the SDK error on HTTP * paths, the raw event on in-stream error paths — before the base's own @@ -1095,9 +1095,7 @@ export class OpenAIResponsesChatProvider implements ChatProvider { let kwargs: Record = { ...this._generationKwargs }; - // Per-turn intent overlays in the fixed contract order. The - // `prompt_cache_key` overlay is official-OpenAI only: strictly-validating - // compatible endpoints reject unknown fields with a 400 (#2166). + // Per-turn intent overlays in the fixed contract order. if (options?.cacheKey !== undefined && isOfficialOpenAIBaseUrl(this._baseUrl)) { kwargs = { ...kwargs, prompt_cache_key: options.cacheKey }; } diff --git a/packages/agent-core-v2/test/kosong/provider/composition.test.ts b/packages/agent-core-v2/test/kosong/provider/composition.test.ts index 2c71cce7c4..14419754ed 100644 --- a/packages/agent-core-v2/test/kosong/provider/composition.test.ts +++ b/packages/agent-core-v2/test/kosong/provider/composition.test.ts @@ -685,9 +685,6 @@ describe('per-turn intent wire encoding (behavior probes)', () => { }); it('omits prompt_cache_key on OpenAI-compatible custom endpoints (chat completions + responses)', async () => { - // Strictly-validating OpenAI-compatible servers reject the unknown - // `prompt_cache_key` field with a 400, so the bare fallback stays - // official-endpoint only (#2166). const legacy = registry.createChatProvider({ protocol: 'openai', modelName: 'gpt-4o', From 6c9fbe68da7bcd056a5d6b236ed69a22be954723 Mon Sep 17 00:00:00 2001 From: mb Date: Wed, 29 Jul 2026 14:39:52 +0200 Subject: [PATCH 4/5] chore: ignore custom plugins in plugins/ directory --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c6f2a483e4..f32ac39235 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,9 @@ coverage/ .claude .conductor .kimi-stash-dir -plugins/cdn/ +plugins/* +!plugins/official/ +!plugins/marketplace.json .worktrees/ .kimi-code/local.toml .kimi-sandbox/ From 9c2861d3e58f1c305b67b21d57bf8256ec939ee5 Mon Sep 17 00:00:00 2001 From: mb Date: Sun, 9 Aug 2026 13:36:25 +0200 Subject: [PATCH 5/5] fix(agent-core): omit generationKwargs when promptCacheKey is undefined or endpoint is unofficial --- .../src/session/provider-manager.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/agent-core/src/session/provider-manager.ts b/packages/agent-core/src/session/provider-manager.ts index 6dc2c376ac..5f1b9e6351 100644 --- a/packages/agent-core/src/session/provider-manager.ts +++ b/packages/agent-core/src/session/provider-manager.ts @@ -328,13 +328,12 @@ 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. Official-endpoint - // only: strictly-validating OpenAI-compatible servers reject the - // unknown `prompt_cache_key` field with a 400 (#2166). - generationKwargs: { - prompt_cache_key: isOfficialOpenAIBaseUrl(baseUrl) ? promptCacheKey : undefined, - }, + // `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 (#2166). + ...(promptCacheKey !== undefined && isOfficialOpenAIBaseUrl(baseUrl) + ? { generationKwargs: { prompt_cache_key: promptCacheKey } } + : {}), ...defaultHeadersField({ ...envCustomHeaders, ...kimiUserAgentHeader(kimiRequestHeaders), @@ -348,7 +347,9 @@ function toKosongProviderConfig( model, baseUrl: modelBaseUrl ?? providerValue(provider.baseUrl, provider.env, 'KIMI_BASE_URL'), apiKey: providerApiKey(provider), - generationKwargs: { prompt_cache_key: promptCacheKey }, + ...(promptCacheKey !== undefined + ? { generationKwargs: { prompt_cache_key: promptCacheKey } } + : {}), ...defaultHeadersField({ ...envCustomHeaders, ...kimiRequestHeaders, @@ -379,9 +380,11 @@ 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: isOfficialOpenAIBaseUrl(baseUrl) ? promptCacheKey : undefined, - }, + // Only sent to official OpenAI API endpoints — third-party endpoints + // reject unknown parameters. + ...(promptCacheKey !== undefined && isOfficialOpenAIBaseUrl(baseUrl) + ? { generationKwargs: { prompt_cache_key: promptCacheKey } } + : {}), ...defaultHeadersField({ ...envCustomHeaders, ...kimiUserAgentHeader(kimiRequestHeaders),