feat(tunnel): 请求体流式传输 & OpenAI CLI 请求 key 排序优化

Hub 端:
- open_local_stream 不再接收 body 参数,改为通过 push_local_request_body 分块推送
- 请求体按 32KB 分帧发送,避免大请求一次性压缩和传输
- local_relay 改为流式解析 envelope 和转发请求体

Proxy 端:
- stream_handler 改为流式传输请求体到上游,不再预先收集完整 body
- upstream_client 请求体类型从 Full<Bytes> 改为 UnsyncBoxBody 以支持流式传输
- dispatcher 将 StreamEnd/StreamError 事件转发给 stream handler

Python 端:
- hub_transport relay envelope 改为异步生成器流式发送
- 提取 reorder_openai_cli_request_prefix_keys 为公共函数
- Codex passthrough 路径也应用稳定的前缀 key 排序
This commit is contained in:
fawney19
2026-03-17 22:07:09 +08:00
parent 59840fa419
commit 0342f609d0
12 changed files with 541 additions and 125 deletions

View File

@@ -58,6 +58,31 @@ def test_patch_openai_cli_request_for_codex_preserves_existing_prompt_cache_key(
assert out["prompt_cache_key"] == "client-cache-key"
def test_patch_openai_cli_request_for_codex_reorders_stable_prefix_keys() -> None:
req = {
"temperature": 0.7,
"input": [],
"metadata": {"request_id": "abc"},
"model": "gpt-test",
"tools": [{"type": "function", "name": "demo"}],
"instructions": "keep",
"store": True,
"_aether_compact": True,
}
out = patch_openai_cli_request_for_codex(req)
assert list(out.keys()) == [
"model",
"instructions",
"tools",
"input",
"temperature",
"metadata",
"store",
]
def test_patch_openai_cli_request_for_codex_does_not_inject_prompt_cache_key() -> None:
req = {"model": "gpt-test", "input": [], "_aether_compact": True}
@@ -151,6 +176,34 @@ def test_openai_cli_normalizer_codex_variant_keeps_instructions_missing_for_defa
assert patched["instructions"] == "You are GPT-5."
def test_openai_cli_normalizer_patch_for_codex_reorders_stable_prefix_keys() -> None:
from src.core.api_format.conversion.normalizers.openai_cli import OpenAICliNormalizer
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",
"_aether_compact": True,
},
"codex",
)
assert out is not None
assert list(out.keys()) == [
"model",
"instructions",
"tools",
"input",
"temperature",
"metadata",
]
def test_codex_envelope_extra_headers_does_not_inject_synthetic_headers() -> None:
from src.services.provider.adapters.codex.envelope import codex_oauth_envelope