diff --git a/polylogue/sources/origin_specs.py b/polylogue/sources/origin_specs.py index 5c9d501b03..3b0f4ad2e6 100644 --- a/polylogue/sources/origin_specs.py +++ b/polylogue/sources/origin_specs.py @@ -434,6 +434,32 @@ def _claude_code_spec() -> OriginSpec: "records -- there is no child-side wire evidence to read. tool_result outcome_unknown_reason is " "NOT_REPORTED when the Anthropic-protocol segment carries no is_error, and DISTRUSTED for the " "background-task start acknowledgement's is_error=false (see _mark_background_task_start).", + "polylogue-cgfy AC1: disposition of the 'other unread keys of substance' the bead's corpus " + "enumeration named beyond the structuredPatch/file_edits cluster (already read, see the note " + "above) and beyond stop_reason/stop_sequence/parentToolUseID/cache_creation/ttftMs/todos/" + "toolUseResult.sandbox/filenames/numFiles (already read, see code_parser.py's " + "_message_usage_event_payload and toolUseResult structural-fact projection). READ (this batch): " + "requestId (1,171 sampled occurrences -- the Anthropic API per-call request id, a real " + "cross-reference key against provider-side billing/support records) and " + "thinkingMetadata.maxThinkingTokens (34 occurrences -- the extended-thinking token budget " + "configured for the turn) both now ride the message_usage session-event payload as " + "request_id/max_thinking_tokens. MEASURED NEGATIVE: userType is the literal string 'external' " + "on every sampled record across two independent corpora (2,789 occurrences in the bead's " + "sample, reconfirmed against a second live ~/.claude/projects corpus this pass) -- a constant, " + "acquiring it adds nothing, same class as usage.service_tier. DELIBERATELY DROPPED, duplicate " + "of an already-captured field: sourceToolAssistantUUID (143 occurrences) was verified against " + "real records to equal that same record's own parentUuid (already captured as " + "ParsedMessage.parent_message_provider_id) -- a second spelling of the same edge, not new " + "evidence. DELIBERATELY DROPPED, duplicate of an already-captured tier: hookCount/hookInfos " + "(22 occurrences) describe which hooks fired on a record; hook execution is already tracked " + "durably in source.db's raw_hook_events (the 2026-07-22 hook-session-inflation fix's `write_" + "hook_event`) with the command line and outcome -- adding a second, index-tier, less complete " + "copy (hookInfos carries only `command`, no outcome) would be a parallel representation of " + "the same fact, not new information. toolUseID (679 occurrences, the record's own tool-call " + "correlation id, distinct from parentToolUseID) is already consumed for the real signal it " + "carries: the progress/agent_progress delegation-edge disposition documented in the module " + "docstring above _parse_code_records (claude_delegation_progress vs. six transient synthetic-" + "tick subtypes) -- not a bare unread field.", "code_parser.py's _NON_MESSAGE_SIDECAR_RECORD_TYPES (14 sidecar record " "types) already carries a per-type disposition with corpus counts " "in a comment block (polylogue-pbuh/parser-diff triage, " diff --git a/polylogue/sources/parsers/claude/code_parser.py b/polylogue/sources/parsers/claude/code_parser.py index 240dc03334..6f324cfe07 100644 --- a/polylogue/sources/parsers/claude/code_parser.py +++ b/polylogue/sources/parsers/claude/code_parser.py @@ -655,6 +655,7 @@ def _message_usage_event_payload( model_name: str | None, model_effort: str | None, message: Mapping[str, object] | None = None, + record: Mapping[str, object] | None = None, ) -> dict[str, object]: last_usage: dict[str, int] = { "input_tokens": _safe_int(usage.get("input_tokens")), @@ -735,6 +736,30 @@ def _message_usage_event_payload( reason_type = cache_miss_reason.get("type") if isinstance(reason_type, str) and reason_type: payload["cache_miss_reason"] = reason_type + if record is not None: + # requestId is the Anthropic API request identifier for this specific + # call -- a real correlation key for cross-referencing a message + # against provider-side support/billing records, distinct from any + # id already captured (uuid is Claude Code's own record id, tool_id + # is per tool-call). parser-diff triage (2026-07-29 / cgfy) found it + # unread despite 1,171 occurrences in the sample corpus. + request_id = record.get("requestId") + if isinstance(request_id, str) and request_id: + payload["request_id"] = request_id + # thinkingMetadata.maxThinkingTokens is the extended-thinking token + # budget Claude Code configured for this turn -- a real reasoning- + # effort signal distinct from the actual token counts already in + # last_token_usage. Low corpus frequency (34 in the cgfy sample) but + # unambiguous and cheap to carry once this payload is already built. + thinking_metadata = record.get("thinkingMetadata") + if isinstance(thinking_metadata, dict): + max_thinking_tokens = thinking_metadata.get("maxThinkingTokens") + if ( + isinstance(max_thinking_tokens, int) + and not isinstance(max_thinking_tokens, bool) + and max_thinking_tokens >= 0 + ): + payload["max_thinking_tokens"] = max_thinking_tokens return payload @@ -1624,6 +1649,7 @@ def _parse_code_records( model_name=msg_model, model_effort=msg_effort, message=message_payload, + record=item, ), ) ) diff --git a/tests/unit/sources/test_claude_code_unread_wire_fields.py b/tests/unit/sources/test_claude_code_unread_wire_fields.py index 3f2024e184..3c17aadf1b 100644 --- a/tests/unit/sources/test_claude_code_unread_wire_fields.py +++ b/tests/unit/sources/test_claude_code_unread_wire_fields.py @@ -249,6 +249,129 @@ def test_tool_result_missing_is_error_gets_not_reported_reason() -> None: assert result_blocks[0].outcome_unknown_reason == "not_reported" +def test_request_id_lands_on_message_usage_event() -> None: + """The top-level ``requestId`` (Anthropic API request id, 1,171 sampled + occurrences per polylogue-cgfy) must reach the ``message_usage`` session + event as ``request_id``. + + Deleting the ``record=item`` wiring at the ``message_usage`` append site + (or the ``requestId`` extraction inside ``_message_usage_event_payload``) + makes this key absent from every event. + """ + parsed = parse_code( + [ + { + "type": "assistant", + "uuid": "a1", + "sessionId": "sess-request-id", + "requestId": "req_011CPuYvnLASUV8W7nChG4jH", + "message": { + "role": "assistant", + "content": [{"type": "text", "text": "hi"}], + "usage": {"input_tokens": 3, "output_tokens": 2}, + }, + }, + ], + "sess-request-id", + ) + usage_events = [e for e in parsed.session_events if e.event_type == "message_usage"] + assert len(usage_events) == 1 + assert usage_events[0].payload["request_id"] == "req_011CPuYvnLASUV8W7nChG4jH" + + +def test_request_id_absent_omits_the_key() -> None: + """Anti-vacuity: no ``requestId`` on the record must not fabricate one.""" + parsed = parse_code( + [ + { + "type": "assistant", + "uuid": "a1", + "sessionId": "sess-no-request-id", + "message": { + "role": "assistant", + "content": [{"type": "text", "text": "hi"}], + "usage": {"input_tokens": 3, "output_tokens": 2}, + }, + }, + ], + "sess-no-request-id", + ) + usage_events = [e for e in parsed.session_events if e.event_type == "message_usage"] + assert len(usage_events) == 1 + assert "request_id" not in usage_events[0].payload + + +def test_thinking_metadata_max_tokens_lands_on_message_usage_event() -> None: + """``thinkingMetadata.maxThinkingTokens`` (extended-thinking budget) must + reach the ``message_usage`` event as ``max_thinking_tokens``. + """ + parsed = parse_code( + [ + { + "type": "assistant", + "uuid": "a1", + "sessionId": "sess-thinking-metadata", + "thinkingMetadata": {"maxThinkingTokens": 31999}, + "message": { + "role": "assistant", + "content": [{"type": "text", "text": "hi"}], + "usage": {"input_tokens": 3, "output_tokens": 2}, + }, + }, + ], + "sess-thinking-metadata", + ) + usage_events = [e for e in parsed.session_events if e.event_type == "message_usage"] + assert len(usage_events) == 1 + assert usage_events[0].payload["max_thinking_tokens"] == 31999 + + +def test_thinking_metadata_absent_omits_the_key() -> None: + """Anti-vacuity: no ``thinkingMetadata`` on the record must not fabricate one.""" + parsed = parse_code( + [ + { + "type": "assistant", + "uuid": "a1", + "sessionId": "sess-no-thinking-metadata", + "message": { + "role": "assistant", + "content": [{"type": "text", "text": "hi"}], + "usage": {"input_tokens": 3, "output_tokens": 2}, + }, + }, + ], + "sess-no-thinking-metadata", + ) + usage_events = [e for e in parsed.session_events if e.event_type == "message_usage"] + assert len(usage_events) == 1 + assert "max_thinking_tokens" not in usage_events[0].payload + + +def test_thinking_metadata_negative_tokens_omits_the_key() -> None: + """A negative ``maxThinkingTokens`` is not a real token budget -- omit it + rather than persisting a nonsensical value (CodeRabbit review, PR #3465).""" + parsed = parse_code( + [ + { + "type": "assistant", + "uuid": "a1", + "sessionId": "sess-negative-thinking-metadata", + "thinkingMetadata": {"maxThinkingTokens": -1}, + "message": { + "role": "assistant", + "content": [{"type": "text", "text": "hi"}], + "usage": {"input_tokens": 3, "output_tokens": 2}, + }, + }, + ], + "sess-negative-thinking-metadata", + ) + usage_events = [e for e in parsed.session_events if e.event_type == "message_usage"] + assert len(usage_events) == 1 + assert "max_thinking_tokens" not in usage_events[0].payload + + def test_background_task_start_ack_gets_distrusted_reason() -> None: """The backgrounded-task start acknowledgement's ``is_error=false`` is positively distrusted (it only confirms the task started), not merely