2026-02-04 15:09:51 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-03-06 21:06:45 +08:00
|
|
|
from types import SimpleNamespace
|
2026-03-18 20:58:33 +08:00
|
|
|
from unittest.mock import AsyncMock
|
2026-03-06 21:06:45 +08:00
|
|
|
|
2026-03-04 10:00:38 +08:00
|
|
|
import jwt
|
|
|
|
|
import pytest
|
|
|
|
|
|
2026-03-06 21:06:45 +08:00
|
|
|
from src.api.handlers.base.request_builder import PassthroughRequestBuilder
|
2026-03-18 12:35:57 +08:00
|
|
|
from src.services.provider.behavior import get_provider_behavior
|
2026-02-04 15:09:51 +08:00
|
|
|
|
|
|
|
|
|
2026-03-18 12:35:57 +08:00
|
|
|
def test_codex_provider_behavior_has_no_runtime_envelope_or_variants() -> None:
|
|
|
|
|
behavior = get_provider_behavior(provider_type="codex", endpoint_sig="openai:cli")
|
2026-03-01 23:55:26 +08:00
|
|
|
|
2026-03-18 12:35:57 +08:00
|
|
|
assert behavior.envelope is None
|
|
|
|
|
assert behavior.same_format_variant is None
|
|
|
|
|
assert behavior.cross_format_variant is None
|
2026-03-01 23:55:26 +08:00
|
|
|
|
|
|
|
|
|
2026-03-18 20:58:33 +08:00
|
|
|
def test_openai_cli_normalizer_request_from_internal_codex_variant_preserves_store() -> (
|
|
|
|
|
None
|
|
|
|
|
):
|
|
|
|
|
from src.core.api_format.conversion.normalizers.openai_cli import (
|
|
|
|
|
OpenAICliNormalizer,
|
|
|
|
|
)
|
2026-03-01 23:55:26 +08:00
|
|
|
|
|
|
|
|
normalizer = OpenAICliNormalizer()
|
2026-03-18 20:58:33 +08:00
|
|
|
internal = normalizer.request_to_internal(
|
|
|
|
|
{"model": "gpt-test", "input": [], "store": True}
|
|
|
|
|
)
|
2026-03-01 23:55:26 +08:00
|
|
|
out = normalizer.request_from_internal(internal, target_variant="codex")
|
|
|
|
|
|
|
|
|
|
assert out["store"] is True
|
|
|
|
|
|
|
|
|
|
|
2026-03-18 20:58:33 +08:00
|
|
|
def test_openai_cli_normalizer_request_from_internal_codex_variant_does_not_inject_store() -> (
|
|
|
|
|
None
|
|
|
|
|
):
|
|
|
|
|
from src.core.api_format.conversion.normalizers.openai_cli import (
|
|
|
|
|
OpenAICliNormalizer,
|
|
|
|
|
)
|
2026-03-01 23:55:26 +08:00
|
|
|
|
|
|
|
|
normalizer = OpenAICliNormalizer()
|
|
|
|
|
internal = normalizer.request_to_internal({"model": "gpt-test", "input": []})
|
|
|
|
|
out = normalizer.request_from_internal(internal, target_variant="codex")
|
|
|
|
|
|
2026-03-18 12:35:57 +08:00
|
|
|
assert "store" not in out
|
2026-02-04 15:09:51 +08:00
|
|
|
|
2026-02-05 15:57:52 +08:00
|
|
|
|
2026-03-18 20:58:33 +08:00
|
|
|
def test_openai_cli_normalizer_codex_variant_keeps_instructions_missing_for_default_rule() -> (
|
|
|
|
|
None
|
|
|
|
|
):
|
2026-03-17 01:25:29 +08:00
|
|
|
from src.api.handlers.base.request_builder import apply_body_rules
|
2026-03-18 20:58:33 +08:00
|
|
|
from src.core.api_format.conversion.normalizers.openai_cli import (
|
|
|
|
|
OpenAICliNormalizer,
|
|
|
|
|
)
|
2026-03-17 01:25:29 +08:00
|
|
|
from src.core.api_format.metadata import CODEX_DEFAULT_BODY_RULES
|
|
|
|
|
|
|
|
|
|
normalizer = OpenAICliNormalizer()
|
|
|
|
|
internal = normalizer.request_to_internal({"model": "gpt-test", "input": []})
|
|
|
|
|
out = normalizer.request_from_internal(internal, target_variant="codex")
|
|
|
|
|
|
|
|
|
|
assert "instructions" not in out
|
|
|
|
|
|
|
|
|
|
patched = apply_body_rules(out, list(CODEX_DEFAULT_BODY_RULES))
|
|
|
|
|
assert patched["instructions"] == "You are GPT-5."
|
|
|
|
|
|
|
|
|
|
|
2026-03-18 12:35:57 +08:00
|
|
|
def test_openai_cli_normalizer_patch_for_codex_is_noop() -> None:
|
2026-03-18 20:58:33 +08:00
|
|
|
from src.core.api_format.conversion.normalizers.openai_cli import (
|
|
|
|
|
OpenAICliNormalizer,
|
|
|
|
|
)
|
2026-03-17 22:07:09 +08:00
|
|
|
|
|
|
|
|
normalizer = OpenAICliNormalizer()
|
|
|
|
|
out = normalizer.patch_for_variant(
|
|
|
|
|
{
|
|
|
|
|
"temperature": 0.7,
|
|
|
|
|
"input": [],
|
|
|
|
|
"metadata": {"request_id": "abc"},
|
|
|
|
|
"model": "gpt-test",
|
|
|
|
|
"tools": [{"type": "function", "name": "demo"}],
|
|
|
|
|
"instructions": "keep",
|
|
|
|
|
},
|
|
|
|
|
"codex",
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-18 12:35:57 +08:00
|
|
|
assert out is None
|
2026-03-16 17:25:52 +08:00
|
|
|
|
|
|
|
|
|
2026-03-06 21:06:45 +08:00
|
|
|
def test_codex_passthrough_builder_preserves_real_codex_headers() -> None:
|
|
|
|
|
builder = PassthroughRequestBuilder()
|
2026-03-18 20:58:33 +08:00
|
|
|
endpoint = SimpleNamespace(
|
|
|
|
|
api_family="openai", endpoint_kind="cli", header_rules=None
|
|
|
|
|
)
|
2026-03-06 21:06:45 +08:00
|
|
|
key = SimpleNamespace(api_key="unused")
|
|
|
|
|
|
|
|
|
|
headers = builder.build_headers(
|
|
|
|
|
original_headers={
|
|
|
|
|
"accept": "text/event-stream",
|
|
|
|
|
"content-type": "application/json",
|
|
|
|
|
"user-agent": "Codex Desktop/0.108.0-alpha.12",
|
|
|
|
|
"originator": "Codex Desktop",
|
|
|
|
|
"x-codex-turn-metadata": '{"turn_id":"abc"}',
|
|
|
|
|
"x-forwarded-scheme": "https",
|
|
|
|
|
"host": "aether.hetunai.cn",
|
|
|
|
|
"content-length": "123",
|
|
|
|
|
},
|
|
|
|
|
endpoint=endpoint,
|
|
|
|
|
key=key,
|
|
|
|
|
pre_computed_auth=("Authorization", "Bearer upstream-token"),
|
2026-03-01 18:20:42 +08:00
|
|
|
)
|
|
|
|
|
|
2026-03-06 21:06:45 +08:00
|
|
|
assert headers["accept"] == "text/event-stream"
|
|
|
|
|
assert headers["content-type"] == "application/json"
|
|
|
|
|
assert headers["user-agent"] == "Codex Desktop/0.108.0-alpha.12"
|
|
|
|
|
assert headers["originator"] == "Codex Desktop"
|
|
|
|
|
assert headers["x-codex-turn-metadata"] == '{"turn_id":"abc"}'
|
|
|
|
|
assert headers["Authorization"] == "Bearer upstream-token"
|
|
|
|
|
assert "Version" not in headers
|
|
|
|
|
assert "Session_id" not in headers
|
|
|
|
|
assert "Connection" not in headers
|
|
|
|
|
assert "Chatgpt-Account-Id" not in headers
|
|
|
|
|
assert "host" not in headers
|
|
|
|
|
assert "content-length" not in headers
|
|
|
|
|
assert "x-forwarded-scheme" not in headers
|
2026-03-04 10:00:38 +08:00
|
|
|
|
|
|
|
|
|
2026-03-17 17:09:39 +08:00
|
|
|
def test_codex_passthrough_builder_applies_prompt_body_rules() -> None:
|
|
|
|
|
from src.core.api_format.metadata import CODEX_DEFAULT_BODY_RULES
|
|
|
|
|
|
|
|
|
|
builder = PassthroughRequestBuilder()
|
|
|
|
|
endpoint = SimpleNamespace(
|
|
|
|
|
api_family="openai",
|
|
|
|
|
endpoint_kind="cli",
|
|
|
|
|
header_rules=None,
|
|
|
|
|
body_rules=list(CODEX_DEFAULT_BODY_RULES),
|
|
|
|
|
)
|
|
|
|
|
key = SimpleNamespace(api_key="unused")
|
|
|
|
|
|
|
|
|
|
payload, _headers = builder.build(
|
|
|
|
|
{"model": "gpt-test", "input": []},
|
|
|
|
|
{"content-type": "application/json"},
|
|
|
|
|
endpoint,
|
|
|
|
|
key,
|
|
|
|
|
pre_computed_auth=("Authorization", "Bearer upstream-token"),
|
|
|
|
|
provider_api_format="openai:cli",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert payload["instructions"] == "You are GPT-5."
|
|
|
|
|
assert payload["store"] is False
|
|
|
|
|
assert "max_output_tokens" not in payload
|
|
|
|
|
|
|
|
|
|
|
2026-03-04 10:00:38 +08:00
|
|
|
def _encode_unsigned_jwt(payload: dict[str, object]) -> str:
|
|
|
|
|
token = jwt.encode(payload, key="", algorithm="none")
|
|
|
|
|
return token.decode("utf-8") if isinstance(token, bytes) else token
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
2026-03-18 20:58:33 +08:00
|
|
|
async def test_enrich_codex_uses_access_token_when_id_token_missing(
|
|
|
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
|
|
|
) -> None:
|
2026-03-04 10:00:38 +08:00
|
|
|
from src.services.provider.adapters.codex.plugin import enrich_codex
|
|
|
|
|
|
|
|
|
|
access_token = _encode_unsigned_jwt(
|
|
|
|
|
{
|
|
|
|
|
"email": "u@example.com",
|
|
|
|
|
"https://api.openai.com/auth": {
|
|
|
|
|
"chatgpt_account_id": "acc-access",
|
|
|
|
|
"chatgpt_plan_type": "team",
|
|
|
|
|
"chatgpt_user_id": "user-access",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
auth_config: dict[str, object] = {}
|
2026-03-18 20:58:33 +08:00
|
|
|
monkeypatch.setattr(
|
|
|
|
|
"src.core.provider_oauth_utils.fetch_openai_account_name",
|
|
|
|
|
AsyncMock(return_value="Workspace Alpha"),
|
|
|
|
|
)
|
2026-03-04 10:00:38 +08:00
|
|
|
out = await enrich_codex(
|
|
|
|
|
auth_config=auth_config,
|
|
|
|
|
token_response={"access_token": access_token},
|
|
|
|
|
access_token=access_token,
|
|
|
|
|
proxy_config=None,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert out["email"] == "u@example.com"
|
|
|
|
|
assert out["account_id"] == "acc-access"
|
2026-03-18 20:58:33 +08:00
|
|
|
assert out["account_name"] == "Workspace Alpha"
|
2026-03-04 10:00:38 +08:00
|
|
|
assert out["plan_type"] == "team"
|
|
|
|
|
assert out["user_id"] == "user-access"
|