SPB Git

spb/wp29_uqo

Public
3.3 KB · 59 lines python
1"""Normalise Claude Code stream-json lines into event records (raw lines are kept separately, verbatim)."""2from __future__ import annotations3import hashlib, json4from .contracts import utcnow56def _summ(s, n=240):7    s = (s or "").replace("\n", " ")8    return s if len(s) <= n else s[: n - 1] + "…"910def normalise(line: str, run_id: str, seq: int, elapsed_s: float):11    raw = line.rstrip("\n")12    if not raw.strip():13        return None14    base = {"event_id": f"{run_id}:{seq:05d}", "run_id": run_id, "seq": seq, "received_utc": utcnow(), "elapsed_s": round(elapsed_s, 3),15            "payload_sha256": hashlib.sha256(raw.encode()).hexdigest()}16    try:17        d = json.loads(raw)18    except Exception:  # noqa: BLE00119        return {**base, "event_type": "unparsed", "summary": _summ(raw), "session_id": None}20    t = d.get("type"); base["session_id"] = d.get("session_id"); base["event_type"] = t or "unknown"; base["subtype"] = d.get("subtype")21    if t == "system":22        base["summary"] = _summ(f"init model={d.get('model')} tools={len(d.get('tools', []))} cwd={d.get('cwd')} version={d.get('claude_code_version') or d.get('version')} permissionMode={d.get('permissionMode')}")23        base["status"] = "ok"24    elif t == "assistant":25        msg = d.get("message", {}); parts = []26        for b in msg.get("content", []) or []:27            if b.get("type") == "text": parts.append("text:" + _summ(b.get("text"), 120))28            elif b.get("type") == "tool_use":29                base["tool_name"] = b.get("name")30                inp = b.get("input", {})31                key = inp.get("command") or inp.get("file_path") or inp.get("pattern") or json.dumps(inp)[:100]32                parts.append(f"tool_use:{b.get('name')}:{_summ(str(key), 1500)}")33            elif b.get("type") == "thinking": parts.append("thinking")34        base["summary"] = " | ".join(parts); base["usage"] = msg.get("usage"); base["status"] = msg.get("stop_reason")35    elif t == "user":36        msg = d.get("message", {}); parts = []37        for b in msg.get("content", []) or []:38            if isinstance(b, dict) and b.get("type") == "tool_result":39                c = b.get("content"); txt = c if isinstance(c, str) else json.dumps(c)[:200]40                parts.append(("tool_result:error:" if b.get("is_error") else "tool_result:") + _summ(txt, 150))41                if b.get("is_error"): base["error_class"] = "tool_error"42        base["summary"] = " | ".join(parts) or _summ(json.dumps(msg)[:200]); base["status"] = "error" if base.get("error_class") else "ok"43    elif t == "result":44        base["summary"] = _summ(f"{d.get('subtype')} cost={d.get('total_cost_usd')} turns={d.get('num_turns')} ms={d.get('duration_ms')} err={d.get('is_error')} :: {d.get('result', '')}")45        base["usage"] = {"total_cost_usd": d.get("total_cost_usd"), "usage": d.get("usage"), "modelUsage": d.get("modelUsage"), "num_turns": d.get("num_turns"), "duration_ms": d.get("duration_ms")}46        base["status"] = "error" if d.get("is_error") else "ok"47        if d.get("is_error") or (d.get("subtype") and d["subtype"] != "success"):48            base["error_class"] = d.get("subtype")49    else:50        base["summary"] = _summ(raw)51    return base5253def parse_result_line(line: str):54    try:55        d = json.loads(line)56        return d if d.get("type") == "result" else None57    except Exception:  # noqa: BLE00158        return None59
Files · 502400bef152b80194d2a72e83992531749d18e1
Menu

Theme