## Problem
Fixes#820.
`headroom codex` traffic through `handle_openai_responses` never
produced waste signals: the path compresses via CompressionUnits (not
`TransformPipeline`, which is where waste detection lives), and the
minimal `messages` list it synthesises only covers `instructions` +
string-typed `input` — list-typed `input` (every real multi-turn Codex
session) is dropped entirely. Tool output never reached
`parse_messages`, so the dashboard "What Headroom Removed" stayed empty
and the new `reread` signal (#853/#854) was blind for Codex.
## Fix (telemetry-only)
1. **`_responses_input_to_waste_messages(instructions, input_data)`** —
converts a Responses payload to OpenAI-style messages for waste parsing
only. Tool output items (`function_call_output`,
`custom_tool_call_output`, `local_shell_call_output`,
`apply_patch_call_output`) become `role="tool"` messages (with
`tool_call_id`); `message` items keep their role and joined part text;
string/part-list `output` and `content` both handled.
2. **`handle_openai_responses`** parses that list behind the same >100
saved-token gate `TransformPipeline.apply` uses, fail-open, and threads
the result into the non-streaming `RequestOutcome` and the streaming
branch.
3. **`_stream_response` / `_finalize_stream_response`** gain an optional
`waste_signals` param passed through to `RequestOutcome.from_stream`
(which already supported it). Default `None` — the other callers are
unaffected.
4. `OPENAI_RESPONSES_OUTPUT_TYPES` now aliases the module-level
frozenset the converter uses (single source; usage is membership-only,
no behavior change).
The existing `role="tool"` parsing from #815 handles the rest:
tool_result blocks, waste flags, and reread grouping all apply.
## Tests
`tests/test_codex_responses_waste_signals.py` — 13 new tests covering
part-text extraction (string/part-list/non-text), conversion (roles
preserved, all four output item types, tool_call_id, skipped unusable
items, non-list input), and parsing (tool_result blocks + `json_bloat`
from `function_call_output`; identical outputs far apart count as
`reread`).
Local regression sweep: responses compression units, codex
routing/aliases/contract parity, responses bypass/compaction/T3-replay,
request outcome, all streaming suites — 168 tests green.
## Live proof
Mock `/v1/responses` upstream on a real port, proxy with
`optimize=True`; list-typed `input` with a large `function_call_output`
served twice (5 messages apart) plus compressible assistant bulk:
```
waste_signals: { "json_bloat": 20448, "reread": 8525, ... }
PROOF OK: codex responses waste visible
```
## Notes
- Sibling of #897 (Gemini functionResponse waste signals) — same bug
class from #813's matrix, independent code paths, no conflicts.
- The WS Responses path (`handle_openai_responses_ws`) still computes no
waste signals; left as a follow-up since its outcome plumbing differs.
Co-authored-by: integration-check <integration@local>