Files
OmniRoute/tests/unit/claude-context-1m-client-beta-forward.test.ts
Diego Rodrigues de Sa e Souza 7db3ba615b fix(sse): stop thrashing the provider prompt cache for caching-aware clients (#8705)
* fix(sse): stop thrashing the provider prompt cache for caching-aware clients

- shouldPreserveCacheControl: preserve client cache_control markers for every
  combo strategy. The deterministic-strategy gate forced marker rewrites whose
  per-request breakpoint positions are not stable turn-over-turn, thrashing the
  upstream prompt cache (observed in production as ~200k cache_write tokens per
  turn on quota-share combos). Preserving is never worse: on a stable target
  the client's breakpoints advance deterministically; on a target switch both
  approaches miss equally.
- prepareClaudeRequest: translator-path opt-in fallback — when preserve-mode
  has nothing to preserve (client sent no cache_control anywhere), apply the
  standard heuristic so requests never ship with zero cache breakpoints. The
  claude-code-compatible relay path keeps its no-supplement contract.
- anthropic-beta: forward the client-negotiated context-1m-2025-08-07 through
  the allowlist merge so a /model <id>[1m] client keeps its long-context
  negotiation behind the proxy (never forced when the client did not send it).

* fix(sse): surface cache tokens in non-streaming OpenAI usage + finalize pending on quota-share block

- translateNonStreamingResponse (claude→openai): fold cache_read into
  prompt_tokens and expose prompt_tokens_details.cached_tokens /
  cache_creation_tokens, mirroring the streaming contract (#1426/#2215).
  Non-streaming OpenAI clients behind a cached Claude upstream previously saw
  prompt_tokens=<uncached remainder> (e.g. 23 for a ~9k request) with no cache
  visibility.
- chatCore quota-share block: finalize the pending-request slot before
  returning the policy 429 — the path never reaches upstream and the orphaned
  pending lingered as a status-0 call-log row until the reaper swept it.
  Validated live on the staging box (repro: blocked key → orphan row; after:
  clean).

---------

Co-authored-by: diegosouzapw <diegosouzapw24@gmail.com>
2026-07-27 17:24:43 -03:00

75 lines
2.9 KiB
TypeScript

/**
* Client-negotiated `context-1m-2025-08-07` must be forwarded upstream.
*
* When Claude Code runs with a `[1m]` model (e.g. `/model sonnet[1m]` or
* `ANTHROPIC_DEFAULT_SONNET_MODEL='<id>[1m]'`), the client sizes its context
* window at 1M AND negotiates `anthropic-beta: context-1m-2025-08-07` itself.
* Behind OmniRoute that beta was silently dropped for non-Opus models:
* selectBetaFlags only emits context-1m for `claude-opus*` (deliberate — never
* FORCE it, see the long-context credit-gate note there), and the
* FORWARDABLE_CLIENT_BETAS allowlist did not include it, so the client's own
* negotiation was stripped. Real Claude Code sends the beta in this setup, so
* forwarding what the client asked for is the fingerprint-faithful behavior —
* and it is required for >200K-context requests on models/accounts where the
* beta is enforced.
*
* Guard preserved: the beta is only APPENDED when the client sent it — a
* client without `[1m]` keeps the exact same beta set as before.
*/
import test from "node:test";
import assert from "node:assert/strict";
const { selectBetaFlags } = await import("../../open-sse/executors/claudeIdentity.ts");
const { mergeClientAnthropicBeta, FORWARDABLE_CLIENT_BETAS } =
await import("../../open-sse/config/anthropicHeaders.ts");
const CONTEXT_1M = "context-1m-2025-08-07";
function fullAgentBody(model: string) {
return {
model,
system: "You are a coding agent.",
tools: [{ name: "read_file", description: "x", input_schema: { type: "object" } }],
};
}
test("mergeClientAnthropicBeta forwards a client-negotiated context-1m beta", () => {
const out = mergeClientAnthropicBeta(
"claude-code-20250219,oauth-2025-04-20",
`oauth-2025-04-20,${CONTEXT_1M}`
);
assert.ok(
out.split(",").includes(CONTEXT_1M),
"client [1m] negotiation must survive the allowlist merge"
);
assert.ok(FORWARDABLE_CLIENT_BETAS.includes(CONTEXT_1M));
});
test("selectBetaFlags + merge keeps context-1m for a Sonnet [1m] client", () => {
const clientBeta = `claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,${CONTEXT_1M}`;
const out = mergeClientAnthropicBeta(
selectBetaFlags(fullAgentBody("claude-sonnet-5"), null, clientBeta),
clientBeta
);
assert.ok(
out.split(",").includes(CONTEXT_1M),
"Sonnet client that negotiated [1m] must reach upstream with context-1m"
);
});
test("context-1m is never forced when the client did not negotiate it", () => {
const clientBeta = "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14";
const out = mergeClientAnthropicBeta(
selectBetaFlags(fullAgentBody("claude-sonnet-5"), null, clientBeta),
clientBeta
);
assert.ok(
!out.split(",").includes(CONTEXT_1M),
"a client without [1m] keeps the exact same beta set as before"
);
});
test("tool-search forwarding keeps working (regression #3974)", () => {
assert.ok(FORWARDABLE_CLIENT_BETAS.includes("tool-search-tool-2025-10-19"));
});