Files
james.maesandClaude Opus 4.7 d8983af004 fix(anthropic): add stream:true to request body — root cause of streamTruncatedFinal
Discovered via live launch on 2026-05-05. Every Anthropic call returned
HTTP 200 + zero SSE frames; SSEDecoder emitted streamTruncatedFinal.

Root cause: `/v1/messages` opts into Server-Sent Events via the request
BODY field `"stream": true`, NOT via the `Accept: text/event-stream`
header alone. Our request body had every other field correct (model,
messages, system, tool_choice, max_tokens) but no stream field. Server
defaulted to non-streaming, returned a regular JSON response, our
SSELineReader read zero `data:` frames, decoder yielded
streamTruncatedFinal on EOF.

The 2026-05-03 audit-fix Track A added a StreamOutcome classifier and
the CacheHints.eligibleForSystemPrompt gate — both helpful diagnostic
+ defensive measures, but neither addressed the actual cause. The
audit hypothesis was "cache_control on small prompts triggers silent
rejection"; live testing disproved that and surfaced this simpler bug.

Fix:
- Add `stream: Bool` to AnthropicRequestBody encoding (mapped to JSON key `stream`)
- Pass `stream: true` from `RequestBody.encodeMultimodal`
- Reverted the speculative beta-header gating from this morning — beta
  header is unconditional per AGENT-02 and was never the cause

Tests:
- New `AnthropicProviderTests.LiveAnthropicStreamingTests`:
  - L3 (regression fence, no network): asserts encoded body contains
    `"stream": true`. FAILED before this commit. PASSES after.
  - L1 (live, env-gated on JARVIS_REAL_ANTHROPIC=1 + ANTHROPIC_API_KEY):
    real-API integration test that drives a small prompt and asserts at
    least one textDelta event arrives. Skipped without env.
  - L2 (no network): dumps the encoded body for diff against curl.
- All 49 existing AnthropicProvider tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 09:25:00 -05:00
..