anthropics / anthropics/claude-agent-sdk-python

parse_message silently drops newer CLI wire fields (ttft_ms, terminal_reason, stop_details, …) with no escape hatch

Aberta
#1,026 0 comentários 0 reações 0 responsáveis Ver no GitHub
bug enhancement
Linguagem predominante
Python
Estrelas
8.1k
Forks
1.3k
Merge médio
2d 31min
PRs com merge (30d)
1

Descrição

## Summary

The Claude Code CLI (2.1.150) emits several stream-json fields that `parse_message` neither reads nor retains — they vanish from the typed messages with no way to recover them. Unlike `SystemMessage` (which keeps the raw frame as `.data`), `AssistantMessage` and `ResultMessage` don't retain the original frame, so SDK consumers cannot access these fields at all without bypassing the SDK and re-parsing the wire themselves.

Verified identical on **claude-agent-sdk 0.2.87 and 0.2.95** (latest).

## Dropped fields observed on the wire

| Wire field | Example recorded value | Notes |
|---|---|---|
| `result.ttft_ms` | `2806` | time-to-first-token — a metric consumers chart |
| `result.terminal_reason` | `"completed"` | |
| `result.fast_mode_state` | `"off"` | |
| `assistant message.content[].caller` | `{"type": "direct"}` | per content block |
| `assistant message.stop_details` | observed `null` | emitted, unmodeled |
| `assistant message.diagnostics` | observed `null` | emitted, unmodeled |
| `assistant message.context_management` | observed `null` | emitted, unmodeled |
| `assistant request_id` | `"req_…"` | top-level frame key |

## Repro

```python
from claude_agent_sdk._internal.message_parser import parse_message

frame = { # verbatim CLI stream-json (trimmed)
"type": "result", "subtype": "success", "duration_ms": 8127,
"duration_api_ms": 7903, "is_error": False, "num_turns": 4,
"session_id": "s", "total_cost_usd": 0.02, "usage": {},
"ttft_ms": 2806, "terminal_reason": "completed", "fast_mode_state": "off",
}
msg = parse_message(frame)
assert not hasattr(msg, "ttft_ms") # gone
assert "terminal_reason" not in str(msg) # gone, and not recoverable
```

## Suggested fix (either works)

1. Model the fields on the dataclasses (`ResultMessage.ttft_ms`, etc.), or
2. Retain the raw frame on every message type (the `SystemMessage.data` pattern) as a forward-compatible escape hatch, so new CLI fields are reachable before they're modeled.

## How this was found

Mechanically, by a record/replay drift detector ([claude-agent-cassette](https://github.com/oneryalcin/claude-agent-cassette)): it runs `parse_message` over an access-tracking view of recorded frames and reports any key the parser neither reads nor retains. Happy to re-run it against a branch if useful.

Guia de contribuição

Nenhum guia de contribuição indexado para este repositório

Avaliação

Esta issue ainda não foi avaliada.

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.