| """CLI event parser for Claude Code CLI output. |
| |
| This parser emits an ordered stream of low-level events suitable for building a |
| Claude Code-like transcript in messaging UIs. |
| """ |
|
|
| from typing import Any |
|
|
| from loguru import logger |
|
|
|
|
| def parse_cli_event(event: Any) -> list[dict]: |
| """ |
| Parse a CLI event and return a structured result. |
| |
| Args: |
| event: Raw event dictionary from CLI |
| |
| Returns: |
| List of parsed event dicts. Empty list if not recognized. |
| """ |
| if not isinstance(event, dict): |
| return [] |
|
|
| etype = event.get("type") |
| results: list[dict[str, Any]] = [] |
|
|
| |
| |
| if etype == "system": |
| return [] |
|
|
| |
| msg_obj = None |
| if etype == "assistant" or etype == "user": |
| msg_obj = event.get("message") |
| elif etype == "result": |
| res = event.get("result") |
| if isinstance(res, dict): |
| msg_obj = res.get("message") |
| |
| if not msg_obj and isinstance(res.get("content"), list): |
| msg_obj = {"content": res.get("content")} |
| if not msg_obj: |
| msg_obj = event.get("message") |
| |
| if not msg_obj and isinstance(event.get("content"), list): |
| msg_obj = {"content": event.get("content")} |
|
|
| if msg_obj and isinstance(msg_obj, dict): |
| content = msg_obj.get("content", []) |
| if isinstance(content, list): |
| |
| for c in content: |
| if not isinstance(c, dict): |
| continue |
| ctype = c.get("type") |
| if ctype == "text": |
| results.append({"type": "text_chunk", "text": c.get("text", "")}) |
| elif ctype == "thinking": |
| results.append( |
| {"type": "thinking_chunk", "text": c.get("thinking", "")} |
| ) |
| elif ctype == "tool_use": |
| results.append( |
| { |
| "type": "tool_use", |
| "id": str(c.get("id", "") or "").strip(), |
| "name": c.get("name", ""), |
| "input": c.get("input"), |
| } |
| ) |
| elif ctype == "tool_result": |
| results.append( |
| { |
| "type": "tool_result", |
| "tool_use_id": str(c.get("tool_use_id", "") or "").strip(), |
| "content": c.get("content"), |
| "is_error": bool(c.get("is_error", False)), |
| } |
| ) |
|
|
| if results: |
| return results |
|
|
| |
| if etype == "content_block_delta": |
| delta = event.get("delta", {}) |
| if isinstance(delta, dict): |
| if delta.get("type") == "text_delta": |
| return [ |
| { |
| "type": "text_delta", |
| "index": event.get("index", -1), |
| "text": delta.get("text", ""), |
| } |
| ] |
| if delta.get("type") == "thinking_delta": |
| return [ |
| { |
| "type": "thinking_delta", |
| "index": event.get("index", -1), |
| "text": delta.get("thinking", ""), |
| } |
| ] |
| if delta.get("type") == "input_json_delta": |
| return [ |
| { |
| "type": "tool_use_delta", |
| "index": event.get("index", -1), |
| "partial_json": delta.get("partial_json", ""), |
| } |
| ] |
|
|
| |
| if etype == "content_block_start": |
| block = event.get("content_block", {}) |
| if isinstance(block, dict): |
| btype = block.get("type") |
| if btype == "thinking": |
| return [{"type": "thinking_start", "index": event.get("index", -1)}] |
| if btype == "text": |
| return [{"type": "text_start", "index": event.get("index", -1)}] |
| if btype == "tool_use": |
| return [ |
| { |
| "type": "tool_use_start", |
| "index": event.get("index", -1), |
| "id": str(block.get("id", "") or "").strip(), |
| "name": block.get("name", ""), |
| "input": block.get("input"), |
| } |
| ] |
|
|
| |
| if etype == "content_block_stop": |
| return [{"type": "block_stop", "index": event.get("index", -1)}] |
|
|
| |
| if etype == "error": |
| err = event.get("error") |
| msg = err.get("message") if isinstance(err, dict) else str(err) |
| logger.info(f"CLI_PARSER: Parsed error event: {msg}") |
| return [{"type": "error", "message": msg}] |
| elif etype == "exit": |
| code = event.get("code", 0) |
| stderr = event.get("stderr") |
| if code == 0: |
| logger.debug(f"CLI_PARSER: Successful exit (code={code})") |
| return [{"type": "complete", "status": "success"}] |
| else: |
| |
| error_msg = stderr if stderr else f"Process exited with code {code}" |
| logger.warning(f"CLI_PARSER: Error exit (code={code}): {error_msg}") |
| return [ |
| {"type": "error", "message": error_msg}, |
| {"type": "complete", "status": "failed"}, |
| ] |
|
|
| |
| if etype: |
| logger.debug(f"CLI_PARSER: Unrecognized event type: {etype}") |
| return [] |
|
|