fix(codex): record app-server token usage in session accounting

This commit is contained in:
JP Lew
2026-06-09 02:46:04 -07:00
committed by Teknium
parent 85852b71d8
commit cb4cc08b0a
4 changed files with 266 additions and 1 deletions
@@ -196,6 +196,40 @@ class TestRunTurn:
# turn_id propagated for downstream session-DB linkage
assert r.turn_id == "turn-fake-001"
def test_token_usage_notification_is_captured(self):
client = FakeClient()
client.queue_notification(
"thread/tokenUsage/updated",
threadId="thread-fake-001",
turnId="turn-fake-001",
tokenUsage={
"last": {
"totalTokens": 130,
"inputTokens": 80,
"cachedInputTokens": 20,
"outputTokens": 25,
"reasoningOutputTokens": 5,
},
"total": {
"totalTokens": 500,
"inputTokens": 300,
"cachedInputTokens": 75,
"outputTokens": 100,
"reasoningOutputTokens": 25,
},
"modelContextWindow": 200000,
},
)
client.queue_notification(
"turn/completed",
threadId="t",
turn={"id": "tu1", "status": "completed", "error": None},
)
r = make_session(client).run_turn("hi", turn_timeout=2.0)
assert r.token_usage_last["totalTokens"] == 130
assert r.token_usage_total["totalTokens"] == 500
assert r.model_context_window == 200000
def test_rich_content_turn_is_collapsed_to_text_payload(self):
client = FakeClient()
client.queue_notification(
@@ -84,6 +84,56 @@ class TestRunConversationCodexPath:
assert result["codex_thread_id"] == "thread-stub-1"
assert result["codex_turn_id"] == "turn-stub-1"
def test_codex_app_server_token_usage_updates_session_accounting(self, monkeypatch):
def fake_run_turn(self, user_input: str, **kwargs):
return TurnResult(
final_text="done",
projected_messages=[{"role": "assistant", "content": "done"}],
turn_id="turn-usage-1",
thread_id="thread-usage-1",
token_usage_last={
"totalTokens": 130,
"inputTokens": 80,
"cachedInputTokens": 20,
"outputTokens": 25,
"reasoningOutputTokens": 5,
},
model_context_window=200000,
)
monkeypatch.setattr(CodexAppServerSession, "run_turn", fake_run_turn)
monkeypatch.setattr(
CodexAppServerSession, "ensure_started", lambda self: "thread-usage-1"
)
agent = _make_codex_agent()
with patch.object(agent, "_spawn_background_review", return_value=None):
result = agent.run_conversation("hello")
assert result["api_calls"] == 1
assert result["prompt_tokens"] == 100
assert result["completion_tokens"] == 25
assert result["total_tokens"] == 130
assert result["input_tokens"] == 80
assert result["output_tokens"] == 25
assert result["cache_read_tokens"] == 20
assert result["cache_write_tokens"] == 0
assert result["reasoning_tokens"] == 5
assert result["last_prompt_tokens"] == 100
assert agent.session_api_calls == 1
assert agent.session_prompt_tokens == 100
assert agent.session_completion_tokens == 25
assert agent.session_total_tokens == 130
assert agent.session_input_tokens == 80
assert agent.session_output_tokens == 25
assert agent.session_cache_read_tokens == 20
assert agent.session_cache_write_tokens == 0
assert agent.session_reasoning_tokens == 5
assert agent.context_compressor.last_prompt_tokens == 100
assert agent.context_compressor.last_completion_tokens == 25
assert agent.context_compressor.last_total_tokens == 130
assert agent.context_compressor.context_length == 200000
def test_projected_messages_are_spliced(self, fake_session):
agent = _make_codex_agent()
with patch.object(agent, "_spawn_background_review", return_value=None):