🤖 feat: GPT-5.6 explicit prompt cache breakpoints for direct OpenAI#3712
Merged
Conversation
Hybrid caching for official direct OpenAI GPT-5.6-family requests: - openaiExplicitPromptCachingAvailable: fail-closed route/auth/endpoint gate (exact openai route, no Codex OAuth, official api.openai.com base URL only) - createOpenAICachedSystemMessage: one explicit prompt_cache_breakpoint at the stable system-instructions boundary; request-wide mode stays implicit so OpenAI keeps its automatic latest-message breakpoint - StreamRequestConfig.system widened to string | SystemModelMessage; the builder evaluates initialMetadata.routeProvider (primary) and initialMetadataPatch.routeProvider (refusal fallback) so cache metadata cannot leak across routes - Chat Completions now carries the project-scoped promptCacheKey only under the stricter GPT-5.6 direct-route gate; legacy Responses key behavior is byte-for-byte unchanged - Wire-level tests (createOpenAI + capture fetch + streamText SSE) prove breakpoint/cache-key serialization and cache read/write usage parsing on both wire formats; displayUsage covers 0.1x read / 1.25x write pricing
Member
Author
|
@codex review |
|
Codex Review: Didn't find any major issues. You're on a roll. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds hybrid prompt caching for official, direct-OpenAI GPT-5.6-family requests: one explicit
prompt_cache_breakpointat the end of Mux's stable system/developer instructions, plus the project-scopedprompt_cache_keyon eligible Chat Completions requests. Request-wide caching stays implicit (nopromptCacheOptions), so OpenAI keeps placing its automatic latest-message breakpoint for growing conversations and tool loops.Background
GPT-5.6 (via
@ai-sdk/openai4.0.11) supports explicit content-level cache breakpoints. Mux's system prefix is stable across turns and context resets within a workspace, so pinning one explicit breakpoint at that boundary lets a fresh context (e.g. after Reset Context, Preserve History) reuse the cached instruction prefix instead of re-writing it — while implicit mode continues to cover the conversation tail.Implementation
openaiExplicitPromptCachingAvailable(cacheStrategy.ts): fail-closed eligibility gate — request namespace and resolved alias target must both beopenai:GPT-5.6-family, route provider must be exactlyopenai, Codex OAuth precedence fails closed (wouldRouteOpenAIThroughCodexOauth), and any configured base URL must be the officialhttps://api.openai.comendpoint (root or/v1). Gateways, custom endpoints, and missing route metadata all fail closed.createOpenAICachedSystemMessage: returns a structuredSystemModelMessagewith message-levelproviderOptions.openai.promptCacheBreakpoint: { mode: "explicit" }; the SDK serializes it to aninput_text(Responses) /text(Chat Completions) block.streamManager.buildStreamRequestConfignow takes the resolved route:initialMetadata.routeProviderfor primary requests andinitialMetadataPatch.routeProviderfor refusal-fallback rebuilds, so cache metadata cannot leak across routes in either direction.StreamRequestConfig.systemwidened tostring | SystemModelMessage; the Anthropic cached-system path is untouched.promptCacheKey(providerOptions.ts): added only behind the stricter gate. The legacy Responses key spread (including its broader route-absent/passthrough behavior) is byte-for-byte unchanged.Validation
createOpenAI+ capture fetch +streamTextover synthetic SSE) prove breakpoint + cache-key serialization on both wire formats, absence ofprompt_cache_options, and thatcached_tokens/cache_write_tokenssurface asinputTokenDetails.cacheReadTokens/cacheWriteTokensthrough the streaming parser.gpt-5.6-luna(official API key, isolated desktop sandbox):previous_response_id, unchangedprompt_cache_key, byte-identical system SHA-256) and read 14,410 cached tokens — explicit stable-prefix reuse. A follow-up read 14,429 (implicit latest-message reuse retained). Cost tab shows distinct cache-read/create rows priced at 0.1×/1.25×.textblock) + key serialize on the live wire; write 10,145 and same-conversation read 10,082 confirmed. Cross-conversation explicit-prefix reads stayed 0 over ~8 min of retries despite byte-identical prefixes — an OpenAI-side CC behavior, not a request-shape defect (Responses, Mux's default, shows full reuse).reasoning_effort != none, so the CC flow was validated with thinking off. Pre-existing behavior, unrelated to this change.Risks
prompt_cache_key. All other models, routes (gateways, Codex OAuth, custom endpoints), and Anthropic cache behavior are unchanged and covered by fail-closed tests.📋 Implementation Plan
GPT-5.6 explicit prompt caching
Goal
Add GPT-5.6 explicit stable-prefix cache breakpoints while retaining OpenAI's implicit latest-message caching, then verify request shape, token accounting, and real cache reuse without changing non-GPT-5.6 routes.
Current state
origin/mainat1d6e0caa5.@ai-sdk/openaiis now4.0.11, which supportspromptCacheOptions, content-partpromptCacheBreakpoint, andcache_write_tokensparsing.promptCacheKeyfor OpenAI Responses requests and already normalizes cache reads/writes through the usage pipeline.routeProvider: "openai"; missing route metadata is a legacy/test-stub case and should fail closed for the new fields.StreamManageralready receives the safeProviderService.getConfig()map, including effective base-URL and OAuth-source metadata, andAIServicealready rebuilds fallback system/provider options from the fallback model and route.Recommended approach
Use hybrid caching for eligible official, direct OpenAI GPT-5.6-family requests:
promptCacheOptions; GPT-5.6 already defaults to implicit mode and a 30-minute minimum TTL.promptCacheKeybehavior for Responses, and add the same key to eligible Chat Completions requests.Estimated net product-code change: approximately +65 to +95 LoC. Test-only changes are excluded.
Scope and non-goals
In scope
openai:gpt-5.6...at the implementation call sites), including supported date-suffixed IDs andopenai:aliases mapped to those targets. Raw unprefixed strings are normalized before these call sites; the new capability helper itself does not infer a provider and fails closed when the model origin is missing or non-OpenAI.StreamManager.Out of scope
promptCacheOptions.mode: "explicit"; explicit-only mode would disable the useful automatic latest-message breakpoint.providerExtras.promptCacheKeyvalues; that generic expert escape hatch is pre-existing behavior, while this plan governs only fields generated by Mux.providerMetadata.anthropic.cacheCreationInputTokenspersistence key; it already carries provider-neutral AI SDK cache-write usage through the current pricing pipeline despite the historical name.Implementation plan
Implement each phase test-first: add the failing behavioral assertion, make the smallest production change that satisfies it, then run the phase gate before continuing.
Phase 1 — Add a route-aware GPT-5.6 cache capability
In
src/common/types/thinking.ts, export the existingisGpt56FamilyModelmatcher instead of duplicating its model-ID regex.In
src/common/utils/ai/cacheStrategy.ts, add one pureopenaiExplicitPromptCachingAvailable(modelString, routeProvider, providersConfig: ProvidersConfigMap)helper that:AIService(openai:<model-id>for direct built-ins andopenai:aliases); require the request model's parsed origin to be exactlyopenai, so raw unprefixed inputs and non-OpenAI namespaces fail closed instead of relying on default-provider inference;mappedToModelaliases throughresolveModelForMetadata, independently requires the resolved capability target's origin to beopenai, and only then applies the GPT-5.6 family predicate to the target model ID;openai; missing, legacy, gateway, or unknown route metadata fails closed even though older provider-option paths are more permissive;wouldRouteOpenAIThroughCodexOauthfor API-key/OAuth precedence instead of duplicating provider-factory auth logic;openai.baseUrl/baseUrlResolvedview already returned byProviderService.getConfig(): absence of both means the SDK's official default, otherwise resolve the active value withbaseUrlprecedence and require HTTPS hostapi.openai.com, no credentials/query/hash/non-default port, and only the root or/v1path with an optional trailing slash;Add
createOpenAICachedSystemMessage, returning the exact AI SDK 7 input shape—message-level provider options with string content, not a content-part array:Return
nullfor an empty system prompt or an ineligible route. Keep Anthropic cache-control helpers and wire semantics unchanged.Quality gate: extend
src/common/utils/ai/cacheStrategy.test.tsto cover the actual production shapes (openai:gpt-5.6...andopenai:mapped aliases), every GPT-5.6 tier, rejection of raw unprefixed and non-OpenAI-origin strings, aliases whose resolved target is not OpenAI GPT-5.6, older models, missing/unknown/gateway routes, Codex OAuth precedence, no URL override, official explicit andbaseUrlResolvedURLs, custom/malformed/non-HTTPS URLs, empty system text, and the exact typed system-message shape.Phase 2 — Attach the breakpoint at the system-instructions boundary
src/node/services/streamManager.ts, widen onlyStreamRequestConfig.systemtostring | SystemModelMessage | undefined; keepPreparedModelFallback.systemand public/startup inputs as strings.buildStreamRequestConfigand let the builder use its existinggetProvidersConfig()callback for endpoint/auth eligibility; do not add another provider-resolution path.initialMetadata?.routeProviderfromcreateStreamAtomically.AIServicefallback rebuild: it already recreates the fallback system, messages, tools, headers, and provider options fromnext.canonicalModelString/next.routeProvider. BeforestreamInfo.initialMetadatais patched, passprepared.data.initialMetadataPatch?.routeProviderdirectly into the fallbackbuildStreamRequestConfigcall so the new system transform evaluates the fallback route rather than stale source metadata.buildStreamRequestConfig:messagesand clearssystem;createOpenAICachedSystemMessagewith the model, safe provider config, and resolved route;messagesunchanged and replace thesystemstring with the structuredSystemModelMessageaccepted by AI SDK 7;promptCacheOptions, because omitted mode and TTL preserve hybrid implicit + explicit caching with the provider default lifetime.allowSystemInMessages: truebehavior unchanged. OpenAI's structured instructions remain in thesystemargument and do not require a new compatibility switch.Quality gate: extend
src/node/services/streamManager.test.tsto prove direct GPT-5.6 produces structured cached instructions, ineligible routes/endpoints preserve the original string, Anthropic behavior remains unchanged, and the fallback route patch controls the transformed fallback system. Extendsrc/node/services/aiService.test.tsonly at the existing fallback seam to prove fallback provider options and route metadata are freshly built from the fallback model/route, including eligible→ineligible and ineligible→eligible Chat Completions cases.Phase 3 — Supply the routing key on GPT-5.6 Chat Completions
src/common/utils/ai/providerOptions.ts, reuse the same exact-route/auth/endpoint eligibility helper for the new Chat Completions behavior.promptCacheKeyspread and scope exactly where it is. Its current route-absent, passthrough-gateway, Codex OAuth, and custom-endpoint behavior is legacy behavior and must not be moved behind the stricter helper in this patch.promptCacheKeywhen the request is eligible for direct official GPT-5.6 explicit caching. Continue omitting Responses-only fields such astruncation,include, and reasoning summaries from Chat Completions.promptCacheOptions; the provider defaults are the intended policy.Quality gate: extend
src/common/utils/ai/providerOptions.test.tsso direct GPT-5.6 Chat Completions withrouteProvider: "openai"receives the cache key, while older models, mapped non-GPT-5.6 aliases, missing/unknown routes, gateways, Codex OAuth, and custom endpoints do not. Retain existing Responses assertions and add a GPT-5.6 Responses regression proving the legacy key remains unchanged for route-absent and passthrough cases.Phase 4 — Prove SDK serialization and accounting behavior
createOpenAI, a non-networked capture fetch, andstreamTextfor both provider formats, following the existing serialization pattern insrc/common/utils/ai/providerOptions.test.ts:input_textblock withprompt_cache_breakpoint;textblock with the same breakpoint;prompt_cache_key;prompt_cache_optionsor disable implicit mode;maxRetries: 0, return minimal valid SSE, fully consume each stream, and await the publicusageresult so the test exercises the same streaming parser used by Mux rather than relying ongenerateTextparser sharing.cached_tokensandcache_write_tokensin the final usage-bearing Chat Completions chunk and in the Responsesresponse.completed.response.usageevent, then assert AI SDK 4.0.11 exposes both asinputTokenDetails.cacheReadTokensandcacheWriteTokensfor each wire format.src/common/utils/tokens/displayUsage.test.tswith a GPT-5.6 case proving Mux separates ordinary input, cache reads, and cache writes and applies the configured 0.1× read and 1.25× write prices. Do not rewrite the already-covered normalization pipeline.Quality gate: run the focused suites before broader checks:
bun test \ src/common/utils/ai/cacheStrategy.test.ts \ src/common/utils/ai/providerOptions.test.ts \ src/node/services/streamManager.test.ts \ src/node/services/aiService.test.ts \ src/common/utils/tokens/displayUsage.test.ts make typecheckPhase 5 — Full validation
Run formatting, lint, typechecking, and the relevant test suite through the repository targets:
Re-run the focused tests in a fresh Bun process if the broad check exposes QuickJS/resource instability unrelated to these files.
Inspect the final diff and confirm no provider config schema, UI, analytics schema, or Anthropic cache behavior changed.
Dogfooding and evidence
Use a live official OpenAI API-key route because custom base URLs are intentionally excluded and a mock endpoint would not exercise the production eligibility gate. Use
gpt-5.6-lunato minimize cost.Setup
Confirm an OpenAI API key with GPT-5.6 access is available, Codex OAuth is not the selected auth path, and the OpenAI provider has no custom base URL.
Start an isolated desktop with provider settings copied into a temporary root:
Connect
agent-browserto the Electron CDP port, enable Settings → General → API Debug Logs, and open the Right Sidebar DevTools and Cost tabs.Use one workspace so its workspace environment block and full system prompt remain byte-identical. Save a sanitized copy of the first raw request and a SHA-256 of its system/developer text. After the first request, use the command palette action Reset Context, Preserve History to create a durable context boundary without changing the workspace or cache key. The post-reset raw request—not the cache counter alone—must prove that the prior conversation was sliced out before treating the next read as explicit stable-prefix reuse. Run requests sequentially and wait for each stream-end signal before continuing.
Responses flow
Start a recording before model selection and preserve it through the DevTools/Cost inspection:
mkdir -p /tmp/mux-gpt56-cache/{screenshots,videos} agent-browser --session gpt56-cache connect 9223 agent-browser --session gpt56-cache record start /tmp/mux-gpt56-cache/videos/responses-cache.webmIn workspace A, select
gpt-5.6-lunaon the Responses wire format and send a short-output prompt. Wait for the explicit stream-end signal, not a fixed sleep.Capture the DevTools request showing:
prompt_cache_key;prompt_cache_breakpoint: { mode: "explicit" };prompt_cache_options.mode: "explicit".Capture the first response's nonzero Cache write value.
Invoke Reset Context, Preserve History in the same workspace, then send a different first user message. Capture the sanitized post-reset raw request and verify all four isolation invariants before interpreting the usage result: no pre-reset user/assistant/tool messages remain in
input; noprevious_response_idwire field (or camel-casepreviousResponseIdmetadata field) is present;prompt_cache_keyis unchanged; and the system/developer block plus itsprompt_cache_breakpointis byte-identical or has the same SHA-256 as the first request. Then capture a nonzero Cache read value; with those invariants established, the hit demonstrates stable instruction-prefix reuse rather than implicit reuse of the prior conversation.Send one follow-up in the new context and capture the longer cached prefix expected from the retained implicit latest-message breakpoint. Treat latency as supporting evidence only; token counters plus the raw-request isolation checks are the acceptance signal.
Capture the Cost tab showing distinct uncached input, cache-read, and cache-write token/cost rows, then stop the recording.
Chat Completions flow
prompt_cache_keyand the system content breakpoint are serialized in the Chat Completions body.messages: no pre-reset turns, no previous-response identifier in raw request or metadata, unchanged cache key, and byte/hash-identical developer/system content./tmp/mux-gpt56-cache/videos/chat-completions-cache.webm.Evidence requirements
.webmper wire format, with pauses around request inspection and token results.attach_file.Acceptance criteria
promptCacheKeybehavior remains byte-for-byte unchanged, including its broader legacy route behavior.make typecheck, andMUX_ESLINT_CONCURRENCY=1 make static-checkpass.Risks and mitigations
Rollback
Removing the OpenAI structured-system transform and the Chat Completions key extension restores today's automatic-only behavior. Existing Responses cache keys, Anthropic cache control, persisted usage, and pricing remain intact, so rollback requires no data migration.
Generated with
mux• Model:anthropic:claude-fable-5• Thinking:xhigh• Cost:$205.62