From 5cbdcab96aa34393756da9dae21aa1109f0a4ce7 Mon Sep 17 00:00:00 2001 From: Ehsan-hash Date: Thu, 15 Oct 2026 23:06:52 -0700 Subject: [PATCH] fix(codex): retain reported reasoning token usage --- research/agents/codex_transport.py | 1 + tests/test_codex_request_provenance.py | 44 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/research/agents/codex_transport.py b/research/agents/codex_transport.py index 8d3cef5..d65a4ca 100644 --- a/research/agents/codex_transport.py +++ b/research/agents/codex_transport.py @@ -90,6 +90,7 @@ def complete(client, messages, json_mode=True, max_output_tokens=None): details.get('reason')=='max_output_tokens') if usage is not None: normalized={k:usage.get(k) for k in ('input_tokens','cached_input_tokens','output_tokens')} + normalized['reasoning_tokens'] = usage.get('reasoning_output_tokens') normalized.update(cost=None,cost_source='codex_subscription_unpriced') client._event('after',reservation=reservation,usage=normalized,status='completed' if result.returncode==0 else 'failed',outcome='response_received') else: diff --git a/tests/test_codex_request_provenance.py b/tests/test_codex_request_provenance.py index 56c4385..c0c4cd7 100644 --- a/tests/test_codex_request_provenance.py +++ b/tests/test_codex_request_provenance.py @@ -3,6 +3,8 @@ from types import SimpleNamespace from pathlib import Path +import pytest + from research.agents.schemas import json_action_schema, decode_tool_call from research.agents.provider import ModelClient @@ -55,3 +57,45 @@ def child(argv, **kwargs): monkeypatch.setattr(codex_transport.shutil, 'which', lambda value: '/verified/test/codex') monkeypatch.setattr(codex_transport.subprocess, 'run', child) client.complete(messages) + + +_MISSING = object() + + +@pytest.mark.parametrize( + 'reported_reasoning,expected_reasoning', + [(42, 42), (0, 0), (_MISSING, None)], + ids=['reported', 'explicit-zero', 'absent-unknown'], +) +def test_codex_usage_preserves_reported_reasoning_tokens(monkeypatch, reported_reasoning, expected_reasoning): + from research.agents import codex_transport + + provider = {'id': 'test-only', 'kind': 'codex_cli', 'base_url': 'http://127.0.0.1', + 'model': 'gpt-6-luna', 'config': {'reasoning_effort': 'high'}} + events = [] + + def record(event): + events.append(event) + return len(events) + + client = ModelClient(provider, request_guard=record) + usage = {'input_tokens': 100, 'cached_input_tokens': 20, 'output_tokens': 50} + if reported_reasoning is not _MISSING: + usage['reasoning_output_tokens'] = reported_reasoning + + def child(argv, **kwargs): + return SimpleNamespace(returncode=0, stdout='\n'.join(json.dumps(event) for event in [ + {'type': 'item.completed', 'item': {'type': 'agent_message', 'text': '{"tool":"finish","arguments":{}}'}}, + {'type': 'turn.completed', 'usage': usage}, + ]), stderr='') + + monkeypatch.setattr(codex_transport.shutil, 'which', lambda value: '/verified/test/codex') + monkeypatch.setattr(codex_transport.subprocess, 'run', child) + + result = client.complete([{'role': 'user', 'content': 'Return the finish action'}]) + + expected = {'input_tokens': 100, 'cached_input_tokens': 20, 'output_tokens': 50, + 'reasoning_tokens': expected_reasoning, 'cost': None, + 'cost_source': 'codex_subscription_unpriced'} + assert result['usage'] == expected + assert events[-1]['usage'] == expected