Skip to content

feat(types): preserve dropped fields on AssistantMessage and ResultMessage#718

Merged
qing-ant merged 1 commit intomainfrom
fix/preserve-dropped-message-fields
Mar 25, 2026
Merged

feat(types): preserve dropped fields on AssistantMessage and ResultMessage#718
qing-ant merged 1 commit intomainfrom
fix/preserve-dropped-message-fields

Conversation

@qing-ant
Copy link
Copy Markdown
Contributor

Summary

The message parser was discarding several fields that the CLI emits in its JSON output. SDK consumers had no way to access important data like per-model usage breakdown (modelUsage), message IDs, UUIDs, and permission denials.

Changes

AssistantMessage — 4 fields added

CLI field SDK field Source
message.id message_id Anthropic API message ID
message.stop_reason stop_reason Per-turn stop reason (end_turn, tool_use, etc.)
session_id session_id Session identifier
uuid uuid SDK message UUID for transcript correlation

ResultMessage — 3 fields added

CLI field SDK field Source
modelUsage model_usage Per-model usage breakdown (tokens, cost, context window)
permission_denials permission_denials List of denied permission requests
uuid uuid SDK message UUID for transcript correlation

All new fields are optional with None defaults for backward compatibility.

E2E Verification

Verified with a live SDK instance (v0.1.50, CLI 2.1.83):

=== AssistantMessage ===
  message_id: msg_013yKzePkD82yiUKxLjeMisU
  session_id: d815620f-c625-4179-9fd0-8fe26ef523d3
  uuid: 2acfe9fc-bab6-4829-9ac4-7894f36205e1

=== ResultMessage ===
  uuid: 582ae113-f2e5-43c2-90dc-9fcb0ec760da
  model_usage: True (per-model breakdown with costUSD)
  permission_denials: []

Tests

4 new test cases:

  • test_parse_assistant_message_with_all_fields — verifies all new fields are parsed
  • test_parse_assistant_message_optional_fields_absent — verifies defaults to None
  • test_parse_result_message_with_model_usage — verifies modelUsage, permission_denials, uuid
  • test_parse_result_message_optional_fields_absent — verifies defaults to None

All 292 tests pass. Lint clean (ruff), type check clean (mypy).

Fixes #562

…ssage

Add missing fields that the CLI emits but the SDK was discarding:

AssistantMessage:
- message_id: Anthropic API message ID (from message.id)
- stop_reason: per-turn stop reason (from message.stop_reason)
- session_id: session identifier
- uuid: SDK message UUID for transcript correlation

ResultMessage:
- model_usage: per-model usage breakdown with cost, token counts,
  and context window info (from modelUsage)
- permission_denials: list of denied permission requests
- uuid: SDK message UUID for transcript correlation

All new fields are optional with None defaults for backward
compatibility.

Fixes #562
Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — straightforward additive change preserving optional CLI fields with full test coverage.

Extended reasoning...

Overview

This PR adds 7 optional fields across two dataclasses (AssistantMessage and ResultMessage) in types.py, extracts them via .get() in message_parser.py, and adds 4 test cases in test_message_parser.py. All new fields default to None for backward compatibility.

Security risks

None. The changes only preserve data that the CLI already emits — no new inputs are processed, no auth/permissions logic is touched, and no external APIs are called.

Level of scrutiny

Low scrutiny is appropriate. This is a mechanical, additive change that follows the exact same pattern already used throughout the parser (e.g., usage=data["message"].get("usage") was already present). The dataclass fields are all optional with None defaults, so there is zero risk of breaking existing consumers.

Other factors

The 4 new tests comprehensively cover both the "all fields present" and "all fields absent" scenarios for both message types. The PR description includes E2E verification against a live CLI. The change is small (7 new lines in types, 7 in parser, ~90 in tests) and self-contained.

@qing-ant
Copy link
Copy Markdown
Contributor Author

E2E Test Results for PR #718

Test: Live claude_agent_sdk.query() call verifying that previously-dropped fields are now preserved on AssistantMessage and ResultMessage.

AssistantMessage Fields (Previously Dropped)

Field Status Sample Value
message_id POPULATED msg_01BqLK6eNJg8jqwqUDG5Lk7U
stop_reason None Expected for intermediate assistant chunks; final stop_reason appears on ResultMessage
session_id POPULATED 9b56dedc-4e43-4bab-8812-e475dcab0c0c
uuid POPULATED 3cbe1b7a-306f-4bf6-b7e1-bb81779e5097

ResultMessage Fields (Previously Dropped)

Field Status Sample Value
model_usage POPULATED Per-model breakdown with inputTokens, outputTokens, cacheReadInputTokens, costUSD, etc.
permission_denials POPULATED [] (empty list, no denials in this run -- correctly not None)
uuid POPULATED 32683150-a744-440c-87bc-75ba623d0eef

Test Summary

Unit tests:           43/43 passed (test_message_parser.py)
AssistantMessage fields accessible: PASS
ResultMessage fields accessible:    PASS
AssistantMessage dataclass has all new fields: PASS
ResultMessage dataclass has all new fields: PASS
OVERALL RESULT: PASS

All previously-dropped fields are now correctly preserved and populated by the CLI. The stop_reason on AssistantMessage being None is expected behavior -- the CLI only sets it on intermediate messages when the stop reason is available at that point; the authoritative stop_reason is on ResultMessage (where it correctly shows end_turn).

@qing-ant qing-ant enabled auto-merge (squash) March 25, 2026 20:31
@qing-ant qing-ant merged commit 24b9b68 into main Mar 25, 2026
10 checks passed
@qing-ant qing-ant deleted the fix/preserve-dropped-message-fields branch March 25, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AssistantMessage and ResultMessage drop significant fields from CLI JSON output

3 participants