chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import uvicorn
|
||||
|
||||
try:
|
||||
import tomllib
|
||||
except ModuleNotFoundError: # pragma: no cover
|
||||
import tomli as tomllib # type: ignore[no-redef]
|
||||
|
||||
from headroom.cli import init as init_cli
|
||||
from headroom.install.models import ConfigScope, DeploymentManifest
|
||||
from headroom.providers.codex import build_launch_env, proxy_base_url
|
||||
from headroom.providers.codex.install import apply_provider_scope, build_install_env
|
||||
from headroom.proxy.server import ProxyConfig, create_app
|
||||
|
||||
|
||||
def _free_port() -> int:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.bind(("127.0.0.1", 0))
|
||||
return int(sock.getsockname()[1])
|
||||
|
||||
|
||||
class _MockOpenAIServer(ThreadingHTTPServer):
|
||||
allow_reuse_address = True
|
||||
daemon_threads = True
|
||||
|
||||
def __init__(self, server_address: tuple[str, int]) -> None:
|
||||
super().__init__(server_address, _MockOpenAIHandler)
|
||||
self.requests: list[dict[str, object]] = []
|
||||
|
||||
|
||||
class _MockOpenAIHandler(BaseHTTPRequestHandler):
|
||||
protocol_version = "HTTP/1.1"
|
||||
|
||||
def log_message(self, format: str, *args: object) -> None: # noqa: A003
|
||||
return
|
||||
|
||||
def _write_json(self, status_code: int, payload: dict[str, object]) -> None:
|
||||
body = json.dumps(payload).encode("utf-8")
|
||||
self.send_response(status_code)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def _record(self, body: dict[str, object] | None = None) -> None:
|
||||
server = self.server
|
||||
assert isinstance(server, _MockOpenAIServer)
|
||||
server.requests.append(
|
||||
{
|
||||
"method": self.command,
|
||||
"path": self.path,
|
||||
"authorization": self.headers.get("Authorization"),
|
||||
"body": body,
|
||||
}
|
||||
)
|
||||
|
||||
def do_GET(self) -> None: # noqa: N802
|
||||
self._record()
|
||||
if self.path == "/v1/models":
|
||||
self._write_json(
|
||||
200,
|
||||
{
|
||||
"object": "list",
|
||||
"data": [{"id": "gpt-4o-mini", "object": "model", "owned_by": "openai"}],
|
||||
},
|
||||
)
|
||||
return
|
||||
self._write_json(404, {"error": {"message": "not found"}})
|
||||
|
||||
def do_POST(self) -> None: # noqa: N802
|
||||
length = int(self.headers.get("Content-Length", "0"))
|
||||
raw_body = self.rfile.read(length) if length else b""
|
||||
payload = json.loads(raw_body.decode("utf-8") or "{}")
|
||||
self._record(body=payload)
|
||||
if self.path == "/v1/chat/completions":
|
||||
self._write_json(
|
||||
200,
|
||||
{
|
||||
"id": "chatcmpl-test",
|
||||
"object": "chat.completion",
|
||||
"created": 0,
|
||||
"model": payload.get("model", "gpt-4o-mini"),
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"finish_reason": "stop",
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "mock completion from upstream",
|
||||
},
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 12,
|
||||
"completion_tokens": 5,
|
||||
"total_tokens": 17,
|
||||
},
|
||||
},
|
||||
)
|
||||
return
|
||||
self._write_json(404, {"error": {"message": "not found"}})
|
||||
|
||||
|
||||
class _ProxyThread:
|
||||
def __init__(self, port: int, upstream_port: int) -> None:
|
||||
app = create_app(
|
||||
ProxyConfig(
|
||||
host="127.0.0.1",
|
||||
port=port,
|
||||
optimize=False,
|
||||
cache_enabled=False,
|
||||
rate_limit_enabled=False,
|
||||
openai_api_url=f"http://127.0.0.1:{upstream_port}",
|
||||
)
|
||||
)
|
||||
config = uvicorn.Config(
|
||||
app,
|
||||
host="127.0.0.1",
|
||||
port=port,
|
||||
log_level="warning",
|
||||
loop="asyncio",
|
||||
lifespan="on",
|
||||
ws="none",
|
||||
)
|
||||
self.server = uvicorn.Server(config)
|
||||
self.thread = threading.Thread(target=self.server.run, name="codex-proxy", daemon=True)
|
||||
|
||||
def start(self) -> None:
|
||||
self.thread.start()
|
||||
deadline = time.perf_counter() + 10.0
|
||||
while time.perf_counter() < deadline:
|
||||
if self.server.started:
|
||||
return
|
||||
time.sleep(0.05)
|
||||
raise RuntimeError("proxy failed to start within 10s")
|
||||
|
||||
def stop(self) -> None:
|
||||
self.server.should_exit = True
|
||||
self.thread.join(timeout=10.0)
|
||||
|
||||
|
||||
@dataclass
|
||||
class _CodexProxyStack:
|
||||
proxy_port: int
|
||||
upstream: _MockOpenAIServer
|
||||
proxy: _ProxyThread
|
||||
|
||||
@property
|
||||
def base_url(self) -> str:
|
||||
return proxy_base_url(self.proxy_port)
|
||||
|
||||
@property
|
||||
def stats_url(self) -> str:
|
||||
return f"http://127.0.0.1:{self.proxy_port}/stats"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def codex_proxy_stack(tmp_path_factory: pytest.TempPathFactory) -> Iterator[_CodexProxyStack]:
|
||||
temp_home = tmp_path_factory.mktemp("codex-proxy-home")
|
||||
previous_env = {name: os.environ.get(name) for name in ("HEADROOM_REQUIRE_RUST_CORE", "HOME")}
|
||||
os.environ["HEADROOM_REQUIRE_RUST_CORE"] = "false"
|
||||
os.environ["HOME"] = str(temp_home)
|
||||
|
||||
upstream_port = _free_port()
|
||||
upstream = _MockOpenAIServer(("127.0.0.1", upstream_port))
|
||||
upstream_thread = threading.Thread(target=upstream.serve_forever, daemon=True)
|
||||
upstream_thread.start()
|
||||
|
||||
proxy_port = _free_port()
|
||||
proxy = _ProxyThread(proxy_port, upstream_port)
|
||||
proxy.start()
|
||||
|
||||
try:
|
||||
_wait_for_stats(f"http://127.0.0.1:{proxy_port}/stats")
|
||||
yield _CodexProxyStack(proxy_port=proxy_port, upstream=upstream, proxy=proxy)
|
||||
finally:
|
||||
proxy.stop()
|
||||
upstream.shutdown()
|
||||
upstream_thread.join(timeout=5.0)
|
||||
for name, value in previous_env.items():
|
||||
if value is None:
|
||||
os.environ.pop(name, None)
|
||||
else:
|
||||
os.environ[name] = value
|
||||
|
||||
|
||||
def _wait_for_stats(url: str) -> None:
|
||||
deadline = time.time() + 10.0
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
response = httpx.get(url, timeout=2.0)
|
||||
if response.status_code == 200:
|
||||
return
|
||||
except Exception: # pragma: no cover - best effort poll
|
||||
pass
|
||||
time.sleep(0.1)
|
||||
raise RuntimeError(f"Timed out waiting for {url}")
|
||||
|
||||
|
||||
def _model_count(stats_url: str, model: str) -> int:
|
||||
response = httpx.get(stats_url, timeout=5.0)
|
||||
response.raise_for_status()
|
||||
payload = response.json()
|
||||
return int(payload["requests"]["by_model"].get(model, 0))
|
||||
|
||||
|
||||
def _send_probe(base_url: str, *, model: str, content: str) -> dict[str, object]:
|
||||
response = httpx.post(
|
||||
f"{base_url}/chat/completions",
|
||||
headers={"Authorization": "Bearer test-key"},
|
||||
json={
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": content}],
|
||||
},
|
||||
timeout=10.0,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def _assert_delivery(
|
||||
stack: _CodexProxyStack,
|
||||
*,
|
||||
base_url: str,
|
||||
model: str,
|
||||
content: str,
|
||||
) -> None:
|
||||
before = _model_count(stack.stats_url, model)
|
||||
payload = _send_probe(base_url, model=model, content=content)
|
||||
|
||||
deadline = time.time() + 10.0
|
||||
while time.time() < deadline:
|
||||
if _model_count(stack.stats_url, model) >= before + 1:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
raise AssertionError(f"Headroom never recorded model {model!r} in /stats")
|
||||
|
||||
assert payload["choices"][0]["message"]["content"] == "mock completion from upstream"
|
||||
assert any(
|
||||
item["path"] == "/v1/chat/completions"
|
||||
and isinstance(item.get("body"), dict)
|
||||
and item["body"].get("model") == model
|
||||
and item["body"].get("messages") == [{"role": "user", "content": content}]
|
||||
for item in stack.upstream.requests
|
||||
)
|
||||
|
||||
|
||||
def _codex_base_url_from_config(path: Path) -> str:
|
||||
parsed = tomllib.loads(path.read_text(encoding="utf-8"))
|
||||
assert parsed["model_provider"] == "headroom"
|
||||
return str(parsed["model_providers"]["headroom"]["base_url"])
|
||||
|
||||
|
||||
def _manifest(tmp_path: Path, *, port: int) -> DeploymentManifest:
|
||||
return DeploymentManifest(
|
||||
profile="default",
|
||||
preset="persistent-service",
|
||||
runtime_kind="python",
|
||||
supervisor_kind="service",
|
||||
scope=ConfigScope.PROVIDER.value,
|
||||
provider_mode="manual",
|
||||
targets=["codex"],
|
||||
port=port,
|
||||
host="127.0.0.1",
|
||||
backend="openai",
|
||||
memory_db_path=str(tmp_path / "memory.db"),
|
||||
tool_envs={"codex": {"OPENAI_BASE_URL": proxy_base_url(port)}},
|
||||
)
|
||||
|
||||
|
||||
def test_codex_proxy_base_url_and_launch_env() -> None:
|
||||
env, lines = build_launch_env(9999, {"OPENAI_API_KEY": "sk-test"})
|
||||
|
||||
assert proxy_base_url(9999) == "http://127.0.0.1:9999/v1"
|
||||
assert env["OPENAI_API_KEY"] == "sk-test"
|
||||
assert env["OPENAI_BASE_URL"] == "http://127.0.0.1:9999/v1"
|
||||
assert lines == ["OPENAI_BASE_URL=http://127.0.0.1:9999/v1"]
|
||||
|
||||
|
||||
def test_codex_launch_env_routes_messages_through_headroom(
|
||||
codex_proxy_stack: _CodexProxyStack,
|
||||
) -> None:
|
||||
env, _ = build_launch_env(codex_proxy_stack.proxy_port, {"OPENAI_API_KEY": "sk-test"})
|
||||
|
||||
_assert_delivery(
|
||||
codex_proxy_stack,
|
||||
base_url=str(env["OPENAI_BASE_URL"]),
|
||||
model="wrap-launch-delivery-probe",
|
||||
content="Verify temporary launch env traffic reaches Headroom.",
|
||||
)
|
||||
|
||||
|
||||
def test_codex_install_env_routes_messages_through_headroom(
|
||||
codex_proxy_stack: _CodexProxyStack,
|
||||
) -> None:
|
||||
env = build_install_env(port=codex_proxy_stack.proxy_port, backend="ignored")
|
||||
|
||||
assert env == {"OPENAI_BASE_URL": codex_proxy_stack.base_url}
|
||||
_assert_delivery(
|
||||
codex_proxy_stack,
|
||||
base_url=env["OPENAI_BASE_URL"],
|
||||
model="persistent-install-delivery-probe",
|
||||
content="Verify persistent install env traffic reaches Headroom.",
|
||||
)
|
||||
|
||||
|
||||
def test_init_codex_config_routes_messages_through_headroom(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
codex_proxy_stack: _CodexProxyStack,
|
||||
) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
init_cli._init_codex(
|
||||
global_scope=False,
|
||||
profile="init-local-demo",
|
||||
port=codex_proxy_stack.proxy_port,
|
||||
)
|
||||
|
||||
config_path = tmp_path / ".codex" / "config.toml"
|
||||
content = config_path.read_text(encoding="utf-8")
|
||||
assert 'env_key = "OPENAI_API_KEY"' not in content
|
||||
# Bug 3 (#406): requires_openai_auth must be absent from headroom provider blocks.
|
||||
assert "requires_openai_auth" not in content, (
|
||||
f"requires_openai_auth must not appear in init-generated Codex config:\n{content}"
|
||||
)
|
||||
|
||||
_assert_delivery(
|
||||
codex_proxy_stack,
|
||||
base_url=_codex_base_url_from_config(config_path),
|
||||
model="init-config-delivery-probe",
|
||||
content="Verify init-generated Codex config sends traffic to Headroom.",
|
||||
)
|
||||
|
||||
|
||||
def test_provider_scope_codex_config_routes_messages_through_headroom(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
codex_proxy_stack: _CodexProxyStack,
|
||||
) -> None:
|
||||
config_path = tmp_path / "config.toml"
|
||||
config_path.write_text('model = "gpt-4o"\n', encoding="utf-8")
|
||||
monkeypatch.setattr("headroom.providers.codex.install.codex_config_path", lambda: config_path)
|
||||
|
||||
mutation = apply_provider_scope(_manifest(tmp_path, port=codex_proxy_stack.proxy_port))
|
||||
|
||||
assert mutation is not None
|
||||
content = config_path.read_text(encoding="utf-8")
|
||||
assert 'env_key = "OPENAI_API_KEY"' not in content
|
||||
assert 'model_provider = "headroom"' in content
|
||||
|
||||
_assert_delivery(
|
||||
codex_proxy_stack,
|
||||
base_url=_codex_base_url_from_config(config_path),
|
||||
model="provider-scope-delivery-probe",
|
||||
content="Verify persistent provider config sends traffic to Headroom.",
|
||||
)
|
||||
Reference in New Issue
Block a user