Step-by-step guide for adding support for new agent platforms
Adding a new platform adapter is the most common contribution to Lerim. This guide walks you through creating an adapter for a new coding agent platform.
Adjust the pattern (*.jsonl, *.db, etc.) based on your platform’s format.
4
Implement find_session_path()
Given a session ID, locate the corresponding file:
def find_session_path( session_id: str, traces_dir: Path | None = None) -> Path | None: """Find a MyPlatform session file by ID.""" base = traces_dir or default_path() if base is None or not base.exists(): return None for path in base.rglob("*.jsonl"): if path.stem == session_id: return path return None
If your platform supports tool calls, track them by ID:
tool_messages: dict[str, ViewerMessage] = {}# When you encounter a tool use:tool_id = entry.get("tool_id")tool_msg = ViewerMessage( role="tool", tool_name=entry.get("tool_name"), tool_input=entry.get("tool_input"), timestamp=timestamp)tool_messages[tool_id] = tool_msgmessages.append(tool_msg)# When you encounter a tool result:tool_id = entry.get("tool_id")if tool_id in tool_messages: tool_messages[tool_id].tool_output = entry.get("output")
6
Implement iter_sessions()
Enumerate all sessions with metadata, respecting time windows and known hashes:
def iter_sessions( traces_dir: Path | None = None, start: datetime | None = None, end: datetime | None = None, known_run_hashes: dict[str, str] | None = None,) -> list[SessionRecord]: """Enumerate MyPlatform sessions, skipping unchanged files.""" base = traces_dir or default_path() if base is None or not base.exists(): return [] records: list[SessionRecord] = [] for path in base.rglob("*.jsonl"): run_id = path.stem file_hash = compute_file_hash(path) # Skip if unchanged if known_run_hashes and run_id in known_run_hashes: if known_run_hashes[run_id] == file_hash: continue entries = load_jsonl_dict_lines(path) if not entries: continue # Extract metadata start_time: datetime | None = None message_count = 0 tool_calls = 0 total_tokens = 0 for entry in entries: ts = parse_timestamp(entry.get("timestamp")) if ts and (start_time is None or ts < start_time): start_time = ts if entry.get("type") in {"user", "assistant"}: message_count += 1 usage = entry.get("usage", {}) total_tokens += usage.get("input_tokens", 0) total_tokens += usage.get("output_tokens", 0) # Filter by time window if not in_window(start_time, start, end): continue records.append( SessionRecord( run_id=run_id, agent_type="myplatform", session_path=str(path), start_time=start_time.isoformat() if start_time else None, message_count=message_count, tool_call_count=tool_calls, total_tokens=total_tokens, content_hash=file_hash, ) ) records.sort(key=lambda r: r.start_time or "") return records
7
Register the adapter
Add your adapter to the registry in src/lerim/adapters/registry.py:
Timestamp format inconsistencies: Different platforms use different timestamp formats (ISO strings, Unix seconds, Unix milliseconds). Always use parse_timestamp() from common.py.
Tool call pairing: Some platforms send tool use and tool result in separate messages. Track tool calls by ID and pair them later (see Claude adapter for reference).
Empty content handling: Some platforms may have empty or null content fields. Always check for None and empty strings before adding messages.
Nested content structures: Platforms like Claude use nested content arrays. Flatten these into plain text for ViewerMessage.content.