Skip to content
Open
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
19 changes: 19 additions & 0 deletions tests/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,25 @@ def user(content, **flags):
assert trace.claude_prompt_count(p) == 3


def test_a_claude_background_task_notification_is_no_prompt(tmp_path):
"""Claude Code tells the model that a background task finished in a `user` entry of its
own, `origin.kind` "task-notification" (2.1.317). The person typed one prompt here. The
notification still starts a turn of its own: the model answers it."""
note = {"type": "user", "promptSource": "system", "turnOrigin": "task_notification",
"origin": {"kind": "task-notification", "producer": "session-task"},
"message": {"role": "user", "content": (
"<task-notification>\n<task-id>b1</task-id>\n<status>completed</status>\n"
"<summary>Background command \"pytest\" completed (exit code 0)</summary>\n"
"</task-notification>")}}
p = _jsonl(tmp_path / "c.jsonl", [
{"type": "user", "message": {"role": "user", "content": "run the tests in the background"}},
{"type": "assistant", "message": {"role": "assistant", "content": "started"}},
note,
{"type": "assistant", "message": {"role": "assistant", "content": "they pass"}}])
assert trace.claude_prompt_count(p) == 1
assert len(list(trace.claude_prompt_turns(p))) == 2


def test_a_codex_session_counts_one_prompt_per_turn(tmp_path):
turn = [{"type": "turn_context", "payload": {"turn_id": "t"}},
{"type": "event_msg", "payload": {"type": "task_started"}},
Expand Down
12 changes: 10 additions & 2 deletions tokeneconomizer/trace/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,23 @@ def _is_prompt(ev):
"[Request interrupted"))


def _is_notification(ev):
"""Is this entry Claude Code telling the model that a background task finished? It starts a
turn, as a prompt does, but nobody typed it (2.1.317: `origin.kind` "task-notification")."""
origin = ev.get("origin")
return isinstance(origin, dict) and origin.get("kind") == "task-notification"


def claude_prompt_count(transcript_path):
"""How many prompts the person gave a Claude Code session (`_is_prompt`)."""
"""How many prompts the person gave a Claude Code session (`_is_prompt`, less the
background-task notifications)."""
n = 0
for line in _lines(transcript_path):
try:
ev = json.loads(line)
except ValueError:
continue
n += isinstance(ev, dict) and _is_prompt(ev)
n += isinstance(ev, dict) and _is_prompt(ev) and not _is_notification(ev)
return n


Expand Down