Merged
Size
M
Change Breakdown
Bug Fix70%
Refactor20%
Docs10%
#60761fix(openrouter): gate prompt cache markers by endpoint

OpenRouter cache markers now gated to verified endpoints

Fixes ensure Anthropic prompt-cache markers only apply to genuine OpenRouter routes, not arbitrary OpenAI proxies that happen to use the same provider identifier.

The OpenRouter integration was incorrectly applying Anthropic prompt-cache cache_control markers based on provider name alone. This meant custom OpenAI-compatible proxies declared with provider: "openrouter" would receive OpenRouter-specific payload mutations, while users with custom provider IDs pointing to the actual OpenRouter API missed out on cache optimizations entirely.

The fix moves cache marker handling into the shared request policy layer and gates it on endpoint classification rather than provider string matching. Now the system verifies whether a request is actually routing to OpenRouter before applying the marker — custom proxies get untouched payloads, while genuine OpenRouter routes (including those behind custom provider IDs) receive the cache optimization as intended.

In the AI agent runner, cache marker wrapping was relocated from the OpenRouter plugin into the generic stream wrapper pipeline. The wrapper itself now calls resolveProviderRequestPolicy to determine the endpoint class, then conditionally applies markers only when the route is verified as OpenRouter or when the provider identifier is the default "openrouter" with no custom base URL.

View Original GitHub Description

AI-assisted: Codex Testing: focused test + build Session log: available in Codex session history

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: OpenRouter prompt-cache cache_control markers were keyed to provider/plugin identity instead of endpoint classification.
  • Why it matters: provider: openrouter on a custom OpenAI-compatible proxy could receive OpenRouter/Anthropic-only payload mutations, while custom provider ids pointed at native OpenRouter missed the cache optimization.
  • What changed: cache marker wrapping now runs in the generic post-plugin path and gates on shared request policy so only native/default OpenRouter routes receive the mutation.
  • What did NOT change (scope boundary): reasoning/routing behavior, non-Anthropic models, and non-OpenRouter transports.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: the cache wrapper was only installed by the OpenRouter plugin and only checked provider === "openrouter", so it diverged from the transport stack's endpoint-based OpenRouter detection.
  • Missing detection / guardrail: there was no direct regression test covering default OpenRouter, custom proxy URL, and custom provider id on native OpenRouter as separate cases.
  • Contributing context (if known): other OpenRouter-specific behavior already used endpoint classification, so the cache path drifted from the shared policy.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/agents/pi-embedded-runner/proxy-stream-wrappers.test.ts
  • Scenario the test should lock in: default-route OpenRouter keeps Anthropic cache markers, custom proxy URLs do not get the mutation, and custom provider ids on native OpenRouter still get the mutation.
  • Why this is the smallest reliable guardrail: the behavior is determined in the stream-wrapper/policy boundary before any live provider call.
  • Existing test that already covers this (if any): the same file already covered header behavior on the OpenRouter wrapper.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • Native/default OpenRouter routes keep Anthropic prompt-cache cache_control markers.
  • Custom OpenAI-compatible proxy deployments declared as OpenRouter stop receiving OpenRouter-only cache payload mutations.
  • Custom provider ids pointed at native OpenRouter now get the same cache marker behavior as the builtin OpenRouter plugin.
  • Changelog entry added under the active release block: Thanks @vincentkoc.

Diagram (if applicable)

Before:
[provider=openrouter] -> [cache wrapper checks provider id only] -> [custom proxy may get OpenRouter-only cache mutation]
[custom provider id -> openrouter.ai] -> [no openrouter plugin wrapper] -> [cache mutation missing]

After:
[request policy resolves endpoint class] -> [native/default OpenRouter only] -> [cache mutation applied when Anthropic-compatible]
[custom proxy endpoint] -> [not classified as OpenRouter] -> [payload left untouched]

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: macOS 15.6.1
  • Runtime/container: local shared Codex worktree, Node v25.8.2, pnpm 10.32.1
  • Model/provider: OpenRouter + Anthropic-model request path
  • Integration/channel (if any): N/A
  • Relevant config (redacted): builtin openrouter provider on default route vs custom baseUrl, plus custom provider id targeting https://openrouter.ai/api/v1

Steps

  1. Send an Anthropic-model request through builtin OpenRouter on the default route.
  2. Send the same shape through provider: openrouter with a custom OpenAI-compatible baseUrl.
  3. Send the same shape through a custom provider id whose baseUrl is native OpenRouter.

Expected

  • Step 1 keeps prompt-cache markers.
  • Step 2 does not inject OpenRouter-only cache markers into proxy traffic.
  • Step 3 keeps prompt-cache markers despite the custom provider id.

Actual

  • Matches expected with the new wrapper gating and regression tests.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Verification snippets:

  • pnpm test src/agents/pi-embedded-runner/proxy-stream-wrappers.test.ts
  • pnpm build
  • pnpm check currently fails in untouched files on this branch (src/agents/pi-embedded-runner/compact.hooks.harness.ts, src/agents/pi-embedded-runner/run/setup.ts) with existing TS2883 portability errors; those files are not part of this diff.

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: focused regression coverage for default OpenRouter, custom proxy URL, and custom provider id on native OpenRouter; post-rebase pnpm build passes.
  • Edge cases checked: non-Anthropic requests remain unmodified by the cache wrapper gate; OpenRouter plugin wrapper composition still works with the generic post-plugin wrapper path.
  • What you did not verify: live network calls to OpenRouter or a third-party proxy; full pnpm check is blocked by untouched TS2883 errors outside this change.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: unusual provider configs could still be misclassified if request-policy detection changes independently later.
    • Mitigation: the cache path now reuses the shared policy helper and has direct regression coverage for the three relevant endpoint classes.
© 2026 · via Gitpulse