diff --git a/envs/opencode_env/sandbox/interception.py b/envs/opencode_env/sandbox/interception.py index 131d410243..674d8d9a7a 100644 --- a/envs/opencode_env/sandbox/interception.py +++ b/envs/opencode_env/sandbox/interception.py @@ -43,6 +43,7 @@ from dataclasses import dataclass, field from pathlib import Path from typing import Any +from urllib.parse import urlsplit import httpx import uvicorn @@ -216,9 +217,14 @@ def _model_uses_max_completion_tokens(model: str) -> bool: def _resolve_upstream_url(upstream: str) -> str: - """Build the fully qualified chat-completions URL from a base URL.""" + """Build the fully qualified chat-completions URL from a base URL. + + ``/v1`` is only added when the base has no path. A base that already has + one (``/v1``, ``/v1beta/openai``, ``/api/paas/v4``) is used as-is, which is + the URL opencode itself would call in ``black_box`` mode. + """ base = upstream.rstrip("/") - if base.endswith("/v1"): + if urlsplit(base).path: return f"{base}/chat/completions" return f"{base}{CHAT_COMPLETIONS_PATH}" diff --git a/tests/envs/test_opencode_env.py b/tests/envs/test_opencode_env.py index b286118742..4dc77c114d 100644 --- a/tests/envs/test_opencode_env.py +++ b/tests/envs/test_opencode_env.py @@ -346,6 +346,27 @@ def fake_serve(cfg) -> None: assert captured["cfg"].upstream_api_key == "sk-from-env" +def test_interception_keeps_upstream_base_path() -> None: + from opencode_env.sandbox.interception import _resolve_upstream_url + + assert ( + _resolve_upstream_url("https://api.openai.com/v1/") + == "https://api.openai.com/v1/chat/completions" + ) + assert ( + _resolve_upstream_url("http://localhost:8000") + == "http://localhost:8000/v1/chat/completions" + ) + assert ( + _resolve_upstream_url("https://generativelanguage.googleapis.com/v1beta/openai") + == "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions" + ) + assert ( + _resolve_upstream_url("https://api.z.ai/api/paas/v4") + == "https://api.z.ai/api/paas/v4/chat/completions" + ) + + # --------------------------------------------------------------------------- # Integration — only runs when E2B + endpoint creds are present and the # user explicitly opts in via ``pytest -m integration``.