fix: handle claude's array style JSON https://github.com/BeehiveInnovations/zen-mcp-server/issues/295
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user