From cad51f458208df4584ebb941f8bc8025a1055fc1 Mon Sep 17 00:00:00 2001 From: phall Date: Wed, 12 Aug 2026 18:31:54 -0400 Subject: [PATCH] fix(core): preserve prompt cache affinity --- packages/core/src/session/runner/llm.ts | 5 ++++- packages/core/test/session-runner.test.ts | 4 ++++ packages/llm/src/protocols/openai-chat.ts | 3 +++ packages/llm/test/provider/openai-chat.test.ts | 14 ++++++++++++++ .../test/provider/openai-compatible-chat.test.ts | 12 ++++++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts index 72c761e10d93..186c9a8c28fb 100644 --- a/packages/core/src/session/runner/llm.ts +++ b/packages/core/src/session/runner/llm.ts @@ -204,7 +204,10 @@ const layer = Layer.effect( const promptCacheKey = /^ses_[0-9a-f]{64}$/.test(session.id) ? session.id.slice(4) : session.id const request = LLM.request({ model, - providerOptions: { openai: { promptCacheKey } }, + providerOptions: { + openai: { promptCacheKey }, + openrouter: { promptCacheKey }, + }, system: [agent.info?.system, system.baseline] .filter((part): part is string => part !== undefined && part.length > 0) .map(SystemPart.make), diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index 57d4456d2df2..af60d1136090 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -2507,6 +2507,10 @@ describe("SessionRunnerLLM", () => { sessionID, otherSessionID, ]) + expect(requests.map((request) => request.providerOptions?.openrouter?.promptCacheKey)).toEqual([ + sessionID, + otherSessionID, + ]) yield* Deferred.succeed(streamGate, undefined) yield* Fiber.join(first) yield* Fiber.join(second) diff --git a/packages/llm/src/protocols/openai-chat.ts b/packages/llm/src/protocols/openai-chat.ts index 9ac85b07b139..5a2a25b27612 100644 --- a/packages/llm/src/protocols/openai-chat.ts +++ b/packages/llm/src/protocols/openai-chat.ts @@ -95,6 +95,7 @@ export const bodyFields = { tool_choice: Schema.optional(OpenAIChatToolChoice), stream: Schema.Literal(true), stream_options: Schema.optional(Schema.Struct({ include_usage: Schema.Boolean })), + prompt_cache_key: Schema.optional(Schema.String), store: Schema.optional(Schema.Boolean), reasoning_effort: Schema.optional(OpenAIOptions.OpenAIReasoningEffort), max_tokens: Schema.optional(Schema.Number), @@ -332,11 +333,13 @@ const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request: const lowerOptions = Effect.fn("OpenAIChat.lowerOptions")(function* (request: LLMRequest) { const store = OpenAIOptions.store(request) + const promptCacheKey = request.model.provider === "openai" ? OpenAIOptions.promptCacheKey(request) : undefined const reasoningEffort = OpenAIOptions.reasoningEffort(request) if (reasoningEffort && !OpenAIOptions.isReasoningEffort(reasoningEffort)) return yield* invalid(`OpenAI Chat does not support reasoning effort ${reasoningEffort}`) return { ...(store !== undefined ? { store } : {}), + ...(promptCacheKey ? { prompt_cache_key: promptCacheKey } : {}), ...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}), } }) diff --git a/packages/llm/test/provider/openai-chat.test.ts b/packages/llm/test/provider/openai-chat.test.ts index b736dc9dd33c..9e2ef0c23b14 100644 --- a/packages/llm/test/provider/openai-chat.test.ts +++ b/packages/llm/test/provider/openai-chat.test.ts @@ -29,6 +29,20 @@ const request = LLM.request({ }) describe("OpenAI Chat route", () => { + it.effect("maps the prompt cache key for native OpenAI models", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.request({ + model: OpenAI.configure({ baseURL: "https://api.openai.test/v1/", apiKey: "test" }).chat("gpt-4o-mini"), + prompt: "hello", + providerOptions: { openai: { promptCacheKey: "session_123" } }, + }), + ) + + expect(prepared.body.prompt_cache_key).toBe("session_123") + }), + ) + it.effect("prepares OpenAI Chat payload", () => Effect.gen(function* () { // Pass the OpenAIChat payload type so `prepared.body` is statically diff --git a/packages/llm/test/provider/openai-compatible-chat.test.ts b/packages/llm/test/provider/openai-compatible-chat.test.ts index 43ae283e9f7c..ae8bcc60b976 100644 --- a/packages/llm/test/provider/openai-compatible-chat.test.ts +++ b/packages/llm/test/provider/openai-compatible-chat.test.ts @@ -50,6 +50,18 @@ const providerFamilies = [ ] as const describe("OpenAI-compatible Chat route", () => { + it.effect("does not send native OpenAI cache options", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.updateRequest(request, { + providerOptions: { openai: { promptCacheKey: "session_123" } }, + }), + ) + + expect(prepared.body).not.toHaveProperty("prompt_cache_key") + }), + ) + it.effect("prepares generic Chat target", () => Effect.gen(function* () { const prepared = yield* LLMClient.prepare(