Files
Aether/tests/api/internal_gateway/test_cli_decision_plan.py

3163 lines
106 KiB
Python
Raw Normal View History

"""Gateway internal decision/plan builder tests."""
import asyncio
import base64
import json
import time
from types import SimpleNamespace
from typing import Any
from unittest.mock import AsyncMock, MagicMock
import pytest
from fastapi import BackgroundTasks, FastAPI
from fastapi.responses import JSONResponse, StreamingResponse
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from src.api.internal.gateway import (
CONTROL_ACTION_HEADER,
CONTROL_ACTION_PROXY_PUBLIC,
CONTROL_EXECUTED_HEADER,
GatewayAuthContext,
GatewayExecutionDecisionResponse,
GatewayExecuteRequest,
GatewayExecutionPlanResponse,
GatewayResolveRequest,
GatewayStreamReportRequest,
GatewaySyncReportRequest,
_dispatch_gateway_sync_telemetry,
_build_gateway_sync_error_payload,
_build_gateway_sync_telemetry_writer,
_run_gateway_stream_report_background,
_run_gateway_sync_report_background,
_stream_executor_requires_python_rewrite,
_build_claude_chat_sync_decision,
_build_claude_chat_stream_decision,
_build_claude_cli_sync_decision,
_build_claude_cli_stream_decision,
_build_gemini_files_download_stream_decision,
_build_gemini_files_proxy_sync_decision,
_build_gemini_chat_sync_decision,
_build_gemini_chat_stream_decision,
_build_gemini_cli_sync_decision,
_build_gemini_cli_stream_decision,
_build_openai_chat_sync_decision,
_build_openai_chat_stream_decision,
_build_openai_cli_stream_decision,
_build_openai_video_content_stream_decision,
_extract_gateway_sync_error_message,
_record_gateway_direct_candidate_graph,
_resolve_gateway_sync_error_status_code,
_build_openai_chat_sync_plan,
_build_openai_cli_sync_decision,
_build_openai_cli_stream_plan,
_build_openai_cli_sync_plan,
_is_streaming_sync_payload,
_resolve_auth_context,
_resolve_gateway_sync_adapter,
classify_gateway_route,
router,
)
from src.database import get_db
from src.models.database import Base, RequestCandidate
from src.services.orchestration.candidate_resolver import CandidateResolver
from src.services.scheduling.schemas import PoolCandidate, ProviderCandidate
from src.services.request.execution_runtime_plan import ExecutionPlan, ExecutionPlanBody, PreparedExecutionPlan
def _wait_until(predicate: Any, *, timeout: float = 1.0, interval: float = 0.01) -> None:
deadline = time.time() + timeout
while time.time() < deadline:
if predicate():
return
time.sleep(interval)
assert predicate()
async def test_build_openai_cli_sync_decision_allows_upstream_stream_finalize(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-cli-upstream-stream-decision-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello"}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-cli-upstream-stream-decision-123",
name="openai",
provider_type="openai",
proxy=None,
request_timeout=30.0,
),
endpoint=SimpleNamespace(id="endpoint-cli-upstream-stream-decision-123", api_format="openai:cli"),
key=SimpleNamespace(id="key-cli-upstream-stream-decision-123", proxy=None),
mapping_matched_model="gpt-5",
needs_conversion=False,
output_limit=None,
request_candidate_id="cand-cli-upstream-stream-decision-123",
)
fake_upstream_request = SimpleNamespace(
url="https://api.openai.example/v1/responses",
headers={
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"accept": "text/event-stream",
},
payload={
"model": "gpt-5",
"input": "hello",
"stream": True,
"prompt_cache_key": "cache-key-123",
},
upstream_is_stream=True,
envelope=None,
tls_profile=None,
)
class FakeCliHandler:
primary_api_format = "openai:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(self, body: dict[str, object], path_params: dict[str, str]) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gpt-5"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-cli-upstream-stream-decision-123"),
SimpleNamespace(id="api-key-cli-upstream-stream-decision-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.get_system_proxy_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route("POST", "/v1/responses")
auth_context = GatewayAuthContext(
user_id="user-cli-upstream-stream-decision-123",
api_key_id="api-key-cli-upstream-stream-decision-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello"},
auth_context=auth_context,
)
result = await _build_openai_cli_sync_decision(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.upstream_is_stream is True
assert result.report_kind == "openai_cli_sync_finalize"
assert result.mapped_model == "gpt-5"
assert result.prompt_cache_key == "cache-key-123"
assert result.upstream_url == "https://api.openai.example/v1/responses"
assert result.extra_headers == {}
assert result.provider_request_headers == {
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"accept": "text/event-stream",
}
assert result.provider_request_body == {
"model": "gpt-5",
"input": "hello",
"stream": True,
"prompt_cache_key": "cache-key-123",
}
@pytest.mark.asyncio
async def test_build_openai_cli_sync_decision_uses_finalize_for_cross_format_candidate(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-cli-decision-finalize-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello"}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-cli-decision-finalize-123",
name="gemini",
provider_type="gemini",
proxy=None,
request_timeout=30.0,
),
endpoint=SimpleNamespace(
id="endpoint-cli-decision-finalize-123",
api_format="gemini:cli",
base_url="https://api.gemini.example",
),
key=SimpleNamespace(id="key-cli-decision-finalize-123", proxy=None, api_key="enc-key"),
mapping_matched_model="gemini-2.5-pro",
needs_conversion=True,
output_limit=None,
request_candidate_id="cand-cli-decision-finalize-123",
)
fake_upstream_request = SimpleNamespace(
url="https://api.gemini.example/v1beta/models/gemini-2.5-pro:generateContent",
headers={
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"x-provider-extra": "1",
},
payload={"contents": [{"role": "user", "parts": [{"text": "hello"}]}]},
upstream_is_stream=False,
envelope=object(),
tls_profile="custom-profile",
)
class FakeCliHandler:
primary_api_format = "openai:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gemini-2.5-pro"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-cli-decision-finalize-123"),
SimpleNamespace(id="api-key-cli-decision-finalize-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.get_system_proxy_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route("POST", "/v1/responses")
auth_context = GatewayAuthContext(
user_id="user-cli-decision-finalize-123",
api_key_id="api-key-cli-decision-finalize-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello"},
auth_context=auth_context,
)
result = await _build_openai_cli_sync_decision(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.report_kind == "openai_cli_sync_finalize"
assert result.provider_api_format == "gemini:cli"
assert result.client_api_format == "openai:cli"
assert result.mapped_model == "gemini-2.5-pro"
assert result.upstream_url == "https://api.gemini.example/v1beta/models/gemini-2.5-pro:generateContent"
assert result.tls_profile == "custom-profile"
assert result.provider_request_headers == {
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"x-provider-extra": "1",
}
assert result.provider_request_body == {
"contents": [{"role": "user", "parts": [{"text": "hello"}]}]
}
@pytest.mark.asyncio
async def test_build_claude_cli_sync_decision_uses_finalize_for_cross_format_candidate(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.claude_cli import ClaudeCliAdapter
adapter = ClaudeCliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-claude-cli-decision-finalize-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "claude-code", "messages": []}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-claude-cli-decision-finalize-123",
name="openai",
provider_type="openai",
proxy=None,
request_timeout=30.0,
),
endpoint=SimpleNamespace(
id="endpoint-claude-cli-decision-finalize-123",
api_format="openai:cli",
base_url="https://api.openai.example",
),
key=SimpleNamespace(id="key-claude-cli-decision-finalize-123", proxy=None, api_key="enc-key"),
mapping_matched_model="gpt-5.1",
needs_conversion=True,
output_limit=None,
request_candidate_id="cand-claude-cli-decision-finalize-123",
)
fake_upstream_request = SimpleNamespace(
url="https://api.openai.example/v1/responses",
headers={
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"x-provider-extra": "1",
},
payload={
"model": "gpt-5.1",
"input": "hello",
"prompt_cache_key": "cache-key-123",
"metadata": {"decision": "exact"},
},
upstream_is_stream=False,
envelope=object(),
tls_profile="custom-profile",
)
class FakeCliHandler:
primary_api_format = "claude:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "claude-code"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gpt-5.1"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-claude-cli-decision-finalize-123"),
SimpleNamespace(id="api-key-claude-cli-decision-finalize-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.get_system_proxy_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route(
"POST",
"/v1/messages",
{"content-type": "application/json", "authorization": "Bearer test-key"},
)
auth_context = GatewayAuthContext(
user_id="user-claude-cli-decision-finalize-123",
api_key_id="api-key-claude-cli-decision-finalize-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/messages",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "claude-code", "messages": []},
auth_context=auth_context,
)
result = await _build_claude_cli_sync_decision(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.report_kind == "claude_cli_sync_finalize"
assert result.provider_api_format == "openai:cli"
assert result.client_api_format == "claude:cli"
assert result.mapped_model == "gpt-5.1"
assert result.upstream_url == "https://api.openai.example/v1/responses"
assert result.tls_profile == "custom-profile"
assert result.provider_request_headers == {
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"x-provider-extra": "1",
}
assert result.provider_request_body == {
"model": "gpt-5.1",
"input": "hello",
"prompt_cache_key": "cache-key-123",
"metadata": {"decision": "exact"},
}
@pytest.mark.asyncio
async def test_build_gemini_cli_sync_decision_uses_finalize_for_cross_format_candidate(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.gemini_cli import GeminiCliAdapter
adapter = GeminiCliAdapter()
fake_context = SimpleNamespace(
path_params={"model": "gemini-cli"},
request_id="req-gemini-cli-decision-finalize-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={
"content-type": "application/json",
"user-agent": "GeminiCLI/1.0",
"x-goog-api-key": "client-key",
},
query_params={},
client_content_encoding=None,
extra={},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"contents": [{"role": "user", "parts": [{"text": "hello"}]}]}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-gemini-cli-decision-finalize-123",
name="openai",
provider_type="openai",
proxy=None,
request_timeout=30.0,
),
endpoint=SimpleNamespace(
id="endpoint-gemini-cli-decision-finalize-123",
api_format="openai:cli",
base_url="https://api.openai.example",
),
key=SimpleNamespace(id="key-gemini-cli-decision-finalize-123", proxy=None, api_key="enc-key"),
mapping_matched_model="gpt-5.1",
needs_conversion=True,
output_limit=None,
request_candidate_id="cand-gemini-cli-decision-finalize-123",
)
fake_upstream_request = SimpleNamespace(
url="https://api.openai.example/v1/responses",
headers={
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"x-provider-extra": "1",
},
payload={
"model": "gpt-5.1",
"input": "hello",
"prompt_cache_key": "cache-key-123",
"metadata": {"decision": "exact"},
},
upstream_is_stream=False,
envelope=object(),
tls_profile="custom-profile",
)
class FakeCliHandler:
primary_api_format = "gemini:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gemini-cli"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gpt-5.1"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: {**body, "model": path_params["model"]},
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {"model": "gemini-cli"}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-gemini-cli-decision-finalize-123"),
SimpleNamespace(id="api-key-gemini-cli-decision-finalize-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.get_system_proxy_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route(
"POST",
"/v1beta/models/gemini-cli:generateContent",
{
"content-type": "application/json",
"user-agent": "GeminiCLI/1.0",
"x-goog-api-key": "client-key",
},
)
auth_context = GatewayAuthContext(
user_id="user-gemini-cli-decision-finalize-123",
api_key_id="api-key-gemini-cli-decision-finalize-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1beta/models/gemini-cli:generateContent",
headers={
"content-type": "application/json",
"user-agent": "GeminiCLI/1.0",
"x-goog-api-key": "client-key",
},
body_json={"contents": [{"role": "user", "parts": [{"text": "hello"}]}]},
auth_context=auth_context,
)
result = await _build_gemini_cli_sync_decision(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.report_kind == "gemini_cli_sync_finalize"
assert result.provider_api_format == "openai:cli"
assert result.client_api_format == "gemini:cli"
assert result.mapped_model == "gpt-5.1"
assert result.upstream_url == "https://api.openai.example/v1/responses"
assert result.tls_profile == "custom-profile"
assert result.provider_request_headers == {
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"x-provider-extra": "1",
}
assert result.provider_request_body == {
"model": "gpt-5.1",
"input": "hello",
"prompt_cache_key": "cache-key-123",
"metadata": {"decision": "exact"},
}
@pytest.mark.asyncio
async def test_build_claude_cli_stream_decision_preserves_exact_provider_request_for_same_format(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.claude_cli import ClaudeCliAdapter
adapter = ClaudeCliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-claude-cli-stream-exact-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "claude-code", "messages": [], "stream": True}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-claude-cli-stream-exact-123",
name="claude",
provider_type="claude",
proxy=None,
request_timeout=30.0,
),
endpoint=SimpleNamespace(
id="endpoint-claude-cli-stream-exact-123",
api_format="claude:cli",
base_url="https://api.anthropic.example",
),
key=SimpleNamespace(id="key-claude-cli-stream-exact-123", proxy=None, api_key="enc-key"),
mapping_matched_model="claude-sonnet-4-upstream",
needs_conversion=False,
output_limit=None,
request_candidate_id="cand-claude-cli-stream-exact-123",
)
fake_upstream_request = SimpleNamespace(
url="https://api.anthropic.example/v1/messages",
headers={
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"x-provider-extra": "1",
},
payload={
"model": "claude-sonnet-4-upstream",
"messages": [],
"stream": True,
"metadata": {"decision": "exact"},
},
upstream_is_stream=True,
envelope=None,
tls_profile="custom-profile",
)
class FakeCliHandler:
primary_api_format = "claude:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "claude-code"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "claude-sonnet-4-upstream"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-claude-cli-stream-exact-123"),
SimpleNamespace(id="api-key-claude-cli-stream-exact-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.get_system_proxy_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route(
"POST",
"/v1/messages",
{"content-type": "application/json", "authorization": "Bearer test-key"},
)
auth_context = GatewayAuthContext(
user_id="user-claude-cli-stream-exact-123",
api_key_id="api-key-claude-cli-stream-exact-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/messages",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "claude-code", "messages": [], "stream": True},
auth_context=auth_context,
)
result = await _build_claude_cli_stream_decision(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.report_kind == "claude_cli_stream_success"
assert result.provider_api_format == "claude:cli"
assert result.client_api_format == "claude:cli"
assert result.mapped_model == "claude-sonnet-4-upstream"
assert result.upstream_url == "https://api.anthropic.example/v1/messages"
assert result.tls_profile == "custom-profile"
assert result.extra_headers == {}
assert result.provider_request_headers == {
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"x-provider-extra": "1",
}
assert result.provider_request_body == {
"model": "claude-sonnet-4-upstream",
"messages": [],
"stream": True,
"metadata": {"decision": "exact"},
}
@pytest.mark.asyncio
async def test_build_gemini_cli_stream_decision_preserves_exact_provider_request_for_same_format(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.gemini_cli import GeminiCliAdapter
adapter = GeminiCliAdapter()
fake_context = SimpleNamespace(
path_params={"model": "gemini-cli", "stream": True},
request_id="req-gemini-cli-stream-exact-123",
client_ip="127.0.0.1",
user_agent="GeminiCLI/1.0",
start_time=0.0,
original_headers={
"content-type": "application/json",
"user-agent": "GeminiCLI/1.0",
"x-goog-api-key": "client-key",
},
query_params={},
client_content_encoding=None,
extra={},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"stream": True,
}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-gemini-cli-stream-exact-123",
name="gemini",
provider_type="gemini",
proxy=None,
request_timeout=30.0,
),
endpoint=SimpleNamespace(
id="endpoint-gemini-cli-stream-exact-123",
api_format="gemini:cli",
base_url="https://generativelanguage.googleapis.com",
),
key=SimpleNamespace(id="key-gemini-cli-stream-exact-123", proxy=None, api_key="enc-key"),
mapping_matched_model="gemini-cli-upstream",
needs_conversion=False,
output_limit=None,
request_candidate_id="cand-gemini-cli-stream-exact-123",
)
fake_upstream_request = SimpleNamespace(
url=(
"https://generativelanguage.googleapis.com/"
"v1beta/models/gemini-cli-upstream:streamGenerateContent"
),
headers={
"content-type": "application/json",
"x-goog-api-key": "upstream-key",
"x-provider-extra": "1",
},
payload={
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"generationConfig": {"temperature": 0.2},
"prompt_cache_key": "cache-key-123",
},
upstream_is_stream=True,
envelope=None,
tls_profile="custom-profile",
)
class FakeCliHandler:
primary_api_format = "gemini:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gemini-cli"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gemini-cli-upstream"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: {**body, "model": path_params["model"]},
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {"model": "gemini-cli", "stream": True}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-gemini-cli-stream-exact-123"),
SimpleNamespace(id="api-key-gemini-cli-stream-exact-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.get_system_proxy_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route(
"POST",
"/v1beta/models/gemini-cli:streamGenerateContent",
{
"content-type": "application/json",
"user-agent": "GeminiCLI/1.0",
"x-goog-api-key": "client-key",
},
)
auth_context = GatewayAuthContext(
user_id="user-gemini-cli-stream-exact-123",
api_key_id="api-key-gemini-cli-stream-exact-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1beta/models/gemini-cli:streamGenerateContent",
headers={
"content-type": "application/json",
"user-agent": "GeminiCLI/1.0",
"x-goog-api-key": "client-key",
},
body_json={
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"stream": True,
},
auth_context=auth_context,
)
result = await _build_gemini_cli_stream_decision(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.report_kind == "gemini_cli_stream_success"
assert result.provider_api_format == "gemini:cli"
assert result.client_api_format == "gemini:cli"
assert result.mapped_model == "gemini-cli-upstream"
assert result.upstream_url == (
"https://generativelanguage.googleapis.com/"
"v1beta/models/gemini-cli-upstream:streamGenerateContent"
)
assert result.tls_profile == "custom-profile"
assert result.extra_headers == {}
assert result.provider_request_headers == {
"content-type": "application/json",
"x-goog-api-key": "upstream-key",
"x-provider-extra": "1",
}
assert result.provider_request_body == {
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"generationConfig": {"temperature": 0.2},
"prompt_cache_key": "cache-key-123",
}
@pytest.mark.asyncio
async def test_build_openai_cli_stream_decision_allows_claude_cross_format_exact_provider_request(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-openai-cli-stream-claude-xfmt-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello", "stream": True}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-openai-cli-stream-claude-xfmt-123",
name="claude",
provider_type="claude",
proxy=None,
request_timeout=30.0,
),
endpoint=SimpleNamespace(
id="endpoint-openai-cli-stream-claude-xfmt-123",
api_format="claude:cli",
base_url="https://api.anthropic.example",
),
key=SimpleNamespace(
id="key-openai-cli-stream-claude-xfmt-123",
proxy=None,
api_key="enc-key",
),
mapping_matched_model="claude-sonnet-4-upstream",
needs_conversion=True,
output_limit=None,
request_candidate_id="cand-openai-cli-stream-claude-xfmt-123",
)
fake_upstream_request = SimpleNamespace(
url="https://api.anthropic.example/v1/messages",
headers={
"content-type": "application/json",
"x-api-key": "upstream-key",
"accept": "text/event-stream",
"x-provider-extra": "1",
},
payload={
"model": "claude-sonnet-4-upstream",
"messages": [{"role": "user", "content": "hello"}],
"stream": True,
"metadata": {"decision": "exact"},
},
upstream_is_stream=True,
envelope=None,
tls_profile="custom-profile",
)
class FakeCliHandler:
primary_api_format = "openai:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "claude-sonnet-4-upstream"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-openai-cli-stream-claude-xfmt-123"),
SimpleNamespace(id="api-key-openai-cli-stream-claude-xfmt-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.get_system_proxy_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route(
"POST",
"/v1/responses",
{"content-type": "application/json", "authorization": "Bearer test-key"},
)
auth_context = GatewayAuthContext(
user_id="user-openai-cli-stream-claude-xfmt-123",
api_key_id="api-key-openai-cli-stream-claude-xfmt-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello", "stream": True},
auth_context=auth_context,
)
result = await _build_openai_cli_stream_decision(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.report_kind == "openai_cli_stream_success"
assert result.provider_api_format == "claude:cli"
assert result.client_api_format == "openai:cli"
assert result.mapped_model == "claude-sonnet-4-upstream"
assert result.upstream_url == "https://api.anthropic.example/v1/messages"
assert result.tls_profile == "custom-profile"
assert result.provider_request_headers == {
"content-type": "application/json",
"x-api-key": "upstream-key",
"accept": "text/event-stream",
"x-provider-extra": "1",
}
assert result.provider_request_body == {
"model": "claude-sonnet-4-upstream",
"messages": [{"role": "user", "content": "hello"}],
"stream": True,
"metadata": {"decision": "exact"},
}
@pytest.mark.asyncio
async def test_build_openai_cli_stream_decision_allows_gemini_cross_format_exact_provider_request(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-openai-cli-stream-gemini-xfmt-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello", "stream": True}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-openai-cli-stream-gemini-xfmt-123",
name="gemini",
provider_type="gemini",
proxy=None,
request_timeout=30.0,
),
endpoint=SimpleNamespace(
id="endpoint-openai-cli-stream-gemini-xfmt-123",
api_format="gemini:cli",
base_url="https://generativelanguage.googleapis.com",
),
key=SimpleNamespace(
id="key-openai-cli-stream-gemini-xfmt-123",
proxy=None,
api_key="enc-key",
),
mapping_matched_model="gemini-2.5-pro-upstream",
needs_conversion=True,
output_limit=None,
request_candidate_id="cand-openai-cli-stream-gemini-xfmt-123",
)
fake_upstream_request = SimpleNamespace(
url=(
"https://generativelanguage.googleapis.com/"
"v1beta/models/gemini-2.5-pro-upstream:streamGenerateContent?alt=sse"
),
headers={
"content-type": "application/json",
"x-goog-api-key": "upstream-key",
"accept": "text/event-stream",
"x-provider-extra": "1",
},
payload={
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"generationConfig": {"temperature": 0.2},
"prompt_cache_key": "cache-key-123",
},
upstream_is_stream=True,
envelope=None,
tls_profile="custom-profile",
)
class FakeCliHandler:
primary_api_format = "openai:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gemini-2.5-pro-upstream"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-openai-cli-stream-gemini-xfmt-123"),
SimpleNamespace(id="api-key-openai-cli-stream-gemini-xfmt-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.get_system_proxy_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route(
"POST",
"/v1/responses",
{"content-type": "application/json", "authorization": "Bearer test-key"},
)
auth_context = GatewayAuthContext(
user_id="user-openai-cli-stream-gemini-xfmt-123",
api_key_id="api-key-openai-cli-stream-gemini-xfmt-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello", "stream": True},
auth_context=auth_context,
)
result = await _build_openai_cli_stream_decision(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.report_kind == "openai_cli_stream_success"
assert result.provider_api_format == "gemini:cli"
assert result.client_api_format == "openai:cli"
assert result.mapped_model == "gemini-2.5-pro-upstream"
assert result.upstream_url == (
"https://generativelanguage.googleapis.com/"
"v1beta/models/gemini-2.5-pro-upstream:streamGenerateContent?alt=sse"
)
assert result.tls_profile == "custom-profile"
assert result.provider_request_headers == {
"content-type": "application/json",
"x-goog-api-key": "upstream-key",
"accept": "text/event-stream",
"x-provider-extra": "1",
}
assert result.provider_request_body == {
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"generationConfig": {"temperature": 0.2},
"prompt_cache_key": "cache-key-123",
}
@pytest.mark.asyncio
async def test_build_openai_compact_stream_decision_allows_gemini_cross_format_exact_provider_request(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-openai-compact-stream-gemini-xfmt-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello", "stream": True}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-openai-compact-stream-gemini-xfmt-123",
name="gemini",
provider_type="gemini",
proxy=None,
request_timeout=30.0,
),
endpoint=SimpleNamespace(
id="endpoint-openai-compact-stream-gemini-xfmt-123",
api_format="gemini:cli",
base_url="https://generativelanguage.googleapis.com",
),
key=SimpleNamespace(
id="key-openai-compact-stream-gemini-xfmt-123",
proxy=None,
api_key="enc-key",
),
mapping_matched_model="gemini-2.5-pro-upstream",
needs_conversion=True,
output_limit=None,
request_candidate_id="cand-openai-compact-stream-gemini-xfmt-123",
)
fake_upstream_request = SimpleNamespace(
url=(
"https://generativelanguage.googleapis.com/"
"v1beta/models/gemini-2.5-pro-upstream:streamGenerateContent?alt=sse"
),
headers={
"content-type": "application/json",
"x-goog-api-key": "upstream-key",
"accept": "text/event-stream",
"x-provider-extra": "1",
},
payload={
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"generationConfig": {"temperature": 0.2},
"prompt_cache_key": "cache-key-123",
},
upstream_is_stream=True,
envelope=None,
tls_profile="custom-profile",
)
class FakeCliHandler:
primary_api_format = "openai:compact"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gemini-2.5-pro-upstream"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-openai-compact-stream-gemini-xfmt-123"),
SimpleNamespace(id="api-key-openai-compact-stream-gemini-xfmt-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.get_system_proxy_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route(
"POST",
"/v1/responses/compact",
{"content-type": "application/json", "authorization": "Bearer test-key"},
)
auth_context = GatewayAuthContext(
user_id="user-openai-compact-stream-gemini-xfmt-123",
api_key_id="api-key-openai-compact-stream-gemini-xfmt-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses/compact",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello", "stream": True},
auth_context=auth_context,
)
result = await _build_openai_cli_stream_decision(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.decision_kind == "openai_compact_stream"
assert result.report_kind == "openai_cli_stream_success"
assert result.provider_api_format == "gemini:cli"
assert result.client_api_format == "openai:compact"
assert result.mapped_model == "gemini-2.5-pro-upstream"
assert result.upstream_url == (
"https://generativelanguage.googleapis.com/"
"v1beta/models/gemini-2.5-pro-upstream:streamGenerateContent?alt=sse"
)
assert result.tls_profile == "custom-profile"
assert result.provider_request_headers == {
"content-type": "application/json",
"x-goog-api-key": "upstream-key",
"accept": "text/event-stream",
"x-provider-extra": "1",
}
assert result.provider_request_body == {
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"generationConfig": {"temperature": 0.2},
"prompt_cache_key": "cache-key-123",
}
@pytest.mark.asyncio
async def test_build_openai_cli_stream_decision_allows_antigravity_gemini_cross_format_exact_provider_request(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
class AntigravityEnvelope:
name = "antigravity:v1internal"
def force_stream_rewrite(self) -> bool:
return True
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-openai-cli-stream-antigravity-gemini-xfmt-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello", "stream": True}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-openai-cli-stream-antigravity-gemini-xfmt-123",
name="antigravity",
provider_type="antigravity",
proxy=None,
request_timeout=30.0,
),
endpoint=SimpleNamespace(
id="endpoint-openai-cli-stream-antigravity-gemini-xfmt-123",
api_format="gemini:cli",
base_url="https://generativelanguage.googleapis.com",
),
key=SimpleNamespace(
id="key-openai-cli-stream-antigravity-gemini-xfmt-123",
proxy=None,
api_key="enc-key",
),
mapping_matched_model="claude-sonnet-4-5",
needs_conversion=True,
output_limit=None,
request_candidate_id="cand-openai-cli-stream-antigravity-gemini-xfmt-123",
)
fake_upstream_request = SimpleNamespace(
url=(
"https://generativelanguage.googleapis.com/"
"v1beta/models/claude-sonnet-4-5:streamGenerateContent?alt=sse"
),
headers={
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"accept": "text/event-stream",
"x-provider-extra": "1",
},
payload={
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"generationConfig": {"temperature": 0.2},
},
upstream_is_stream=True,
envelope=AntigravityEnvelope(),
tls_profile="custom-profile",
)
class FakeCliHandler:
primary_api_format = "openai:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "claude-sonnet-4-5"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-openai-cli-stream-antigravity-gemini-xfmt-123"),
SimpleNamespace(id="api-key-openai-cli-stream-antigravity-gemini-xfmt-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.get_system_proxy_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route(
"POST",
"/v1/responses",
{"content-type": "application/json", "authorization": "Bearer test-key"},
)
auth_context = GatewayAuthContext(
user_id="user-openai-cli-stream-antigravity-gemini-xfmt-123",
api_key_id="api-key-openai-cli-stream-antigravity-gemini-xfmt-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello", "stream": True},
auth_context=auth_context,
)
result = await _build_openai_cli_stream_decision(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.report_kind == "openai_cli_stream_success"
assert result.provider_api_format == "gemini:cli"
assert result.client_api_format == "openai:cli"
assert result.mapped_model == "claude-sonnet-4-5"
assert result.upstream_url == (
"https://generativelanguage.googleapis.com/"
"v1beta/models/claude-sonnet-4-5:streamGenerateContent?alt=sse"
)
assert result.tls_profile == "custom-profile"
assert result.provider_request_headers == {
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"accept": "text/event-stream",
"x-provider-extra": "1",
}
assert result.provider_request_body == {
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"generationConfig": {"temperature": 0.2},
}
assert result.report_context["has_envelope"] is True
assert result.report_context["envelope_name"] == "antigravity:v1internal"
assert result.report_context["needs_conversion"] is True
async def test_build_gemini_cli_stream_decision_allows_antigravity_force_rewrite_envelope(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.gemini_cli import GeminiCliAdapter
class AntigravityEnvelope:
name = "antigravity:v1internal"
def force_stream_rewrite(self) -> bool:
return True
adapter = GeminiCliAdapter()
fake_context = SimpleNamespace(
path_params={"model": "gemini-cli", "stream": True},
request_id="req-antigravity-gemini-cli-stream-exact-123",
client_ip="127.0.0.1",
user_agent="GeminiCLI/1.0",
start_time=0.0,
original_headers={
"content-type": "application/json",
"user-agent": "GeminiCLI/1.0",
"authorization": "Bearer client-key",
},
query_params={},
client_content_encoding=None,
extra={},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"stream": True,
}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-antigravity-gemini-cli-stream-exact-123",
name="antigravity",
provider_type="antigravity",
proxy=None,
request_timeout=30.0,
),
endpoint=SimpleNamespace(
id="endpoint-antigravity-gemini-cli-stream-exact-123",
api_format="gemini:cli",
base_url="https://generativelanguage.googleapis.com",
),
key=SimpleNamespace(
id="key-antigravity-gemini-cli-stream-exact-123",
proxy=None,
api_key="enc-key",
),
mapping_matched_model="claude-sonnet-4-5",
needs_conversion=False,
output_limit=None,
request_candidate_id="cand-antigravity-gemini-cli-stream-exact-123",
)
fake_upstream_request = SimpleNamespace(
url=(
"https://generativelanguage.googleapis.com/"
"v1beta/models/claude-sonnet-4-5:streamGenerateContent"
),
headers={
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"x-provider-extra": "1",
},
payload={
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"generationConfig": {"temperature": 0.2},
},
upstream_is_stream=True,
envelope=AntigravityEnvelope(),
tls_profile="custom-profile",
)
class FakeCliHandler:
primary_api_format = "gemini:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gemini-cli"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "claude-sonnet-4-5"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: {**body, "model": path_params["model"]},
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {"model": "gemini-cli", "stream": True}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-antigravity-gemini-cli-stream-exact-123"),
SimpleNamespace(id="api-key-antigravity-gemini-cli-stream-exact-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.get_system_proxy_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route(
"POST",
"/v1beta/models/gemini-cli:streamGenerateContent",
{
"content-type": "application/json",
"user-agent": "GeminiCLI/1.0",
"authorization": "Bearer client-key",
},
)
auth_context = GatewayAuthContext(
user_id="user-antigravity-gemini-cli-stream-exact-123",
api_key_id="api-key-antigravity-gemini-cli-stream-exact-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1beta/models/gemini-cli:streamGenerateContent",
headers={
"content-type": "application/json",
"user-agent": "GeminiCLI/1.0",
"authorization": "Bearer client-key",
},
body_json={
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"stream": True,
},
auth_context=auth_context,
)
result = await _build_gemini_cli_stream_decision(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.report_kind == "gemini_cli_stream_success"
assert result.provider_api_format == "gemini:cli"
assert result.client_api_format == "gemini:cli"
assert result.mapped_model == "claude-sonnet-4-5"
assert result.upstream_url == (
"https://generativelanguage.googleapis.com/"
"v1beta/models/claude-sonnet-4-5:streamGenerateContent"
)
assert result.provider_request_headers == {
"content-type": "application/json",
"authorization": "Bearer upstream-key",
"x-provider-extra": "1",
}
assert result.provider_request_body == {
"contents": [{"role": "user", "parts": [{"text": "hello"}]}],
"generationConfig": {"temperature": 0.2},
}
@pytest.mark.asyncio
async def test_build_openai_cli_sync_plan_uses_finalize_for_cross_format_candidate(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-cli-finalize-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello"}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-cli-finalize-123",
name="gemini",
provider_type="gemini",
proxy=None,
request_timeout=30.0,
),
endpoint=SimpleNamespace(id="endpoint-cli-finalize-123", api_format="gemini:cli"),
key=SimpleNamespace(id="key-cli-finalize-123", proxy=None),
mapping_matched_model="gemini-2.5-pro",
needs_conversion=True,
output_limit=None,
request_candidate_id="cand-cli-finalize-123",
)
fake_upstream_request = SimpleNamespace(
url="https://api.gemini.example/v1beta/models/gemini-2.5-pro:generateContent",
headers={"content-type": "application/json"},
payload={"contents": []},
upstream_is_stream=False,
envelope=object(),
tls_profile=None,
)
class FakeCliHandler:
primary_api_format = "openai:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(self, body: dict[str, object], path_params: dict[str, str]) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gemini-2.5-pro"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-cli-finalize-123"),
SimpleNamespace(id="api-key-cli-finalize-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route("POST", "/v1/responses")
auth_context = GatewayAuthContext(
user_id="user-cli-finalize-123",
api_key_id="api-key-cli-finalize-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello"},
auth_context=auth_context,
)
result = await _build_openai_cli_sync_plan(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.report_kind == "openai_cli_sync_finalize"
assert result.report_context["provider_api_format"] == "gemini:cli"
assert result.report_context["client_api_format"] == "openai:cli"
assert result.report_context["mapped_model"] == "gemini-2.5-pro"
@pytest.mark.asyncio
async def test_build_openai_cli_sync_plan_allows_upstream_stream_finalize(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-cli-upstream-stream-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello"}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-cli-upstream-stream-123",
name="openai",
provider_type="openai",
proxy=None,
request_timeout=30.0,
),
endpoint=SimpleNamespace(id="endpoint-cli-upstream-stream-123", api_format="openai:cli"),
key=SimpleNamespace(id="key-cli-upstream-stream-123", proxy=None),
mapping_matched_model="gpt-5",
needs_conversion=False,
output_limit=None,
request_candidate_id="cand-cli-upstream-stream-123",
)
fake_upstream_request = SimpleNamespace(
url="https://api.openai.example/v1/responses",
headers={"content-type": "application/json"},
payload={"model": "gpt-5", "input": "hello"},
upstream_is_stream=True,
envelope=None,
tls_profile=None,
)
class FakeCliHandler:
primary_api_format = "openai:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gpt-5"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-cli-upstream-stream-123"),
SimpleNamespace(id="api-key-cli-upstream-stream-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route("POST", "/v1/responses")
auth_context = GatewayAuthContext(
user_id="user-cli-upstream-stream-123",
api_key_id="api-key-cli-upstream-stream-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello"},
auth_context=auth_context,
)
result = await _build_openai_cli_sync_plan(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.report_kind == "openai_cli_sync_finalize"
assert result.plan["stream"] is True
@pytest.mark.asyncio
async def test_build_openai_cli_stream_plan_allows_non_rewrite_envelope(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
class NonRewriteEnvelope:
def force_stream_rewrite(self) -> bool:
return False
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-cli-stream-envelope-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={"perf": None},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello", "stream": True}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-cli-stream-envelope-123",
name="claude_code",
proxy=None,
),
endpoint=SimpleNamespace(id="endpoint-cli-stream-envelope-123", api_format="openai:cli"),
key=SimpleNamespace(id="key-cli-stream-envelope-123", proxy=None),
mapping_matched_model="gpt-5",
needs_conversion=False,
output_limit=None,
request_candidate_id="cand-cli-stream-envelope-123",
)
fake_upstream_request = SimpleNamespace(
url="https://api.openai.example/v1/responses",
headers={"content-type": "application/json", "accept": "text/event-stream"},
payload={"model": "gpt-5", "input": "hello", "stream": True},
upstream_is_stream=True,
envelope=NonRewriteEnvelope(),
tls_profile=None,
)
class FakeCliHandler:
primary_api_format = "openai:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gpt-5"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-cli-stream-envelope-123"),
SimpleNamespace(id="api-key-cli-stream-envelope-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route("POST", "/v1/responses")
auth_context = GatewayAuthContext(
user_id="user-cli-stream-envelope-123",
api_key_id="api-key-cli-stream-envelope-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello", "stream": True},
auth_context=auth_context,
)
result = await _build_openai_cli_stream_plan(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.plan_kind == "openai_cli_stream"
assert result.report_kind == "openai_cli_stream_success"
@pytest.mark.asyncio
async def test_build_openai_compact_stream_plan_allows_non_rewrite_envelope(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
class NonRewriteEnvelope:
def force_stream_rewrite(self) -> bool:
return False
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-compact-stream-envelope-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={"perf": None},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello", "stream": True}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-compact-stream-envelope-123",
name="openai",
proxy=None,
),
endpoint=SimpleNamespace(
id="endpoint-compact-stream-envelope-123",
api_format="openai:compact",
),
key=SimpleNamespace(id="key-compact-stream-envelope-123", proxy=None),
mapping_matched_model="gpt-5",
needs_conversion=False,
output_limit=None,
request_candidate_id="cand-compact-stream-envelope-123",
)
fake_upstream_request = SimpleNamespace(
url="https://api.openai.example/v1/responses/compact",
headers={"content-type": "application/json", "accept": "text/event-stream"},
payload={"model": "gpt-5", "input": "hello", "stream": True},
upstream_is_stream=True,
envelope=NonRewriteEnvelope(),
tls_profile=None,
)
class FakeCliHandler:
primary_api_format = "openai:compact"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gpt-5"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-compact-stream-envelope-123"),
SimpleNamespace(id="api-key-compact-stream-envelope-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route("POST", "/v1/responses/compact")
auth_context = GatewayAuthContext(
user_id="user-compact-stream-envelope-123",
api_key_id="api-key-compact-stream-envelope-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses/compact",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello", "stream": True},
auth_context=auth_context,
)
result = await _build_openai_cli_stream_plan(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is not None
assert result.plan_kind == "openai_compact_stream"
assert result.report_kind == "openai_cli_stream_success"
@pytest.mark.asyncio
async def test_build_openai_cli_stream_plan_rejects_force_rewrite_envelope(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
class RewriteEnvelope:
def force_stream_rewrite(self) -> bool:
return True
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-cli-stream-rewrite-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={"perf": None},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello", "stream": True}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-cli-stream-rewrite-123",
name="kiro",
proxy=None,
),
endpoint=SimpleNamespace(id="endpoint-cli-stream-rewrite-123", api_format="openai:cli"),
key=SimpleNamespace(id="key-cli-stream-rewrite-123", proxy=None),
mapping_matched_model="gpt-5",
needs_conversion=False,
output_limit=None,
request_candidate_id="cand-cli-stream-rewrite-123",
)
fake_upstream_request = SimpleNamespace(
url="https://api.openai.example/v1/responses",
headers={"content-type": "application/json", "accept": "text/event-stream"},
payload={"model": "gpt-5", "input": "hello", "stream": True},
upstream_is_stream=True,
envelope=RewriteEnvelope(),
tls_profile=None,
)
class FakeCliHandler:
primary_api_format = "openai:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gpt-5"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-cli-stream-rewrite-123"),
SimpleNamespace(id="api-key-cli-stream-rewrite-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route("POST", "/v1/responses")
auth_context = GatewayAuthContext(
user_id="user-cli-stream-rewrite-123",
api_key_id="api-key-cli-stream-rewrite-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello", "stream": True},
auth_context=auth_context,
)
result = await _build_openai_cli_stream_plan(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is None
@pytest.mark.asyncio
async def test_build_openai_cli_stream_plan_rejects_codex_transport_for_direct_executor(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-cli-stream-codex-transport-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={"perf": None},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello", "stream": True}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-cli-stream-codex-transport-123",
name="codex",
proxy=None,
),
endpoint=SimpleNamespace(id="endpoint-cli-stream-codex-transport-123", api_format="openai:cli"),
key=SimpleNamespace(id="key-cli-stream-codex-transport-123", proxy=None),
mapping_matched_model="gpt-5",
needs_conversion=False,
output_limit=None,
request_candidate_id="cand-cli-stream-codex-transport-123",
)
fake_upstream_request = SimpleNamespace(
url="https://chatgpt.com/backend-api/codex/responses",
headers={"content-type": "application/json", "accept": "text/event-stream"},
payload={"model": "gpt-5", "input": "hello", "stream": True},
upstream_is_stream=True,
envelope=None,
tls_profile=None,
)
class FakeCliHandler:
primary_api_format = "openai:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gpt-5"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-cli-stream-codex-transport-123"),
SimpleNamespace(id="api-key-cli-stream-codex-transport-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route("POST", "/v1/responses")
auth_context = GatewayAuthContext(
user_id="user-cli-stream-codex-transport-123",
api_key_id="api-key-cli-stream-codex-transport-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello", "stream": True},
auth_context=auth_context,
)
result = await _build_openai_cli_stream_plan(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is None
@pytest.mark.asyncio
async def test_build_openai_cli_sync_plan_rejects_codex_transport_for_direct_executor(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-cli-sync-codex-transport-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={"perf": None},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello"}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-cli-sync-codex-transport-123",
name="codex",
proxy=None,
request_timeout=None,
),
endpoint=SimpleNamespace(id="endpoint-cli-sync-codex-transport-123", api_format="openai:cli"),
key=SimpleNamespace(id="key-cli-sync-codex-transport-123", proxy=None),
mapping_matched_model="gpt-5",
needs_conversion=False,
output_limit=None,
request_candidate_id="cand-cli-sync-codex-transport-123",
)
fake_upstream_request = SimpleNamespace(
url="https://chatgpt.com/backendapi/codex/responses",
headers={"content-type": "application/json"},
payload={"model": "gpt-5", "input": "hello"},
upstream_is_stream=False,
envelope=None,
tls_profile=None,
)
class FakeCliHandler:
primary_api_format = "openai:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gpt-5"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-cli-sync-codex-transport-123"),
SimpleNamespace(id="api-key-cli-sync-codex-transport-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.get_system_proxy_config_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route("POST", "/v1/responses")
auth_context = GatewayAuthContext(
user_id="user-cli-sync-codex-transport-123",
api_key_id="api-key-cli-sync-codex-transport-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello"},
auth_context=auth_context,
)
result = await _build_openai_cli_sync_plan(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is None