diff --git a/packages/ai/src/protocols/open-responses.ts b/packages/ai/src/protocols/open-responses.ts index 4ea2d81f183..1dd449319eb 100644 --- a/packages/ai/src/protocols/open-responses.ts +++ b/packages/ai/src/protocols/open-responses.ts @@ -633,6 +633,7 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques const lowerOptions = (request: LLMRequest) => { const options = OpenResponsesOptions.resolve(request) + const cacheKey = ProviderShared.clampPromptCacheKey(request.promptCacheKey) return { ...(options.instructions ? { instructions: options.instructions } : {}), ...(options.store !== undefined ? { store: options.store } : {}), @@ -642,7 +643,7 @@ const lowerOptions = (request: LLMRequest) => { ? { stream_options: { include_obfuscation: options.streamOptions.includeObfuscation } } : {}), ...(options.topLogprobs !== undefined ? { top_logprobs: options.topLogprobs } : {}), - ...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}), + ...(cacheKey ? { prompt_cache_key: cacheKey } : {}), ...(options.include ? { include: options.include } : {}), ...(options.reasoningEffort || options.reasoningSummary ? { reasoning: { effort: options.reasoningEffort, summary: options.reasoningSummary } } diff --git a/packages/ai/src/protocols/openai-chat.ts b/packages/ai/src/protocols/openai-chat.ts index 795ec921996..95d0249144f 100644 --- a/packages/ai/src/protocols/openai-chat.ts +++ b/packages/ai/src/protocols/openai-chat.ts @@ -530,9 +530,10 @@ const hasToolHistory = (messages: ReadonlyArray) const lowerOptions = (request: LLMRequest) => { const options = OpenAIOptions.resolve(request) + const cacheKey = ProviderShared.clampPromptCacheKey(request.promptCacheKey) return { ...(options.store !== undefined ? { store: options.store } : {}), - ...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}), + ...(cacheKey ? { prompt_cache_key: cacheKey } : {}), ...(options.reasoningEffort ? { reasoning_effort: options.reasoningEffort } : {}), } } diff --git a/packages/ai/src/protocols/shared.ts b/packages/ai/src/protocols/shared.ts index 12c7cf7031b..49d8acef386 100644 --- a/packages/ai/src/protocols/shared.ts +++ b/packages/ai/src/protocols/shared.ts @@ -24,6 +24,17 @@ export const JsonObject = Schema.Record(Schema.String, Schema.Unknown) export const optionalArray = (schema: S) => Schema.optional(Schema.Array(schema)) export const optionalNull = (schema: S) => Schema.optional(Schema.NullOr(schema)) +export const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64 + +// OpenAI limits `prompt_cache_key` to 64 chars; DeepSeek and Zai inherit the same +// limit via their OpenAI-compatible APIs. Clamp with unicode-aware slicing. +export const clampPromptCacheKey = (key: string | undefined): string | undefined => { + if (key === undefined) return undefined + const chars = Array.from(key) + if (chars.length <= OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH) return key + return chars.slice(0, OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH).join("") +} + /** * Streaming tool-call accumulator. Adapters that build a tool call across * multiple `tool-input-delta` chunks store the partial JSON input string here diff --git a/packages/ai/src/providers/openrouter.ts b/packages/ai/src/providers/openrouter.ts index 83d1339d5b7..b00cd54e4d3 100644 --- a/packages/ai/src/providers/openrouter.ts +++ b/packages/ai/src/providers/openrouter.ts @@ -9,7 +9,7 @@ import type { ProviderPackage } from "../provider-package.js" import * as OpenAICompatibleProfiles from "./openai-compatible-profile.js" import * as OpenAIChat from "../protocols/openai-chat.js" import { newBreakpoints, ttlBucket } from "../protocols/utils/cache.js" -import { isRecord } from "../protocols/shared.js" +import { isRecord, ProviderShared } from "../protocols/shared.js" export const profile = OpenAICompatibleProfiles.profiles.openrouter export const id = ProviderID.make(profile.provider) @@ -115,11 +115,12 @@ export const protocol = Protocol.make({ reasoning_details: reasoningDetails, } }) + const cacheKey = ProviderShared.clampPromptCacheKey(request.promptCacheKey) return { ...body, messages, ...bodyOptions(request.providerOptions), - ...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}), + ...(cacheKey ? { prompt_cache_key: cacheKey } : {}), } as OpenRouterBody }), ),