"""Source discovery helpers for local agent session providers.""" from __future__ import annotations import os from datetime import UTC, datetime from pathlib import Path from typing import Any from app.services import codex_session_reader def _claude_projects_dir() -> Path: override = os.environ.get("CLAUDE_PROJECTS_PATH", "").strip() if override: return Path(override) return Path.home() / ".claude" / "projects" def _claude_source_card(scanned_at: datetime) -> dict[str, Any]: root = _claude_projects_dir() files = ( sorted(root.rglob("*.jsonl"), key=lambda path: path.stat().st_mtime, reverse=True) if root.exists() else [] ) last_activity = None if files: last_activity = datetime.fromtimestamp(files[0].stat().st_mtime, UTC).replace(tzinfo=None) return { "source": "claude_code", "provider_label": "Claude Code", "source_status": "available" if files else "unavailable", "source_path": str(root), "session_count": len(files), "last_activity_at": last_activity, "last_scanned_at": scanned_at, "unavailable_reason": ( None if files else "Claude Code session history was not found or contains no readable sessions." ), "setup_hint": ( None if files else "Run Claude Code locally, or set CLAUDE_PROJECTS_PATH to a readable projects directory." ), } def list_sources() -> list[dict[str, Any]]: """Return availability cards for supported agent session sources.""" scanned_at = datetime.now(UTC).replace(tzinfo=None) codex = codex_session_reader.source_metadata() openai_reason = "Pipeline does not yet have an owned OpenAI API session event source." openai_hint = ( "Connect gateway logs, stored API traces, an import file, or a future collector before " "OpenAI API history can be shown." ) return [ _claude_source_card(scanned_at), { "source": codex.source, "provider_label": codex.provider_label, "source_status": codex.source_status, "source_path": codex.source_path, "session_count": codex.session_count, "last_activity_at": codex.last_activity_at, "last_scanned_at": codex.last_scanned_at, "unavailable_reason": codex.unavailable_reason, "setup_hint": codex.setup_hint, }, { "source": "openai_api", "provider_label": "OpenAI API", "source_status": "unavailable", "source_path": None, "session_count": 0, "last_activity_at": None, "last_scanned_at": scanned_at, "unavailable_reason": openai_reason, "setup_hint": openai_hint, }, ]