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
+33 -1
View File
@@ -15,6 +15,7 @@ class _FakeRelayClient:
def __init__(self, response: httpx.Response) -> None:
self.response = response
self.sent_request: httpx.Request | None = None
self.sent_body: bytes | None = None
def build_request(self, method: str, url: str, **kwargs: Any) -> httpx.Request:
return httpx.Request(method, url, **kwargs)
@@ -22,6 +23,7 @@ class _FakeRelayClient:
async def send(self, request: httpx.Request, *, stream: bool = False) -> httpx.Response:
_ = stream
self.sent_request = request
self.sent_body = await request.aread()
self.response.request = request
return self.response
@@ -56,7 +58,7 @@ async def test_transport_encodes_local_relay_envelope(monkeypatch: pytest.Monkey
await response.aclose()
assert fake_client.sent_request is not None
payload = fake_client.sent_request.content
payload = fake_client.sent_body
assert payload is not None
meta_len = struct.unpack("!I", payload[:4])[0]
meta = json.loads(payload[4 : 4 + meta_len].decode("utf-8"))
@@ -88,3 +90,33 @@ async def test_transport_maps_relay_timeout_to_read_timeout(
with pytest.raises(httpx.ReadTimeout, match="relay timed out"):
await transport.handle_async_request(request)
@pytest.mark.asyncio
async def test_transport_streams_request_body_from_async_generator(
monkeypatch: pytest.MonkeyPatch,
) -> None:
transport = HubTunnelTransport("node-1", timeout=12.0)
fake_client = _FakeRelayClient(httpx.Response(200, content=b"ok"))
monkeypatch.setattr("src.services.proxy_node.hub_transport.get_hub_config", _relay_config)
monkeypatch.setattr(transport, "_relay_client", fake_client)
async def body() -> Any:
yield b'{"hello":'
yield b'"world"}'
request = httpx.Request(
"POST",
"https://example.com/v1/chat/completions",
headers={"content-type": "application/json"},
content=body(),
)
response = await transport.handle_async_request(request)
assert response.status_code == 200
await response.aclose()
payload = fake_client.sent_body
assert payload is not None
meta_len = struct.unpack("!I", payload[:4])[0]
assert payload[4 + meta_len :] == b'{"hello":"world"}'