mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
feat(codex): 支持compact端点非流式请求,更新请求头与字段清理
- Codex适配器支持compact端点:非流式请求使用application/json Accept头, stream_policy根据compact上下文返回FORCE_NON_STREAM - 更新Codex请求头格式:添加Version/Connection头,header key首字母大写 - 简化include列表处理:normalizer和request_patching统一强制为固定列表 - 清理Codex不支持的字段:truncation、context_management、user - 默认instructions改为空字符串 - 前端用量页面:用户页面使用前端筛选后总数,避免不必要的后端分页请求
This commit is contained in:
@@ -12,8 +12,8 @@ def test_patch_openai_cli_request_for_codex_sets_store_and_instructions() -> Non
|
||||
|
||||
assert out is not req
|
||||
assert out["store"] is False
|
||||
assert "instructions" in out
|
||||
assert isinstance(out["instructions"], str)
|
||||
assert out["stream"] is True
|
||||
assert out["instructions"] == ""
|
||||
|
||||
|
||||
def test_patch_openai_cli_request_for_codex_strips_rejected_params() -> None:
|
||||
@@ -22,20 +22,24 @@ def test_patch_openai_cli_request_for_codex_strips_rejected_params() -> None:
|
||||
"input": [],
|
||||
"max_output_tokens": 123,
|
||||
"max_completion_tokens": 456,
|
||||
"max_tokens": 789,
|
||||
"temperature": 0.5,
|
||||
"top_p": 0.9,
|
||||
"service_tier": "default",
|
||||
"truncation": "auto",
|
||||
"context_management": {"compaction": {"type": "summary"}},
|
||||
"user": "u_123",
|
||||
}
|
||||
out = patch_openai_cli_request_for_codex(req)
|
||||
|
||||
for key in (
|
||||
"max_output_tokens",
|
||||
"max_completion_tokens",
|
||||
"max_tokens",
|
||||
"temperature",
|
||||
"top_p",
|
||||
"service_tier",
|
||||
"truncation",
|
||||
"context_management",
|
||||
"user",
|
||||
):
|
||||
assert key not in out
|
||||
|
||||
@@ -68,8 +72,28 @@ def test_patch_openai_cli_request_for_codex_adds_required_include_item() -> None
|
||||
req = {"model": "gpt-test", "input": []}
|
||||
out = patch_openai_cli_request_for_codex(req)
|
||||
|
||||
assert "include" in out
|
||||
assert "reasoning.encrypted_content" in out["include"]
|
||||
assert out["include"] == ["reasoning.encrypted_content"]
|
||||
|
||||
|
||||
def test_patch_openai_cli_request_for_codex_overrides_include() -> None:
|
||||
req = {
|
||||
"model": "gpt-test",
|
||||
"input": [],
|
||||
"include": ["foo", "bar"],
|
||||
}
|
||||
out = patch_openai_cli_request_for_codex(req)
|
||||
assert out["include"] == ["reasoning.encrypted_content"]
|
||||
|
||||
|
||||
def test_patch_openai_cli_request_for_codex_compact_drops_stream() -> None:
|
||||
req = {
|
||||
"model": "gpt-test",
|
||||
"input": [],
|
||||
"_aether_compact": True,
|
||||
"stream": True,
|
||||
}
|
||||
out = patch_openai_cli_request_for_codex(req)
|
||||
assert "stream" not in out
|
||||
|
||||
|
||||
def test_maybe_patch_request_for_codex_is_noop_for_non_codex() -> None:
|
||||
@@ -110,7 +134,34 @@ def test_codex_envelope_extra_headers_includes_sse_accept_and_session() -> None:
|
||||
|
||||
headers = codex_oauth_envelope.extra_headers() or {}
|
||||
assert headers.get("Accept") == "text/event-stream"
|
||||
assert headers.get("x-oai-web-search-eligible") == "true"
|
||||
assert headers.get("originator") == "codex_cli_rs"
|
||||
assert isinstance(headers.get("session_id"), str)
|
||||
assert headers.get("session_id")
|
||||
assert headers.get("Originator") == "codex_cli_rs"
|
||||
assert headers.get("Version") == "0.101.0"
|
||||
assert headers.get("Connection") == "Keep-Alive"
|
||||
assert isinstance(headers.get("Session_id"), str)
|
||||
assert headers.get("Session_id")
|
||||
|
||||
|
||||
def test_codex_envelope_extra_headers_compact_uses_json_accept() -> None:
|
||||
from src.services.provider.adapters.codex.context import (
|
||||
CodexRequestContext,
|
||||
set_codex_request_context,
|
||||
)
|
||||
from src.services.provider.adapters.codex.envelope import codex_oauth_envelope
|
||||
|
||||
set_codex_request_context(CodexRequestContext(is_compact=True))
|
||||
headers = codex_oauth_envelope.extra_headers() or {}
|
||||
assert headers.get("Accept") == "application/json"
|
||||
set_codex_request_context(None)
|
||||
|
||||
|
||||
def test_codex_envelope_extra_headers_uses_account_id_header() -> None:
|
||||
from src.services.provider.adapters.codex.context import (
|
||||
CodexRequestContext,
|
||||
set_codex_request_context,
|
||||
)
|
||||
from src.services.provider.adapters.codex.envelope import codex_oauth_envelope
|
||||
|
||||
set_codex_request_context(CodexRequestContext(account_id="acc_123"))
|
||||
headers = codex_oauth_envelope.extra_headers() or {}
|
||||
assert headers.get("Chatgpt-Account-Id") == "acc_123"
|
||||
set_codex_request_context(None)
|
||||
|
||||
@@ -3,6 +3,10 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from types import SimpleNamespace
|
||||
|
||||
from src.services.provider.adapters.codex.context import (
|
||||
CodexRequestContext,
|
||||
set_codex_request_context,
|
||||
)
|
||||
from src.services.provider.transport import build_provider_url
|
||||
|
||||
|
||||
@@ -44,3 +48,19 @@ def test_codex_openai_cli_does_not_duplicate_responses_suffix() -> None:
|
||||
)
|
||||
|
||||
assert url == "https://chatgpt.com/backend-api/codex/responses"
|
||||
|
||||
|
||||
def test_codex_openai_cli_uses_compact_suffix_when_context_marked_compact() -> None:
|
||||
endpoint = _DummyEndpoint(
|
||||
base_url="https://chatgpt.com/backend-api/codex",
|
||||
api_format="openai:cli",
|
||||
provider=SimpleNamespace(provider_type="codex"),
|
||||
)
|
||||
set_codex_request_context(CodexRequestContext(is_compact=True))
|
||||
url = build_provider_url(
|
||||
endpoint, # type: ignore[arg-type]
|
||||
path_params={"model": "ignored"},
|
||||
is_stream=False,
|
||||
)
|
||||
assert url == "https://chatgpt.com/backend-api/codex/responses/compact"
|
||||
set_codex_request_context(None)
|
||||
|
||||
@@ -3,6 +3,10 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from types import SimpleNamespace
|
||||
|
||||
from src.services.provider.adapters.codex.context import (
|
||||
CodexRequestContext,
|
||||
set_codex_request_context,
|
||||
)
|
||||
from src.services.provider.stream_policy import (
|
||||
UpstreamStreamPolicy,
|
||||
enforce_stream_mode_for_upstream,
|
||||
@@ -42,6 +46,19 @@ def test_get_upstream_stream_policy_codex_ignores_force_non_stream_config() -> N
|
||||
assert get_upstream_stream_policy(ep) == UpstreamStreamPolicy.FORCE_STREAM
|
||||
|
||||
|
||||
def test_get_upstream_stream_policy_codex_compact_forces_non_stream() -> None:
|
||||
ep = _DummyEndpoint(
|
||||
api_format="openai:cli",
|
||||
config=None,
|
||||
provider=SimpleNamespace(provider_type="codex"),
|
||||
)
|
||||
try:
|
||||
set_codex_request_context(CodexRequestContext(is_compact=True))
|
||||
assert get_upstream_stream_policy(ep) == UpstreamStreamPolicy.FORCE_NON_STREAM
|
||||
finally:
|
||||
set_codex_request_context(None)
|
||||
|
||||
|
||||
def test_enforce_stream_mode_for_upstream_openai_chat_sets_stream_options_usage() -> None:
|
||||
body = {"stream": False}
|
||||
out = enforce_stream_mode_for_upstream(
|
||||
|
||||
Reference in New Issue
Block a user