MCPcopy
hub / github.com/phuryn/claude-usage / parse_jsonl_file

Function parse_jsonl_file

scanner.py:317–449  ·  view source on GitHub ↗

Parse a JSONL file and return (session_metas, turns, agents, line_count). Deduplicates streaming events by message.id — Claude Code logs multiple JSONL records per API response, all sharing the same message.id. Only the last record per message_id is kept (it has the final usage tallies)

(filepath)

Source from the content-addressed store, hash-verified

315
316
317def parse_jsonl_file(filepath):
318 """Parse a JSONL file and return (session_metas, turns, agents, line_count).
319
320 Deduplicates streaming events by message.id — Claude Code logs multiple
321 JSONL records per API response, all sharing the same message.id. Only the
322 last record per message_id is kept (it has the final usage tallies).
323 """
324 seen_messages = {} # message_id -> turn dict (dedup streaming records)
325 turns_no_id = [] # turns without a message_id (kept as-is)
326 session_meta = {} # session_id -> dict
327 agents = {} # agent_id -> dispatch dict
328 line_count = 0
329
330 try:
331 with open(filepath, encoding="utf-8", errors="replace") as f:
332 for line_count, line in enumerate(f, 1):
333 line = line.strip()
334 if not line:
335 continue
336 try:
337 record = json.loads(line)
338 except json.JSONDecodeError:
339 continue
340
341 rtype = record.get("type")
342 if rtype not in ("assistant", "user", "custom-title", "ai-title"):
343 continue
344
345 session_id = record.get("sessionId")
346 if not session_id:
347 continue
348
349 # Extract session title from title records
350 title = _extract_title(record)
351 if title:
352 if session_id not in session_meta:
353 session_meta[session_id] = {
354 "session_id": session_id,
355 "project_name": "unknown",
356 "first_timestamp": "",
357 "last_timestamp": "",
358 "git_branch": "",
359 "model": None,
360 "topic": None,
361 }
362 meta = session_meta[session_id]
363 # custom-title always wins; ai-title only if no custom-title set
364 if rtype == "custom-title":
365 meta["topic"] = title
366 elif rtype == "ai-title" and not meta.get("topic"):
367 meta["topic"] = title
368 continue
369
370 if rtype == "user":
371 dispatch = extract_agent_dispatch(record)
372 if dispatch is not None:
373 agents[dispatch["agent_id"]] = dispatch
374

Calls 5

_extract_titleFunction · 0.85
extract_agent_dispatchFunction · 0.85
project_name_from_cwdFunction · 0.85
is_subagent_recordFunction · 0.85
record_agent_idFunction · 0.85