0ef5fcb1c5
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
CI / lint (push) Has been cancelled
CI / build-wheel (push) Has been cancelled
CI / build-wheel-windows (push) Has been cancelled
CI / prefetch-model (push) Has been cancelled
CI / test-dashboard-ui (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test-extras (push) Has been cancelled
CI / test-agno (push) Has been cancelled
CI / build (push) Has been cancelled
CI / workflow-validation (push) Has been cancelled
CI / docker-native-e2e (push) Has been cancelled
CI / windows-native-wrapper (push) Has been cancelled
CI / macos-native-wrapper (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / promote-latest (push) Has been cancelled
Init Native E2E / init-native (macos-latest, claude) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, codex) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, copilot) (push) Has been cancelled
Install Native E2E / install-native (macos-latest) (push) Has been cancelled
Wrap Native E2E / wrap-native (macos-latest) (push) Has been cancelled
673 lines
21 KiB
Python
673 lines
21 KiB
Python
from __future__ import annotations
|
|
|
|
import threading
|
|
from types import MethodType, SimpleNamespace
|
|
|
|
from headroom.proxy.handlers import openai as openai_handler
|
|
from headroom.proxy.handlers.openai import OpenAIHandlerMixin
|
|
from headroom.transforms.compression_units import UnitCompressionResult
|
|
from headroom.transforms.content_router import (
|
|
CompressionStrategy,
|
|
ContentRouter,
|
|
RouterCompressionResult,
|
|
)
|
|
|
|
|
|
class TokenCounter:
|
|
def count_text(self, text: str) -> int:
|
|
return len(text.split())
|
|
|
|
|
|
def _handler_with_router(router: ContentRouter) -> OpenAIHandlerMixin:
|
|
handler = OpenAIHandlerMixin()
|
|
handler.openai_pipeline = SimpleNamespace(transforms=[router])
|
|
handler.openai_provider = SimpleNamespace(
|
|
get_token_counter=lambda _model: TokenCounter(),
|
|
)
|
|
return handler
|
|
|
|
|
|
def test_openai_responses_unit_parallelism_env_defaults_and_clamps(monkeypatch):
|
|
monkeypatch.delenv("HEADROOM_TOOL_OUTPUT_COMPRESSION_PARALLELISM", raising=False)
|
|
assert openai_handler._openai_responses_unit_parallelism() == 4
|
|
|
|
monkeypatch.setenv("HEADROOM_TOOL_OUTPUT_COMPRESSION_PARALLELISM", "bad")
|
|
assert openai_handler._openai_responses_unit_parallelism() == 4
|
|
|
|
monkeypatch.setenv("HEADROOM_TOOL_OUTPUT_COMPRESSION_PARALLELISM", "0")
|
|
assert openai_handler._openai_responses_unit_parallelism() == 1
|
|
|
|
monkeypatch.setenv("HEADROOM_TOOL_OUTPUT_COMPRESSION_PARALLELISM", "999")
|
|
assert openai_handler._openai_responses_unit_parallelism() == 16
|
|
|
|
|
|
def test_openai_responses_cached_unit_handles_results_without_router_result():
|
|
result = UnitCompressionResult(
|
|
original="original",
|
|
compressed="compressed",
|
|
modified=True,
|
|
tokens_before=2,
|
|
tokens_after=1,
|
|
tokens_saved=1,
|
|
transforms_applied=[],
|
|
strategy="none",
|
|
router_result=None,
|
|
)
|
|
|
|
assert openai_handler._openai_responses_result_with_cache_hit(result) is result
|
|
|
|
|
|
def test_openai_responses_unit_cache_evicts_oldest_entry(monkeypatch):
|
|
monkeypatch.setattr(openai_handler, "_OPENAI_RESPONSES_UNIT_CACHE_MAX_ENTRIES", 1)
|
|
handler = OpenAIHandlerMixin()
|
|
first = UnitCompressionResult(
|
|
original="first",
|
|
compressed="first compressed",
|
|
modified=True,
|
|
tokens_before=2,
|
|
tokens_after=1,
|
|
tokens_saved=1,
|
|
transforms_applied=[],
|
|
strategy="none",
|
|
router_result=None,
|
|
)
|
|
second = UnitCompressionResult(
|
|
original="second",
|
|
compressed="second compressed",
|
|
modified=True,
|
|
tokens_before=2,
|
|
tokens_after=1,
|
|
tokens_saved=1,
|
|
transforms_applied=[],
|
|
strategy="none",
|
|
router_result=None,
|
|
)
|
|
|
|
handler._store_openai_responses_cached_unit("first", first)
|
|
handler._store_openai_responses_cached_unit("second", second)
|
|
|
|
assert handler._get_openai_responses_cached_unit("first") is None
|
|
assert handler._get_openai_responses_cached_unit("second") is second
|
|
|
|
|
|
def test_openai_responses_adapter_compresses_only_live_text_slots():
|
|
router = ContentRouter()
|
|
|
|
def compress(self, content: str, **_kwargs):
|
|
return RouterCompressionResult(
|
|
compressed="kept words",
|
|
original=content,
|
|
strategy_used=CompressionStrategy.KOMPRESS,
|
|
)
|
|
|
|
router.compress = MethodType(compress, router)
|
|
handler = _handler_with_router(router)
|
|
long_text = " ".join(f"word{i}" for i in range(180))
|
|
payload = {
|
|
"model": "gpt-5",
|
|
"input": [
|
|
{"type": "reasoning", "encrypted_content": long_text},
|
|
{"type": "function_call", "arguments": long_text},
|
|
{"type": "local_shell_call_output", "call_id": "c1", "output": long_text},
|
|
{
|
|
"type": "message",
|
|
"role": "user",
|
|
"content": [{"type": "input_text", "text": long_text}],
|
|
},
|
|
],
|
|
}
|
|
|
|
new_payload, modified, saved, transforms, units_by_category, strategy_chain, _attempted = (
|
|
handler._compress_openai_responses_live_text_units_with_router(
|
|
payload,
|
|
model="gpt-5",
|
|
request_id="req_test",
|
|
)
|
|
)
|
|
|
|
assert modified is True
|
|
assert saved > 0
|
|
assert new_payload["input"][0]["encrypted_content"] == long_text
|
|
assert new_payload["input"][1]["arguments"] == long_text
|
|
assert new_payload["input"][2]["output"] == "kept words"
|
|
assert new_payload["input"][3]["content"][0]["text"] == long_text
|
|
assert any(t.startswith("router:openai:responses:") for t in transforms)
|
|
assert units_by_category == {"applied": 1}
|
|
assert strategy_chain == []
|
|
|
|
|
|
def test_openai_responses_adapter_compresses_custom_tool_call_output():
|
|
router = ContentRouter()
|
|
|
|
def compress(self, content: str, **_kwargs):
|
|
return RouterCompressionResult(
|
|
compressed="custom output summary",
|
|
original=content,
|
|
strategy_used=CompressionStrategy.KOMPRESS,
|
|
)
|
|
|
|
router.compress = MethodType(compress, router)
|
|
handler = _handler_with_router(router)
|
|
long_text = " ".join(f"word{i}" for i in range(180))
|
|
payload = {
|
|
"model": "gpt-5",
|
|
"input": [
|
|
{
|
|
"type": "custom_tool_call_output",
|
|
"call_id": "c1",
|
|
"output": long_text,
|
|
}
|
|
],
|
|
}
|
|
|
|
new_payload, modified, saved, transforms, units_by_category, strategy_chain, _attempted = (
|
|
handler._compress_openai_responses_live_text_units_with_router(
|
|
payload,
|
|
model="gpt-5",
|
|
request_id="req_test",
|
|
)
|
|
)
|
|
|
|
assert modified is True
|
|
assert saved > 0
|
|
assert new_payload["input"][0]["output"] == "custom output summary"
|
|
assert "router:openai:responses:custom_tool_call_output:kompress" in transforms
|
|
assert units_by_category == {"applied": 1}
|
|
assert strategy_chain == []
|
|
|
|
|
|
def test_openai_responses_adapter_reuses_exact_tool_output_cache():
|
|
router = ContentRouter()
|
|
calls = {"count": 0}
|
|
|
|
def compress(self, content: str, **_kwargs):
|
|
calls["count"] += 1
|
|
return RouterCompressionResult(
|
|
compressed="cached output summary",
|
|
original=content,
|
|
strategy_used=CompressionStrategy.KOMPRESS,
|
|
)
|
|
|
|
router.compress = MethodType(compress, router)
|
|
handler = _handler_with_router(router)
|
|
long_text = " ".join(f"word{i}" for i in range(180))
|
|
|
|
payload_one = {
|
|
"model": "gpt-5",
|
|
"input": [
|
|
{"type": "local_shell_call_output", "call_id": "c1", "output": long_text},
|
|
],
|
|
}
|
|
payload_two = {
|
|
"model": "gpt-5",
|
|
"input": [
|
|
{"type": "message", "role": "user", "content": "changed envelope"},
|
|
{"type": "local_shell_call_output", "call_id": "c2", "output": long_text},
|
|
],
|
|
}
|
|
|
|
new_payload_one, modified_one, saved_one, *_ = (
|
|
handler._compress_openai_responses_live_text_units_with_router(
|
|
payload_one,
|
|
model="gpt-5",
|
|
request_id="req_cache_one",
|
|
)
|
|
)
|
|
new_payload_two, modified_two, saved_two, *_ = (
|
|
handler._compress_openai_responses_live_text_units_with_router(
|
|
payload_two,
|
|
model="gpt-5",
|
|
request_id="req_cache_two",
|
|
)
|
|
)
|
|
|
|
assert calls["count"] == 1
|
|
assert modified_one is True
|
|
assert modified_two is True
|
|
assert saved_one > 0
|
|
assert saved_two == saved_one
|
|
assert new_payload_one["input"][0]["output"] == "cached output summary"
|
|
assert new_payload_two["input"][1]["output"] == "cached output summary"
|
|
|
|
|
|
def test_openai_responses_adapter_reuses_identical_tool_output_in_same_request():
|
|
router = ContentRouter()
|
|
calls = {"count": 0}
|
|
|
|
def compress(self, content: str, **_kwargs):
|
|
calls["count"] += 1
|
|
return RouterCompressionResult(
|
|
compressed="same request cached summary",
|
|
original=content,
|
|
strategy_used=CompressionStrategy.KOMPRESS,
|
|
)
|
|
|
|
router.compress = MethodType(compress, router)
|
|
handler = _handler_with_router(router)
|
|
long_text = " ".join(f"word{i}" for i in range(180))
|
|
payload = {
|
|
"model": "gpt-5",
|
|
"input": [
|
|
{"type": "function_call_output", "call_id": "c1", "output": long_text},
|
|
{"type": "function_call_output", "call_id": "c2", "output": long_text},
|
|
],
|
|
}
|
|
|
|
new_payload, modified, saved, *_ = (
|
|
handler._compress_openai_responses_live_text_units_with_router(
|
|
payload,
|
|
model="gpt-5",
|
|
request_id="req_same_request_cache",
|
|
)
|
|
)
|
|
|
|
assert calls["count"] == 1
|
|
assert modified is True
|
|
assert saved > 0
|
|
assert [item["output"] for item in new_payload["input"]] == [
|
|
"same request cached summary",
|
|
"same request cached summary",
|
|
]
|
|
|
|
|
|
def test_openai_responses_adapter_parallelizes_cache_misses_preserving_order(monkeypatch):
|
|
monkeypatch.setenv("HEADROOM_TOOL_OUTPUT_COMPRESSION_PARALLELISM", "4")
|
|
router = ContentRouter()
|
|
lock = threading.Lock()
|
|
release = threading.Event()
|
|
active = {"count": 0, "max": 0}
|
|
|
|
def compress(self, content: str, **_kwargs):
|
|
with lock:
|
|
active["count"] += 1
|
|
active["max"] = max(active["max"], active["count"])
|
|
if active["count"] >= 2:
|
|
release.set()
|
|
release.wait(0.05)
|
|
try:
|
|
marker = content.rsplit(" marker", 1)[1]
|
|
return RouterCompressionResult(
|
|
compressed=f"summary marker{marker}",
|
|
original=content,
|
|
strategy_used=CompressionStrategy.KOMPRESS,
|
|
)
|
|
finally:
|
|
with lock:
|
|
active["count"] -= 1
|
|
|
|
router.compress = MethodType(compress, router)
|
|
handler = _handler_with_router(router)
|
|
|
|
def long_text(index: int) -> str:
|
|
return " ".join(f"word{index}_{j}" for j in range(180)) + f" marker{index}"
|
|
|
|
payload = {
|
|
"model": "gpt-5",
|
|
"input": [
|
|
{
|
|
"type": "local_shell_call_output",
|
|
"call_id": f"c{i}",
|
|
"output": long_text(i),
|
|
}
|
|
for i in range(4)
|
|
],
|
|
}
|
|
|
|
new_payload, modified, saved, *_ = (
|
|
handler._compress_openai_responses_live_text_units_with_router(
|
|
payload,
|
|
model="gpt-5",
|
|
request_id="req_parallel",
|
|
)
|
|
)
|
|
|
|
assert active["max"] >= 2
|
|
assert modified is True
|
|
assert saved > 0
|
|
assert [item["output"] for item in new_payload["input"]] == [
|
|
"summary marker0",
|
|
"summary marker1",
|
|
"summary marker2",
|
|
"summary marker3",
|
|
]
|
|
|
|
|
|
def test_openai_responses_adapter_accepts_empty_input_list():
|
|
router = ContentRouter()
|
|
handler = _handler_with_router(router)
|
|
payload = {"model": "gpt-5", "input": [], "tools": []}
|
|
|
|
new_payload, modified, saved, transforms, units_by_category, strategy_chain, _attempted = (
|
|
handler._compress_openai_responses_live_text_units_with_router(
|
|
payload,
|
|
model="gpt-5",
|
|
request_id="req_test",
|
|
)
|
|
)
|
|
|
|
assert new_payload == payload
|
|
assert modified is False
|
|
assert saved == 0
|
|
assert transforms == []
|
|
assert units_by_category == {}
|
|
assert strategy_chain == []
|
|
|
|
|
|
def test_openai_responses_adapter_preserves_headroom_retrieve_outputs():
|
|
router = ContentRouter()
|
|
|
|
def compress(self, content: str, **_kwargs):
|
|
return RouterCompressionResult(
|
|
compressed="compressed retrieve output",
|
|
original=content,
|
|
strategy_used=CompressionStrategy.KOMPRESS,
|
|
)
|
|
|
|
router.compress = MethodType(compress, router)
|
|
handler = _handler_with_router(router)
|
|
retrieved = " ".join(f"retrieved{i}" for i in range(180))
|
|
payload = {
|
|
"model": "gpt-5",
|
|
"input": [
|
|
{
|
|
"type": "function_call",
|
|
"call_id": "call_retrieve",
|
|
"name": "mcp__headroom__headroom_retrieve",
|
|
"arguments": "{}",
|
|
},
|
|
{
|
|
"type": "function_call_output",
|
|
"call_id": "call_retrieve",
|
|
"output": retrieved,
|
|
},
|
|
],
|
|
}
|
|
|
|
new_payload, modified, saved, transforms, units_by_category, strategy_chain, _attempted = (
|
|
handler._compress_openai_responses_live_text_units_with_router(
|
|
payload,
|
|
model="gpt-5",
|
|
request_id="req_test",
|
|
)
|
|
)
|
|
|
|
assert modified is False
|
|
assert saved == 0
|
|
assert transforms == []
|
|
assert new_payload == payload
|
|
assert units_by_category == {}
|
|
assert strategy_chain == []
|
|
|
|
|
|
def test_openai_responses_adapter_preserves_excluded_tool_outputs():
|
|
"""Regression for #940: outputs for HEADROOM_EXCLUDE_TOOLS tools stay raw.
|
|
|
|
The Responses path carries the tool name on the ``function_call`` item and
|
|
the originating ``call_id`` on the matching ``function_call_output``; the
|
|
adapter must correlate them and skip compression for excluded tools.
|
|
"""
|
|
router = ContentRouter()
|
|
router.config.exclude_tools = {"serena.find_symbol", "find_symbol"}
|
|
|
|
def compress(self, content: str, **_kwargs):
|
|
return RouterCompressionResult(
|
|
compressed="should not be used",
|
|
original=content,
|
|
strategy_used=CompressionStrategy.KOMPRESS,
|
|
)
|
|
|
|
router.compress = MethodType(compress, router)
|
|
handler = _handler_with_router(router)
|
|
output = " ".join(f"sym{i}" for i in range(180))
|
|
payload = {
|
|
"model": "gpt-5",
|
|
"input": [
|
|
{
|
|
"type": "function_call",
|
|
"call_id": "call_1",
|
|
"name": "serena.find_symbol",
|
|
"arguments": "{}",
|
|
},
|
|
{
|
|
"type": "function_call_output",
|
|
"call_id": "call_1",
|
|
"output": output,
|
|
},
|
|
],
|
|
}
|
|
|
|
new_payload, modified, saved, transforms, units_by_category, strategy_chain, _attempted = (
|
|
handler._compress_openai_responses_live_text_units_with_router(
|
|
payload,
|
|
model="gpt-5",
|
|
request_id="req_test",
|
|
)
|
|
)
|
|
|
|
assert modified is False
|
|
assert saved == 0
|
|
assert transforms == []
|
|
assert new_payload == payload
|
|
assert units_by_category == {}
|
|
assert strategy_chain == []
|
|
|
|
|
|
def test_openai_responses_adapter_losslessly_folds_excluded_grep_output():
|
|
"""Excluded tools skip *lossy* compression, but grep/log/json output is still
|
|
byte/data-losslessly compacted on the Responses path (matches chat/Anthropic).
|
|
"""
|
|
from headroom.transforms.lossless_compaction import search_unheading
|
|
|
|
router = ContentRouter()
|
|
router.config.exclude_tools = {"grep"}
|
|
handler = _handler_with_router(router)
|
|
grep_out = "".join(
|
|
f"src/mod_{f}.py:{ln}:some matching content on this line here\n"
|
|
for f in range(8)
|
|
for ln in range(6)
|
|
)
|
|
payload = {
|
|
"model": "gpt-5",
|
|
"input": [
|
|
{"type": "function_call", "call_id": "call_1", "name": "grep", "arguments": "{}"},
|
|
{"type": "function_call_output", "call_id": "call_1", "output": grep_out},
|
|
],
|
|
}
|
|
|
|
new_payload, modified, saved, transforms, _units, _chain, _attempted = (
|
|
handler._compress_openai_responses_live_text_units_with_router(
|
|
payload,
|
|
model="gpt-5",
|
|
request_id="req_test",
|
|
)
|
|
)
|
|
|
|
assert modified is True
|
|
assert saved >= 0 # token accounting never goes negative
|
|
assert "router:excluded:lossless" in transforms
|
|
folded = new_payload["input"][1]["output"]
|
|
assert len(folded) < len(grep_out) # byte-smaller (real guarantee)
|
|
assert search_unheading(folded) == grep_out # byte-exact recovery
|
|
|
|
|
|
def test_openai_responses_adapter_excludes_tool_case_insensitively_with_debug(monkeypatch):
|
|
"""Excluded match is case-insensitive, and the debug path stays exercised.
|
|
|
|
The configured name is lowercase only; the call advertises a mixed-case
|
|
name, so the protection must hit via the lowercased fallback. Debug logging
|
|
is enabled so the protected-extraction debug record is also covered.
|
|
"""
|
|
monkeypatch.setattr(openai_handler, "_log_codex_compression_debug", lambda *_a, **_k: None)
|
|
router = ContentRouter()
|
|
router.config.exclude_tools = {"serena.find_symbol"}
|
|
|
|
def compress(self, content: str, **_kwargs):
|
|
return RouterCompressionResult(
|
|
compressed="should not be used",
|
|
original=content,
|
|
strategy_used=CompressionStrategy.KOMPRESS,
|
|
)
|
|
|
|
router.compress = MethodType(compress, router)
|
|
handler = _handler_with_router(router)
|
|
output = " ".join(f"sym{i}" for i in range(180))
|
|
payload = {
|
|
"model": "gpt-5",
|
|
"input": [
|
|
{
|
|
"type": "function_call",
|
|
"call_id": "call_1",
|
|
"name": "Serena.Find_Symbol",
|
|
"arguments": "{}",
|
|
},
|
|
{
|
|
"type": "function_call_output",
|
|
"call_id": "call_1",
|
|
"output": output,
|
|
},
|
|
],
|
|
}
|
|
|
|
new_payload, modified, saved, *_ = (
|
|
handler._compress_openai_responses_live_text_units_with_router(
|
|
payload,
|
|
model="gpt-5",
|
|
request_id="req_test",
|
|
)
|
|
)
|
|
|
|
assert modified is False
|
|
assert saved == 0
|
|
assert new_payload == payload
|
|
|
|
|
|
def test_openai_responses_adapter_compresses_non_excluded_tool_outputs():
|
|
"""Only excluded tools are protected; other tool outputs still compress."""
|
|
router = ContentRouter()
|
|
router.config.exclude_tools = {"serena.find_symbol"}
|
|
|
|
def compress(self, content: str, **_kwargs):
|
|
return RouterCompressionResult(
|
|
compressed="compressed tool output",
|
|
original=content,
|
|
strategy_used=CompressionStrategy.KOMPRESS,
|
|
)
|
|
|
|
router.compress = MethodType(compress, router)
|
|
handler = _handler_with_router(router)
|
|
output = " ".join(f"word{i}" for i in range(180))
|
|
payload = {
|
|
"model": "gpt-5",
|
|
"input": [
|
|
{
|
|
"type": "function_call",
|
|
"call_id": "call_1",
|
|
"name": "some.other_tool",
|
|
"arguments": "{}",
|
|
},
|
|
{
|
|
"type": "function_call_output",
|
|
"call_id": "call_1",
|
|
"output": output,
|
|
},
|
|
],
|
|
}
|
|
|
|
new_payload, modified, saved, transforms, units_by_category, strategy_chain, _attempted = (
|
|
handler._compress_openai_responses_live_text_units_with_router(
|
|
payload,
|
|
model="gpt-5",
|
|
request_id="req_test",
|
|
)
|
|
)
|
|
|
|
assert modified is True
|
|
assert saved > 0
|
|
assert new_payload["input"][1]["output"] == "compressed tool output"
|
|
assert "router:openai:responses:function_call_output:kompress" in transforms
|
|
assert units_by_category == {"applied": 1}
|
|
|
|
|
|
def test_openai_responses_adapter_keeps_small_and_opaque_items():
|
|
router = ContentRouter()
|
|
|
|
def compress(self, content: str, **_kwargs):
|
|
return RouterCompressionResult(
|
|
compressed="short",
|
|
original=content,
|
|
strategy_used=CompressionStrategy.KOMPRESS,
|
|
)
|
|
|
|
router.compress = MethodType(compress, router)
|
|
handler = _handler_with_router(router)
|
|
payload = {
|
|
"model": "gpt-5",
|
|
"input": [
|
|
{"type": "local_shell_call_output", "call_id": "c1", "output": "too small"},
|
|
{"type": "compaction", "encrypted_content": " ".join(["secret"] * 200)},
|
|
],
|
|
}
|
|
|
|
new_payload, modified, saved, transforms, units_by_category, strategy_chain, _attempted = (
|
|
handler._compress_openai_responses_live_text_units_with_router(
|
|
payload,
|
|
model="gpt-5",
|
|
request_id="req_test",
|
|
)
|
|
)
|
|
|
|
assert modified is False
|
|
assert saved == 0
|
|
assert transforms == []
|
|
assert new_payload == payload
|
|
assert units_by_category == {"size_floor": 1}
|
|
assert strategy_chain == []
|
|
|
|
|
|
def test_openai_responses_payload_routes_through_content_router_without_rust(
|
|
monkeypatch,
|
|
):
|
|
router = ContentRouter()
|
|
|
|
def compress(self, content: str, **_kwargs):
|
|
return RouterCompressionResult(
|
|
compressed="compressed fallback",
|
|
original=content,
|
|
strategy_used=CompressionStrategy.KOMPRESS,
|
|
)
|
|
|
|
router.compress = MethodType(compress, router)
|
|
handler = _handler_with_router(router)
|
|
|
|
import headroom._core as core
|
|
|
|
def rust_must_not_run(*_args, **_kwargs):
|
|
raise AssertionError("Responses payload compression should route through ContentRouter")
|
|
|
|
monkeypatch.setattr(core, "compress_openai_responses_live_zone", rust_must_not_run)
|
|
|
|
payload = {
|
|
"model": "gpt-5",
|
|
"input": [
|
|
{
|
|
"type": "local_shell_call_output",
|
|
"call_id": "c1",
|
|
"output": " ".join(f"word{i}" for i in range(180)),
|
|
}
|
|
],
|
|
}
|
|
|
|
new_payload, modified, saved, transforms, reason, _, _, _ = (
|
|
handler._compress_openai_responses_payload(
|
|
payload,
|
|
model="gpt-5",
|
|
request_id="req_router",
|
|
)
|
|
)
|
|
|
|
assert modified is True
|
|
assert saved > 0
|
|
assert reason is None
|
|
assert new_payload["input"][0]["output"] == "compressed fallback"
|
|
assert any(t.startswith("router:openai:responses:") for t in transforms)
|