Parse a CLI event and return a structured result. Args: event: Raw event dictionary from CLI log_raw_cli: When True, log full error text from the CLI. Default is metadata-only (lengths / exit codes) to avoid leaking user content. Returns: List of pa
(event: Any, *, log_raw_cli: bool = False)
| 10 | |
| 11 | |
| 12 | def parse_cli_event(event: Any, *, log_raw_cli: bool = False) -> list[dict]: |
| 13 | """ |
| 14 | Parse a CLI event and return a structured result. |
| 15 | |
| 16 | Args: |
| 17 | event: Raw event dictionary from CLI |
| 18 | log_raw_cli: When True, log full error text from the CLI. Default is |
| 19 | metadata-only (lengths / exit codes) to avoid leaking user content. |
| 20 | |
| 21 | Returns: |
| 22 | List of parsed event dicts. Empty list if not recognized. |
| 23 | """ |
| 24 | if not isinstance(event, dict): |
| 25 | return [] |
| 26 | |
| 27 | etype = event.get("type") |
| 28 | results: list[dict[str, Any]] = [] |
| 29 | |
| 30 | # Some CLI/proxy layers emit "system" events that are not user-visible and |
| 31 | # carry no transcript content. Ignore them explicitly to avoid noisy logs. |
| 32 | if etype == "system": |
| 33 | return [] |
| 34 | |
| 35 | # 1. Handle full messages (assistant/user or result) |
| 36 | msg_obj = None |
| 37 | if etype == "assistant" or etype == "user": |
| 38 | msg_obj = event.get("message") |
| 39 | elif etype == "result": |
| 40 | res = event.get("result") |
| 41 | if isinstance(res, dict): |
| 42 | msg_obj = res.get("message") |
| 43 | # Some variants put content directly on the result. |
| 44 | if not msg_obj and isinstance(res.get("content"), list): |
| 45 | msg_obj = {"content": res.get("content")} |
| 46 | if not msg_obj: |
| 47 | msg_obj = event.get("message") |
| 48 | # Some variants put content directly on the event. |
| 49 | if not msg_obj and isinstance(event.get("content"), list): |
| 50 | msg_obj = {"content": event.get("content")} |
| 51 | |
| 52 | if msg_obj and isinstance(msg_obj, dict): |
| 53 | content = msg_obj.get("content", []) |
| 54 | if isinstance(content, list): |
| 55 | # Preserve order exactly as content blocks appear. |
| 56 | for c in content: |
| 57 | if not isinstance(c, dict): |
| 58 | continue |
| 59 | ctype = c.get("type") |
| 60 | if ctype == "text": |
| 61 | results.append({"type": "text_chunk", "text": c.get("text", "")}) |
| 62 | elif ctype == "thinking": |
| 63 | results.append( |
| 64 | {"type": "thinking_chunk", "text": c.get("thinking", "")} |
| 65 | ) |
| 66 | elif ctype == "tool_use": |
| 67 | results.append( |
| 68 | { |
| 69 | "type": "tool_use", |