Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/copilot_experiments/sessionlog.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@
from pathlib import Path
from typing import Any

from rich.console import Console

from . import pricing
from .models import Metrics, ModelMetric, TokenEconomics

err = Console(stderr=True)


def session_state_root(base: Path | None = None) -> Path:
"""Directory where the Copilot CLI stores per-session state."""
Expand All @@ -27,14 +31,23 @@ def load_events(path: Path) -> list[dict[str, Any]]:
if not path.exists():
return []
events: list[dict[str, Any]] = []
skipped_lines = 0
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line:
continue
try:
events.append(json.loads(line))
except json.JSONDecodeError:
skipped_lines += 1
continue

if skipped_lines > 0:
err.print(
f"[yellow]Warning: Skipped {skipped_lines} malformed JSON line(s) "
f"in {path.name}[/yellow]"
)

return events


Expand Down
26 changes: 24 additions & 2 deletions tests/test_sessionlog.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from __future__ import annotations

from copilot_experiments.sessionlog import parse_metrics
from pathlib import Path

from copilot_experiments.sessionlog import load_events, parse_metrics


def _events():
Expand Down Expand Up @@ -61,7 +63,7 @@ def test_parse_metrics_empty():
# --------------------------------------------------------------------------- #


def test_load_events_skips_blank_and_invalid_lines(tmp_path):
def test_load_events_skips_blank_and_invalid_lines(tmp_path, capsys):
from copilot_experiments.sessionlog import load_events

path = tmp_path / "events.jsonl"
Expand All @@ -72,6 +74,9 @@ def test_load_events_skips_blank_and_invalid_lines(tmp_path):
events = load_events(path)
assert [e["type"] for e in events] == ["session.start", "user.message"]

out, err = capsys.readouterr()
assert "Skipped 1 malformed JSON line(s)" in err


def test_load_events_missing_file_returns_empty(tmp_path):
from copilot_experiments.sessionlog import load_events
Expand Down Expand Up @@ -121,3 +126,20 @@ def test_compaction_start_tracks_peak_without_shutdown():
econ = extract_economics(events)
assert econ.total_tokens is None # no shutdown -> no authoritative totals
assert econ.peak_context_tokens == 9500 # 1000 + 8000 + 500


def test_load_events_with_malformed_lines(tmp_path: Path, capsys):
events_file = tmp_path / "events.jsonl"
events_file.write_text(
'{"type": "session.start"}\n'
'{"type": "malformed"\n' # bad json
'{"type": "session.end"}\n',
encoding="utf-8",
)
events = load_events(events_file)
assert len(events) == 2
assert events[0]["type"] == "session.start"
assert events[1]["type"] == "session.end"

out, err = capsys.readouterr()
assert "Skipped 1 malformed JSON line(s)" in err
Loading