refactor(anthropic): simplify OAuth relocation + add 4-breakpoint-cap test

simplify-code cleanup pass over the prior commit (behavior-preserving):

- Reuse prompt_caching._build_marker('5m') for the relocated block's
  cache_control instead of inlining {type: ephemeral} (extend-don't-duplicate;
  also gains the 1h-TTL path for free).
- Hoist _sanitize_oauth_text and its brand-replacement pairs to module level
  (_OAUTH_TEXT_REPLACEMENTS) — the file's style is module-level helpers, and
  the nested def was rebuilt on every request. Now auditable + unit-testable.
- Fold sanitization into the single collection pass (drop the second list
  rebuild) and drop the redundant 'if p' join filter (parts are already
  non-empty). Tag literal -> _OAUTH_SYSTEM_CONTEXT_TAG constant.
- Document the 4-breakpoint-cap arithmetic and add a regression test
  (test_oauth_relocation_respects_4_breakpoint_cap) that runs the REAL
  production order (apply_anthropic_cache_control then build_anthropic_kwargs)
  and asserts the OAuth wire never exceeds Anthropic's 4 cache breakpoints —
  the one genuinely risky interaction the original tests didn't cover.

No behavior change: full live request still bills to plan, caching preserved,
no single-underscore mcp_ on the wire. 366 anthropic tests pass.
This commit is contained in:
kshitijk4poor 2026-06-17 14:09:52 +05:30
parent ab7b4edcc6
commit 7bc2916b40
2 changed files with 99 additions and 26 deletions

View File

@ -374,6 +374,25 @@ def _detect_claude_code_version() -> str:
_CLAUDE_CODE_SYSTEM_PREFIX = "You are Claude Code, Anthropic's official CLI for Claude."
_MCP_TOOL_PREFIX = "mcp__"
# Product-name replacements applied to the relocated OAuth system prompt so
# nothing in the preamble reads as a competing-product identity (Anthropic's
# OAuth billing classifier fingerprints distinctive non-Claude-Code content).
_OAUTH_TEXT_REPLACEMENTS = (
("Hermes Agent", "Claude Code"),
("Hermes agent", "Claude Code"),
("hermes-agent", "claude-code"),
("Nous Research", "Anthropic"),
)
# Wrapper tag for the relocated prompt on the first user message.
_OAUTH_SYSTEM_CONTEXT_TAG = "system_context"
def _sanitize_oauth_text(text: str) -> str:
"""Mask competing-product identity references in OAuth-relocated prompt text."""
for old, new in _OAUTH_TEXT_REPLACEMENTS:
text = text.replace(old, new)
return text
def _prepend_oauth_system_context(messages, preamble: str) -> None:
"""Prepend ``preamble`` as a cache-marked leading block of the first user message.
@ -383,10 +402,18 @@ def _prepend_oauth_system_context(messages, preamble: str) -> None:
and into the conversation, mirroring how Claude Code keeps only its identity
line in ``system[]``.
The relocated block carries ``cache_control: ephemeral`` so the heavy prompt
prefix is still cached across turns the first user message is a stable
prefix within a conversation, so the cache breakpoint simply moves from the
system slot to the first-user-message slot without breaking caching.
The relocated block carries a 5m ``cache_control`` marker (built via
``prompt_caching._build_marker``) so the heavy prompt prefix is still cached
across turns the first user message is a stable prefix within a
conversation, so the cache breakpoint simply moves from the system slot to
the first-user-message slot without breaking caching.
Note on the 4-breakpoint cap: the upstream ``apply_anthropic_cache_control``
pass places a marker on the (heavy) system block + the last 3 messages. When
the OAuth path below reduces ``system[]`` to the identity line, that system
marker is discarded along with the block it rode on, and this preamble marker
takes its place net total stays at exactly 4 (verified by
``test_oauth_relocation_respects_4_breakpoint_cap``).
Mutates ``messages`` in place. Handles user messages whose content is a
plain string or a list of content blocks, and synthesises a user message at
@ -394,10 +421,11 @@ def _prepend_oauth_system_context(messages, preamble: str) -> None:
"""
if not preamble:
return
from agent.prompt_caching import _build_marker
block = {
"type": "text",
"text": preamble,
"cache_control": {"type": "ephemeral"},
"cache_control": _build_marker("5m"),
}
for msg in messages:
if msg.get("role") != "user":
@ -2385,40 +2413,30 @@ def build_anthropic_kwargs(
# still cached across turns (the first user message is a stable prefix; a
# 2-turn check confirms cache_read on turn 2).
if is_oauth:
# 1. Collect existing system text (string or content blocks).
# 1. Collect + sanitize existing system text in one pass (string or
# content blocks). Sanitizing inline avoids a second list rebuild.
extra_system_parts: List[str] = []
if isinstance(system, list):
for b in system:
if isinstance(b, dict) and b.get("type") == "text" and b.get("text"):
extra_system_parts.append(b["text"])
extra_system_parts.append(_sanitize_oauth_text(b["text"]))
elif isinstance(system, str) and system:
extra_system_parts.append(system)
extra_system_parts.append(_sanitize_oauth_text(system))
# 2. Sanitize the relocated text — replace product-name references so
# nothing in the preamble reads as a competing-product identity.
def _sanitize_oauth_text(text: str) -> str:
text = text.replace("Hermes Agent", "Claude Code")
text = text.replace("Hermes agent", "Claude Code")
text = text.replace("hermes-agent", "claude-code")
text = text.replace("Nous Research", "Anthropic")
return text
extra_system_parts = [_sanitize_oauth_text(t) for t in extra_system_parts]
# 3. system[] = the official Claude Code identity line only.
# 2. system[] = the official Claude Code identity line only.
system = [{"type": "text", "text": _CLAUDE_CODE_SYSTEM_PREFIX}]
# 4. Relocate the real prompt into a <system_context> preamble on the
# 3. Relocate the real prompt into a <system_context> preamble on the
# first user message, with cache_control to preserve prompt caching.
if extra_system_parts:
preamble = (
"<system_context>\n"
+ "\n\n".join(p for p in extra_system_parts if p).strip()
+ "\n</system_context>"
f"<{_OAUTH_SYSTEM_CONTEXT_TAG}>\n"
+ "\n\n".join(extra_system_parts).strip()
+ f"\n</{_OAUTH_SYSTEM_CONTEXT_TAG}>"
)
_prepend_oauth_system_context(anthropic_messages, preamble)
# 5. Normalize tool names so NOTHING goes on the OAuth wire with a
# 4. Normalize tool names so NOTHING goes on the OAuth wire with a
# single-underscore ``mcp_`` prefix. Anthropic's subscription/OAuth
# billing classifier treats a single-underscore ``mcp_`` tool name as
# a third-party-app fingerprint and rejects the request with HTTP 400
@ -2449,7 +2467,7 @@ def build_anthropic_kwargs(
if "name" in tool:
tool["name"] = _to_oauth_wire_name(tool["name"])
# 6. Apply the same normalization to tool names in message history
# 5. Apply the same normalization to tool names in message history
# (tool_use blocks) so replayed turns match the wire names above.
for msg in anthropic_messages:
content = msg.get("content")

View File

@ -125,3 +125,58 @@ class TestOAuthSystemRelocation:
assert not any("<system_context>" in b.get("text", "") for b in content)
else:
assert "<system_context>" not in content
def _count_cache_markers(obj) -> int:
"""Count cache_control markers anywhere in a nested message/system structure."""
n = 0
if isinstance(obj, dict):
if "cache_control" in obj:
n += 1
for v in obj.values():
n += _count_cache_markers(v)
elif isinstance(obj, list):
for v in obj:
n += _count_cache_markers(v)
return n
class TestOAuthCacheBreakpointCap:
"""Regression guard for the 4-breakpoint cap under the REAL production order.
The risky interaction (untested before this): the upstream
``apply_anthropic_cache_control`` pass marks the system block + last 3
messages (4 breakpoints), THEN the OAuth relocation reduces system[] to the
identity line. The relocated preamble must take the place of the discarded
system marker so the wire stays at exactly 4 never 5, which Anthropic 400s.
"""
def test_oauth_relocation_respects_4_breakpoint_cap(self):
from agent.prompt_caching import apply_anthropic_cache_control
msgs = [
{"role": "system", "content": "You are Hermes Agent by Nous Research. " * 80},
{"role": "user", "content": "first question"},
{"role": "assistant", "content": "first answer"},
{"role": "user", "content": "second question"},
{"role": "assistant", "content": "second answer"},
{"role": "user", "content": "third question"},
]
# Production order: caching pass first, then build_anthropic_kwargs.
cached = apply_anthropic_cache_control(msgs, cache_ttl="5m", native_anthropic=False)
kw = build_anthropic_kwargs(
model="claude-opus-4-8",
messages=cached,
tools=None,
max_tokens=8,
reasoning_config=None,
is_oauth=True,
)
total = _count_cache_markers(kw.get("system")) + _count_cache_markers(kw.get("messages"))
assert total <= 4, f"OAuth wire exceeded Anthropic's 4-breakpoint cap: {total}"
# And the relocated preamble must be one of the breakpoints (cache preserved).
first_user = next(m for m in kw["messages"] if m["role"] == "user")
blocks = first_user["content"]
assert isinstance(blocks, list)
assert "cache_control" in blocks[0]
assert blocks[0]["text"].startswith("<system_context>")