Pipeline/backend/app/services/agent_session_sources.py

88 lines
2.8 KiB
Python

"""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,
},
]