headroom/tests/test_proxy_openai_responses_stream_ccr.py
Ben Younes a617455f02
fix(proxy): preserve chatgpt responses streaming (#2012)
## Description

Closes #1956

Keep ChatGPT OAuth `/v1/responses` requests streaming when CCR retrieve
tools are present. The buffered `stream:false` conversion is still used
for regular OpenAI Responses CCR requests, but ChatGPT Codex routing now
bypasses that conversion so the upstream receives the streaming request
shape it expects.

## Type of Change

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Documentation update
- [ ] Refactoring
- [ ] Performance improvement
- [ ] Test update
- [ ] Other

## Changes Made

- Extracted the OpenAI Responses CCR stream-buffering decision into a
small helper.
- Excluded ChatGPT OAuth/Codex-routed requests from the buffered
`stream:false` path.
- Added tests proving regular OpenAI CCR still buffers while ChatGPT
OAuth CCR remains streaming.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check`)
- [x] Formatting verified (`ruff format --check`)
- [ ] Manual testing performed

### Test Output

```text
$ python3 -m pytest tests/test_proxy_openai_responses_stream_ccr.py -q
collected 3 items

tests/test_proxy_openai_responses_stream_ccr.py ...                      [100%]

============================== 3 passed in 0.59s ===============================

$ .venv/bin/ruff check headroom/proxy/handlers/openai.py tests/test_proxy_openai_responses_stream_ccr.py
All checks passed!

$ .venv/bin/ruff format --check headroom/proxy/handlers/openai.py tests/test_proxy_openai_responses_stream_ccr.py
2 files already formatted
```

## Test verification (RED -> GREEN)

RED, with the ChatGPT OAuth guard temporarily removed from the buffering
decision:

```text
tests/test_proxy_openai_responses_stream_ccr.py .F.                      [100%]
FAILED tests/test_proxy_openai_responses_stream_ccr.py::test_responses_ccr_keeps_chatgpt_oauth_requests_streaming
E   AssertionError: assert not True
E    +  where True = _should_buffer(tools=[{'type': 'function', 'name': 'headroom_retrieve'}], is_chatgpt_auth=True)
```

GREEN, with this patch applied:

```text
tests/test_proxy_openai_responses_stream_ccr.py ...                      [100%]
============================== 3 passed in 0.59s ===============================
```

## Real Behavior Proof

- Environment: Linux, Python 3.12.3, pytest 9.1.1, ruff 0.14.14.
- Exact command / steps: Removed the `not is_chatgpt_auth` guard from
the CCR buffering decision, ran the targeted tests, restored the guard,
and reran the tests plus targeted ruff checks.
- Observed result: The ChatGPT OAuth streaming regression test fails
without the guard and passes with the guard, while regular OpenAI CCR
buffering remains covered.
- Not tested: Full `uv run pytest`, full-project `uv run ruff check .`,
full-project `uv run ruff format --check .`, and `uv run mypy headroom`
were not run locally; `uv run --extra dev ruff` attempted to build the
Rust extension in this worktree, so targeted checks used the existing
`.venv/bin/ruff`.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have added tests that prove my fix is effective
- [x] New and existing targeted tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules

## Screenshots (if applicable)

N/A

## Additional Notes

The existing buffered CCR path is preserved for non-ChatGPT OpenAI
Responses requests.
2026-07-11 00:10:02 -05:00

36 lines
1.1 KiB
Python

from __future__ import annotations
from headroom.ccr import CCR_TOOL_NAME
from headroom.proxy.handlers.openai import _should_buffer_openai_responses_stream_ccr
_TOOL_TYPE_FUNCTION = "function"
_UNRELATED_TOOL_NAME = "unrelated_tool"
def _ccr_tool() -> dict[str, str]:
return {"type": _TOOL_TYPE_FUNCTION, "name": CCR_TOOL_NAME}
def _unrelated_tool() -> dict[str, str]:
return {"type": _TOOL_TYPE_FUNCTION, "name": _UNRELATED_TOOL_NAME}
def _should_buffer(*, tools: list[dict[str, str]], is_chatgpt_auth: bool) -> bool:
return _should_buffer_openai_responses_stream_ccr(
stream=True,
ccr_response_handler_enabled=True,
tools=tools,
is_chatgpt_auth=is_chatgpt_auth,
)
def test_responses_ccr_buffers_streaming_openai_requests() -> None:
assert _should_buffer(tools=[_ccr_tool()], is_chatgpt_auth=False)
def test_responses_ccr_keeps_chatgpt_oauth_requests_streaming() -> None:
assert not _should_buffer(tools=[_ccr_tool()], is_chatgpt_auth=True)
def test_responses_ccr_ignores_requests_without_retrieve_tool() -> None:
assert not _should_buffer(tools=[_unrelated_tool()], is_chatgpt_auth=False)