This commit is contained in:
Fahad
2025-10-21 10:41:02 +04:00
parent d2773f488a
commit d5790a9bfe
2 changed files with 52 additions and 1 deletions

View File

@@ -18,11 +18,35 @@ class ClaudeJSONParser(BaseParser):
raise ParserError("Claude CLI returned empty stdout while JSON output was expected") raise ParserError("Claude CLI returned empty stdout while JSON output was expected")
try: try:
payload: dict[str, Any] = json.loads(stdout) loaded = json.loads(stdout)
except json.JSONDecodeError as exc: # pragma: no cover - defensive logging except json.JSONDecodeError as exc: # pragma: no cover - defensive logging
raise ParserError(f"Failed to decode Claude CLI JSON output: {exc}") from exc raise ParserError(f"Failed to decode Claude CLI JSON output: {exc}") from exc
events: list[dict[str, Any]] | None = None
assistant_entry: dict[str, Any] | None = None
if isinstance(loaded, dict):
payload: dict[str, Any] = loaded
elif isinstance(loaded, list):
events = [item for item in loaded if isinstance(item, dict)]
result_entry = next(
(item for item in events if item.get("type") == "result" or "result" in item),
None,
)
assistant_entry = next(
(item for item in reversed(events) if item.get("type") == "assistant"),
None,
)
payload = result_entry or assistant_entry or (events[-1] if events else {})
if not payload:
raise ParserError("Claude CLI JSON array did not contain any parsable objects")
else:
raise ParserError("Claude CLI returned unexpected JSON payload")
metadata = self._build_metadata(payload, stderr) metadata = self._build_metadata(payload, stderr)
if events is not None:
metadata["raw_events"] = events
metadata["raw"] = loaded
result = payload.get("result") result = payload.get("result")
content: str = "" content: str = ""
@@ -37,6 +61,8 @@ class ClaudeJSONParser(BaseParser):
return ParsedCLIResponse(content=content, metadata=metadata) return ParsedCLIResponse(content=content, metadata=metadata)
message = self._extract_message(payload) message = self._extract_message(payload)
if message is None and assistant_entry and assistant_entry is not payload:
message = self._extract_message(assistant_entry)
if message: if message:
return ParsedCLIResponse(content=message, metadata=metadata) return ParsedCLIResponse(content=message, metadata=metadata)

View File

@@ -1,5 +1,7 @@
"""Tests for the Claude CLI JSON parser.""" """Tests for the Claude CLI JSON parser."""
import json
import pytest import pytest
from clink.parsers.base import ParserError from clink.parsers.base import ParserError
@@ -43,3 +45,26 @@ def test_claude_parser_requires_output():
with pytest.raises(ParserError): with pytest.raises(ParserError):
parser.parse(stdout="", stderr="") parser.parse(stdout="", stderr="")
def test_claude_parser_handles_array_payload_with_result_event():
parser = ClaudeJSONParser()
events = [
{"type": "system", "session_id": "abc"},
{"type": "assistant", "message": "intermediate"},
{
"type": "result",
"subtype": "success",
"result": "42",
"duration_api_ms": 9876,
"usage": {"input_tokens": 12, "output_tokens": 3},
},
]
stdout = json.dumps(events)
parsed = parser.parse(stdout=stdout, stderr="warning")
assert parsed.content == "42"
assert parsed.metadata["duration_api_ms"] == 9876
assert parsed.metadata["raw_events"] == events
assert parsed.metadata["raw"] == events