fix(auxiliary): enforce Codex Responses stream timeout

## Summary
- Forwards chat-completions `timeout` into the Codex Responses stream call.
- Adds total elapsed-time enforcement while the Responses stream is still yielding events.
- Closes the underlying client on timeout to unblock stalled streams, then raises `TimeoutError`.
- Adds focused tests for timeout forwarding and total timeout enforcement.

## Why
The Codex auxiliary adapter can be used by non-interactive auxiliary work such as context compression. If the stream keeps yielding progress-like events but never completes, SDK socket/read timeouts do not necessarily protect the full operation. This makes the CLI look stuck until the user force-interrupts the whole session.

This is a refreshed upstream-ready version of the earlier fork fix around `d3f08e9a0` / PR #3.

## Verification
- `python -m py_compile agent/auxiliary_client.py tests/agent/test_auxiliary_client.py`
- `python -m pytest -o addopts='' tests/agent/test_auxiliary_client.py::TestCodexAuxiliaryAdapterTimeout -q`
- `git diff --check`
This commit is contained in:
acc001k 2026-05-07 14:18:20 +02:00 committed by Teknium
parent fd13b7d2b9
commit 5533ad7644
2 changed files with 133 additions and 0 deletions

View file

@ -3,7 +3,9 @@
import json
import logging
import os
import time
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch, MagicMock, AsyncMock
import pytest
@ -24,6 +26,7 @@ from agent.auxiliary_client import (
_normalize_aux_provider,
_try_payment_fallback,
_resolve_auto,
_CodexCompletionsAdapter,
)
@ -1894,6 +1897,85 @@ class TestVisionAutoSkipsKimiCoding:
})
class TestCodexAuxiliaryAdapterTimeout:
def test_forwards_timeout_to_responses_stream(self):
class FakeStream:
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
return False
def __iter__(self):
return iter(())
def get_final_response(self):
return SimpleNamespace(
output=[SimpleNamespace(
type="message",
content=[SimpleNamespace(type="output_text", text="summary")],
)],
usage=None,
)
class FakeResponses:
def __init__(self):
self.kwargs = None
def stream(self, **kwargs):
self.kwargs = kwargs
return FakeStream()
fake_client = SimpleNamespace(responses=FakeResponses())
adapter = _CodexCompletionsAdapter(fake_client, "gpt-5.5")
response = adapter.create(
messages=[{"role": "user", "content": "summarize this"}],
timeout=12.5,
)
assert fake_client.responses.kwargs["timeout"] == 12.5
assert response.choices[0].message.content == "summary"
def test_enforces_total_timeout_while_stream_keeps_emitting_events(self):
class SlowAliveStream:
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
return False
def __iter__(self):
for _ in range(5):
time.sleep(0.03)
yield SimpleNamespace(type="response.in_progress")
def get_final_response(self):
return SimpleNamespace(
output=[SimpleNamespace(
type="message",
content=[SimpleNamespace(type="output_text", text="late")],
)],
usage=None,
)
class FakeResponses:
def stream(self, **kwargs):
return SlowAliveStream()
fake_client = SimpleNamespace(responses=FakeResponses(), close=lambda: None)
adapter = _CodexCompletionsAdapter(fake_client, "gpt-5.5")
started = time.monotonic()
with pytest.raises(TimeoutError):
adapter.create(
messages=[{"role": "user", "content": "summarize this"}],
timeout=0.05,
)
assert time.monotonic() - started < 0.14
# ---------------------------------------------------------------------------
# _build_call_kwargs — tool dedup at API boundary
# ---------------------------------------------------------------------------