Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ class CodesAdapterSync(CodesSync):
RUN_CODE_PATH = "/code"
CREATE_CONTEXT_PATH = "/code/context"

def __init__(self, execd_endpoint: SandboxEndpoint, connection_config: ConnectionConfigSync) -> None:
def __init__(
self, execd_endpoint: SandboxEndpoint, connection_config: ConnectionConfigSync
) -> None:
"""
Initialize the code service adapter (sync).

Expand All @@ -96,7 +98,11 @@ def __init__(self, execd_endpoint: SandboxEndpoint, connection_config: Connectio
base_url = f"{self.connection_config.protocol}://{self.execd_endpoint.endpoint}"
timeout_seconds = self.connection_config.request_timeout.total_seconds()
timeout = httpx.Timeout(timeout_seconds)
headers = {"User-Agent": self.connection_config.user_agent, **self.connection_config.headers}
headers = {
"User-Agent": self.connection_config.user_agent,
**self.connection_config.headers,
**(self.execd_endpoint.headers or {}),
}

self._client = Client(base_url=base_url, timeout=timeout)
self._httpx_client = httpx.Client(
Expand All @@ -107,16 +113,24 @@ def __init__(self, execd_endpoint: SandboxEndpoint, connection_config: Connectio
)
self._client.set_httpx_client(self._httpx_client)

sse_headers = {**headers, "Accept": "text/event-stream", "Cache-Control": "no-cache"}
sse_headers = {
**headers,
"Accept": "text/event-stream",
"Cache-Control": "no-cache",
}
self._sse_client = httpx.Client(
headers=sse_headers,
timeout=httpx.Timeout(connect=timeout_seconds, read=None, write=timeout_seconds, pool=None),
timeout=httpx.Timeout(
connect=timeout_seconds, read=None, write=timeout_seconds, pool=None
),
transport=self.connection_config.transport,
)

def _get_execd_url(self, path: str) -> str:
"""Build URL for execd endpoint."""
return f"{self.connection_config.protocol}://{self.execd_endpoint.endpoint}{path}"
return (
f"{self.connection_config.protocol}://{self.execd_endpoint.endpoint}{path}"
)

def create_context(self, language: str) -> CodeContextSync:
"""
Expand Down Expand Up @@ -243,15 +257,21 @@ def run(
raise InvalidArgumentException("Code cannot be empty")

try:
if context is not None and language is not None and context.language != language:
if (
context is not None
and language is not None
and context.language != language
):
raise InvalidArgumentException(
f"language '{language}' must match context.language '{context.language}'"
)

if context is None:
# Default context: language default context (server-side behavior).
# When context.id is omitted, execd will create/reuse a default session per language.
context = CodeContextSync(language=language or SupportedLanguageSync.PYTHON)
context = CodeContextSync(
language=language or SupportedLanguageSync.PYTHON
)
api_request = {
"code": code,
"context": {
Expand Down Expand Up @@ -305,7 +325,9 @@ def interrupt(self, execution_id: str) -> None:
try:
from opensandbox.api.execd.api.code_interpreting import interrupt_code

response_obj = interrupt_code.sync_detailed(client=self._client, id=execution_id)
response_obj = interrupt_code.sync_detailed(
client=self._client, id=execution_id
)
handle_api_error(response_obj, "Interrupt code execution")
except Exception as e:
logger.error("Failed to interrupt code execution", exc_info=e)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#
# Copyright 2025 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from opensandbox.config.connection_sync import ConnectionConfigSync
from opensandbox.models.sandboxes import SandboxEndpoint

from code_interpreter.sync.adapters.code_adapter import CodesAdapterSync


def test_sync_adapter_merges_endpoint_headers_into_both_clients() -> None:
cfg = ConnectionConfigSync(protocol="http", headers={"X-Base": "base"})
endpoint = SandboxEndpoint(
endpoint="localhost:44772",
headers={"X-Endpoint": "endpoint"},
)

adapter = CodesAdapterSync(endpoint, cfg)

assert adapter._httpx_client.headers["X-Base"] == "base"
assert adapter._httpx_client.headers["X-Endpoint"] == "endpoint"
assert adapter._sse_client.headers["X-Base"] == "base"
assert adapter._sse_client.headers["X-Endpoint"] == "endpoint"


def test_sync_adapter_endpoint_headers_override_connection_headers() -> None:
cfg = ConnectionConfigSync(protocol="http", headers={"X-Shared": "base"})
endpoint = SandboxEndpoint(
endpoint="localhost:44772",
headers={"X-Shared": "endpoint"},
)

adapter = CodesAdapterSync(endpoint, cfg)

assert adapter._httpx_client.headers["X-Shared"] == "endpoint"
assert adapter._sse_client.headers["X-Shared"] == "endpoint"