Phase 3 of the anthropic port (M3):
- ir.py gains the response IR (ChatResponse, ResponseUsage, finish reasons)
and stream-event union; providers/anthropic parse_response/parse_stream
reproduce v1 exactly for the v2-reachable surface (tool-name reverse
mapping recomputed from the request, json_tool_call rewrite, usage cache
folding, redacted thinking) and fail loudly on block types a v2-sent
request cannot trigger
- inbound/openai_chat serialize_response/serialize_stream mirror v1's
ModelResponse / CustomStreamWrapper shapes chunk-for-chunk (role on first
chunk, tool-index counting, thinking provider fields, finish chunk)
- engine/: pipeline.py (prepare -> send split so the fallback decision
happens before any I/O; async-first, sync is one asyncio.run wrapper at
the seam), http.py (injected HttpPort + ExecuteError values), stream.py
(the ONE accumulator: provider lines -> IR events -> chunk bodies)
- litellm/translation_seam.py (outside the package) adapts deps from litellm
ambient state, ModelResponse/ModelResponseStream envelopes, and owns the
completion() fork; litellm.translation_v2_providers allowlist global
seeded from LITELLM_TRANSLATION_V2_PROVIDERS (off by default, yaml-
configurable via the litellm_settings setattr fallback)
- main.py anthropic branch forks to the seam; streaming and modify_params
traffic stay on v1 (documented follow-ups); once sent, provider errors
raise the v1 exception contract, never a silent re-send
- differential gates: responses (4 full-cycle shapes incl. sanitized tool
names and json_tool_call), streams (3 SSE replays vs the real
CustomStreamWrapper), seam tests over respx (serve, fallback, 429
contract, async path); DIFFERENTIAL_REPORT.md committed as the merge
artifact: 53 rows, 0 divergent