feat(codex): 拆分openai:compact为独立端点,简化Codex请求为透传模式

- 新增openai:compact端点类型(EndpointKind.COMPACT),独立于openai:cli
- OpenAICompactAdapter继承OpenAICliAdapter,自动标记compact模式
- Codex请求补丁改为纯透传:仅清理内部标记,不再修改客户端payload
- stream_policy支持openai:compact独立策略,compact端点移除stream字段
- candidate_builder支持compact回退到cli端点
- auth_type: vertex_ai重命名为service_account,保持向后兼容
- Vertex Provider新增api_formats与auth_type组合校验
- KeyAllowedModels对话框改为从Provider获取模型,展示provider_model_name
- Dialog内Select组件自动禁用Portal,修复层级遮挡问题
- 新增Codex compact端点回填迁移脚本
This commit is contained in:
fawney19
2026-03-01 23:55:26 +08:00
parent 4bf3a453e7
commit 97d42703da
37 changed files with 650 additions and 286 deletions
+57 -79
View File
@@ -6,94 +6,39 @@ from src.services.provider.adapters.codex.request_patching import (
)
def test_patch_openai_cli_request_for_codex_sets_store_and_instructions() -> None:
req = {"model": "gpt-test", "input": []}
out = patch_openai_cli_request_for_codex(req)
assert out is not req
assert out["store"] is False
assert out["stream"] is True
assert out["instructions"] == ""
def test_patch_openai_cli_request_for_codex_strips_rejected_params() -> None:
def test_patch_openai_cli_request_for_codex_is_passthrough_except_internal_sentinel() -> None:
req = {
"model": "gpt-test",
"input": [],
"max_output_tokens": 123,
"max_completion_tokens": 456,
"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",
"temperature",
"top_p",
"service_tier",
"truncation",
"context_management",
"user",
):
assert key not in out
def test_patch_openai_cli_request_for_codex_converts_system_role_to_developer() -> None:
req = {
"model": "gpt-test",
"instructions": "ignored",
"input": [
{
"type": "message",
"role": "system",
"content": [{"type": "input_text", "text": "You are a pirate."}],
},
{
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": "Hello"}],
},
}
],
}
out = patch_openai_cli_request_for_codex(req)
assert isinstance(out.get("input"), list)
assert out["input"][0]["role"] == "developer"
assert out["input"][1]["role"] == "user"
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 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": [],
"store": True,
"stream": False,
"instructions": "keep",
"include": ["foo"],
"parallel_tool_calls": False,
"temperature": 0.7,
"context_management": {"compaction": {"type": "summary"}},
"user": "u_123",
"_aether_compact": True,
"stream": True,
}
out = patch_openai_cli_request_for_codex(req)
assert "stream" not in out
assert out is not req
assert "_aether_compact" not in out
assert out["store"] is True
assert out["stream"] is False
assert out["instructions"] == "keep"
assert out["include"] == ["foo"]
assert out["parallel_tool_calls"] is False
assert out["temperature"] == 0.7
assert out["context_management"] == {"compaction": {"type": "summary"}}
assert out["user"] == "u_123"
assert out["input"][0]["role"] == "system"
def test_maybe_patch_request_for_codex_is_noop_for_non_codex() -> None:
@@ -117,7 +62,7 @@ def test_maybe_patch_request_for_codex_is_noop_for_non_openai_cli() -> None:
def test_maybe_patch_request_for_codex_patches_for_codex_openai_cli() -> None:
req = {"model": "gpt-test", "input": []}
req = {"model": "gpt-test", "input": [], "_aether_compact": True, "store": True}
out = maybe_patch_request_for_codex(
provider_type="codex",
provider_api_format="openai:cli",
@@ -125,8 +70,41 @@ def test_maybe_patch_request_for_codex_patches_for_codex_openai_cli() -> None:
)
assert out is not req
assert out["store"] is True
assert "_aether_compact" not in out
def test_maybe_patch_request_for_codex_patches_for_codex_openai_compact() -> None:
req = {"model": "gpt-test", "input": [], "_aether_compact": True, "store": True}
out = maybe_patch_request_for_codex(
provider_type="codex",
provider_api_format="openai:compact",
request_body=req,
)
assert out is not req
assert out["store"] is True
assert "_aether_compact" not in out
def test_openai_cli_normalizer_request_from_internal_codex_variant_preserves_store() -> None:
from src.core.api_format.conversion.normalizers.openai_cli import OpenAICliNormalizer
normalizer = OpenAICliNormalizer()
internal = normalizer.request_to_internal({"model": "gpt-test", "input": [], "store": True})
out = normalizer.request_from_internal(internal, target_variant="codex")
assert out["store"] is True
def test_openai_cli_normalizer_request_from_internal_codex_variant_defaults_store_false() -> None:
from src.core.api_format.conversion.normalizers.openai_cli import OpenAICliNormalizer
normalizer = OpenAICliNormalizer()
internal = normalizer.request_to_internal({"model": "gpt-test", "input": []})
out = normalizer.request_from_internal(internal, target_variant="codex")
assert out["store"] is False
assert "instructions" in out
def test_codex_envelope_extra_headers_includes_sse_accept_and_session() -> None: