opensandbox-code-interpreter 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,45 @@
1
+ #
2
+ # Copyright 2025 Alibaba Group Holding Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ """
17
+ OpenSandbox Code Interpreter SDK.
18
+
19
+ This package provides secure, isolated code execution capabilities built on top
20
+ of the OpenSandbox infrastructure. It supports multiple programming languages,
21
+ session management, and variable persistence across executions.
22
+ """
23
+
24
+ from code_interpreter.code_interpreter import CodeInterpreter
25
+ from code_interpreter.models.code import (
26
+ CodeContext,
27
+ SupportedLanguage,
28
+ )
29
+ from code_interpreter.sync.code_interpreter import CodeInterpreterSync
30
+
31
+ __all__ = [
32
+ "CodeInterpreter",
33
+ "CodeInterpreterSync",
34
+ "CodeContext",
35
+ "SupportedLanguage",
36
+ ]
37
+
38
+ try:
39
+ from importlib.metadata import PackageNotFoundError
40
+ from importlib.metadata import version as _pkg_version
41
+
42
+ __version__ = _pkg_version("opensandbox-code-interpreter")
43
+ except PackageNotFoundError: # pragma: no cover
44
+ # Fallback for editable/uninstalled source checkouts.
45
+ __version__ = "0.0.0"
@@ -0,0 +1,30 @@
1
+ #
2
+ # Copyright 2025 Alibaba Group Holding Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ """
17
+ Adapter implementations for code execution services.
18
+ """
19
+
20
+ from code_interpreter.adapters.code_adapter import CodesAdapter
21
+ from code_interpreter.adapters.converter.code_execution_converter import (
22
+ CodeExecutionConverter,
23
+ )
24
+ from code_interpreter.adapters.factory import AdapterFactory
25
+
26
+ __all__ = [
27
+ "CodesAdapter",
28
+ "CodeExecutionConverter",
29
+ "AdapterFactory",
30
+ ]
@@ -0,0 +1,268 @@
1
+ #
2
+ # Copyright 2025 Alibaba Group Holding Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ """
17
+ Adapter implementation for code execution service.
18
+
19
+ Provides the concrete implementation of Codes by wrapping auto-generated
20
+ API clients and handling SSE streaming for real-time code execution.
21
+ """
22
+
23
+ import json
24
+ import logging
25
+
26
+ import httpx
27
+ from opensandbox.adapters.converter.event_node import EventNode
28
+ from opensandbox.adapters.converter.exception_converter import (
29
+ ExceptionConverter,
30
+ )
31
+ from opensandbox.adapters.converter.execution_event_dispatcher import (
32
+ ExecutionEventDispatcher,
33
+ )
34
+ from opensandbox.adapters.converter.response_handler import (
35
+ handle_api_error,
36
+ require_parsed,
37
+ )
38
+ from opensandbox.config import ConnectionConfig
39
+ from opensandbox.exceptions import InvalidArgumentException, SandboxApiException
40
+ from opensandbox.models.execd import Execution, ExecutionHandlers
41
+ from opensandbox.models.sandboxes import SandboxEndpoint
42
+
43
+ from code_interpreter.adapters.converter.code_execution_converter import (
44
+ CodeExecutionConverter,
45
+ )
46
+ from code_interpreter.models.code import CodeContext, SupportedLanguage
47
+ from code_interpreter.services.code import Codes
48
+
49
+ logger = logging.getLogger(__name__)
50
+
51
+
52
+ class CodesAdapter(Codes):
53
+ """
54
+ Adapter implementation for code execution service.
55
+
56
+ This adapter wraps auto-generated API clients and provides the concrete
57
+ implementation of the Codes interface. It handles both standard
58
+ API calls and SSE streaming for real-time code execution output.
59
+
60
+ Similar to CommandServiceAdapter, this adapter uses:
61
+ - Generated API clients for simple operations (create_context, interrupt)
62
+ - Direct httpx SSE streaming for run
63
+ - ExceptionConverter for unified exception handling
64
+ """
65
+
66
+ RUN_CODE_PATH = "/code"
67
+ CREATE_CONTEXT_PATH = "/code/context"
68
+
69
+ def __init__(
70
+ self, execd_endpoint: SandboxEndpoint, connection_config: ConnectionConfig
71
+ ) -> None:
72
+ """
73
+ Initialize the code service adapter.
74
+
75
+ Args:
76
+ execd_endpoint: Endpoint for execd daemon connection
77
+ connection_config: Shared connection configuration (transport, headers, timeouts)
78
+ """
79
+ self.execd_endpoint = execd_endpoint
80
+ self.connection_config = connection_config
81
+ from opensandbox.api.execd import Client
82
+
83
+ protocol = self.connection_config.protocol
84
+ base_url = f"{protocol}://{self.execd_endpoint.endpoint}"
85
+ timeout_seconds = self.connection_config.request_timeout.total_seconds()
86
+ timeout = httpx.Timeout(timeout_seconds)
87
+
88
+ headers = {
89
+ "User-Agent": self.connection_config.user_agent,
90
+ **self.connection_config.headers,
91
+ }
92
+
93
+ # Execd API does not require authentication
94
+ self._client = Client(
95
+ base_url=base_url,
96
+ timeout=timeout,
97
+ )
98
+
99
+ # Inject httpx client (adapter-owned)
100
+ self._httpx_client = httpx.AsyncClient(
101
+ base_url=base_url,
102
+ headers=headers,
103
+ timeout=timeout,
104
+ transport=self.connection_config.transport,
105
+ )
106
+ self._client.set_async_httpx_client(self._httpx_client)
107
+
108
+ # SSE client (read timeout disabled)
109
+ sse_headers = {
110
+ **headers,
111
+ "Accept": "text/event-stream",
112
+ "Cache-Control": "no-cache",
113
+ }
114
+ self._sse_client = httpx.AsyncClient(
115
+ headers=sse_headers,
116
+ timeout=httpx.Timeout(
117
+ connect=timeout_seconds,
118
+ read=None,
119
+ write=timeout_seconds,
120
+ pool=None,
121
+ ),
122
+ transport=self.connection_config.transport,
123
+ )
124
+
125
+ async def _get_client(self):
126
+ """Return the client for execd API (no auth required)."""
127
+ return self._client
128
+
129
+ def _get_execd_url(self, path: str) -> str:
130
+ """Build URL for execd endpoint."""
131
+ protocol = self.connection_config.protocol
132
+ return f"{protocol}://{self.execd_endpoint.endpoint}{path}"
133
+
134
+ async def _get_sse_client(self) -> httpx.AsyncClient:
135
+ """Return SSE client (read timeout disabled) for execd streaming."""
136
+ return self._sse_client
137
+
138
+ async def create_context(self, language: str) -> CodeContext:
139
+ """
140
+ Creates a new execution context for code interpretation.
141
+
142
+ Uses the generated API client for this non-streaming operation.
143
+ """
144
+ try:
145
+ from opensandbox.api.execd.api.code_interpreting import create_code_context
146
+ from opensandbox.api.execd.models.code_context_request import (
147
+ CodeContextRequest,
148
+ )
149
+
150
+ client = await self._get_client()
151
+ api_request = CodeContextRequest(language=language)
152
+
153
+ response_obj = await create_code_context.asyncio_detailed(
154
+ client=client,
155
+ body=api_request,
156
+ )
157
+
158
+ handle_api_error(response_obj, "Create code context")
159
+ from opensandbox.api.execd.models.code_context import (
160
+ CodeContext as ApiCodeContext,
161
+ )
162
+
163
+ parsed = require_parsed(response_obj, ApiCodeContext, "Create code context")
164
+ return CodeExecutionConverter.from_api_code_context(parsed)
165
+
166
+ except Exception as e:
167
+ logger.error("Failed to create context", exc_info=e)
168
+ raise ExceptionConverter.to_sandbox_exception(e) from e
169
+
170
+ async def run(
171
+ self,
172
+ code: str,
173
+ *,
174
+ context: CodeContext | None = None,
175
+ handlers: ExecutionHandlers | None = None,
176
+ ) -> Execution:
177
+ """
178
+ Executes code within the specified context using SSE streaming.
179
+
180
+ Similar to CommandServiceAdapter.run, this uses direct httpx
181
+ streaming to handle SSE responses from the execd service.
182
+ """
183
+ if not code.strip():
184
+ raise InvalidArgumentException("Code cannot be empty")
185
+
186
+ try:
187
+ # Default context: ephemeral python context (server-side behavior)
188
+ context = context or CodeContext(language=SupportedLanguage.PYTHON)
189
+ api_request = CodeExecutionConverter.to_api_run_code_request(code, context)
190
+
191
+ # Prepare URL
192
+ url = self._get_execd_url(self.RUN_CODE_PATH)
193
+
194
+ execution = Execution(
195
+ id=None,
196
+ execution_count=None,
197
+ result=[],
198
+ error=None,
199
+ )
200
+
201
+ # Use SSE client for streaming responses (read timeout disabled)
202
+ client = await self._get_sse_client()
203
+
204
+ # Use streaming request for SSE
205
+ async with client.stream("POST", url, json=api_request) as response:
206
+ if response.status_code != 200:
207
+ await response.aread()
208
+ error_body = response.text
209
+ logger.error(
210
+ "Failed to run code. Status: %s, Body: %s",
211
+ response.status_code,
212
+ error_body,
213
+ )
214
+ raise SandboxApiException(
215
+ message=f"Failed to run code. Status code: {response.status_code}",
216
+ status_code=response.status_code,
217
+ )
218
+
219
+ dispatcher = ExecutionEventDispatcher(execution, handlers)
220
+
221
+ async for line in response.aiter_lines():
222
+ if not line.strip():
223
+ continue
224
+
225
+ # Handle potential SSE format "data: ..."
226
+ data = line
227
+ if data.startswith("data:"):
228
+ data = data[5:].strip()
229
+
230
+ try:
231
+ event_dict = json.loads(data)
232
+ event_node = EventNode(**event_dict)
233
+ await dispatcher.dispatch(event_node)
234
+ except json.JSONDecodeError:
235
+ logger.debug("Failed to parse SSE line: %s", line)
236
+ continue
237
+ except Exception as e:
238
+ logger.error("Error processing event: %s", data, exc_info=e)
239
+ continue
240
+
241
+ return execution
242
+
243
+ except Exception as e:
244
+ logger.error(
245
+ "Failed to run code (length: %s)", len(code), exc_info=e
246
+ )
247
+ raise ExceptionConverter.to_sandbox_exception(e) from e
248
+
249
+ async def interrupt(self, execution_id: str) -> None:
250
+ """
251
+ Interrupts a currently running code execution.
252
+
253
+ Uses the generated API client for this operation.
254
+ """
255
+ try:
256
+ from opensandbox.api.execd.api.code_interpreting import interrupt_code
257
+
258
+ client = await self._get_client()
259
+ response_obj = await interrupt_code.asyncio_detailed(
260
+ client=client,
261
+ id=execution_id,
262
+ )
263
+
264
+ handle_api_error(response_obj, "Interrupt code execution")
265
+
266
+ except Exception as e:
267
+ logger.error("Failed to interrupt code execution", exc_info=e)
268
+ raise ExceptionConverter.to_sandbox_exception(e) from e
@@ -0,0 +1,26 @@
1
+ #
2
+ # Copyright 2025 Alibaba Group Holding Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ """
17
+ Model converters for code execution adapters.
18
+ """
19
+
20
+ from code_interpreter.adapters.converter.code_execution_converter import (
21
+ CodeExecutionConverter,
22
+ )
23
+
24
+ __all__ = [
25
+ "CodeExecutionConverter",
26
+ ]
@@ -0,0 +1,108 @@
1
+ #
2
+ # Copyright 2025 Alibaba Group Holding Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ """
17
+ Converter for code execution models between domain and API layers.
18
+
19
+ Handles the transformation of code execution requests and contexts
20
+ between the domain model and auto-generated API client models.
21
+ """
22
+
23
+ from typing import Any
24
+
25
+ from opensandbox.api.execd.models import CodeContext as ApiCodeContext
26
+
27
+ from code_interpreter.models.code import CodeContext
28
+
29
+
30
+ class CodeExecutionConverter:
31
+ """
32
+ Converts code execution models between domain and API representations.
33
+ """
34
+
35
+ @staticmethod
36
+ def to_api_run_code_request(code: str, context: CodeContext | None) -> dict[str, Any]:
37
+ """
38
+ Converts domain code + context to API request dictionary.
39
+
40
+ Args:
41
+ code: Source code to execute
42
+ context: Optional execution context (language + optional id)
43
+
44
+ Returns:
45
+ Dictionary representation for API call
46
+ """
47
+ result: dict[str, Any] = {"code": code}
48
+
49
+ if context is not None:
50
+ result["context"] = CodeExecutionConverter.to_api_code_context(context)
51
+
52
+ return result
53
+
54
+ @staticmethod
55
+ def to_api_code_context(context: CodeContext) -> dict[str, Any]:
56
+ """
57
+ Converts domain CodeContext to API context dictionary.
58
+
59
+ Args:
60
+ context: Domain model code context
61
+
62
+ Returns:
63
+ Dictionary representation for API call
64
+ """
65
+ result: dict[str, Any] = {
66
+ "language": context.language,
67
+ }
68
+
69
+ if context.id:
70
+ result["id"] = context.id
71
+
72
+ return result
73
+
74
+ @staticmethod
75
+ def from_api_code_context(api_context: ApiCodeContext) -> CodeContext:
76
+ """
77
+ Converts API CodeContextResponse to domain CodeContext.
78
+
79
+ Args:
80
+ api_context: API response from create_code_context
81
+
82
+ Returns:
83
+ Domain model code context
84
+ """
85
+ from opensandbox.api.execd.types import UNSET
86
+
87
+ context_id = api_context.id if api_context.id is not UNSET else None
88
+
89
+ return CodeContext(
90
+ id=context_id,
91
+ language=api_context.language
92
+ )
93
+
94
+ @staticmethod
95
+ def from_api_code_context_dict(api_context: dict[str, Any]) -> CodeContext:
96
+ """
97
+ Converts API code context dictionary to domain CodeContext.
98
+
99
+ Args:
100
+ api_context: API response dictionary containing context data
101
+
102
+ Returns:
103
+ Domain model code context
104
+ """
105
+ return CodeContext(
106
+ id=api_context.get("id"),
107
+ language=api_context.get("language", "python")
108
+ )
@@ -0,0 +1,58 @@
1
+ #
2
+ # Copyright 2025 Alibaba Group Holding Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ """
17
+ Factory for creating code interpreter services.
18
+
19
+ Provides a centralized way to create and configure code execution services
20
+ with proper dependency injection and configuration management.
21
+ """
22
+
23
+ from opensandbox.config import ConnectionConfig
24
+ from opensandbox.models.sandboxes import SandboxEndpoint
25
+
26
+ from code_interpreter.adapters.code_adapter import CodesAdapter
27
+ from code_interpreter.services.code import Codes
28
+
29
+
30
+ class AdapterFactory:
31
+ """
32
+ Factory for creating code interpreter service instances.
33
+
34
+ This factory handles the creation of code execution services with proper
35
+ configuration and dependency injection, ensuring all services have access
36
+ to the required HTTP client and endpoint configuration.
37
+ """
38
+
39
+ def __init__(self, connection_config: ConnectionConfig) -> None:
40
+ """
41
+ Initialize the factory with shared connection configuration.
42
+
43
+ Args:
44
+ connection_config: Shared connection configuration (transport, headers, timeouts)
45
+ """
46
+ self.connection_config = connection_config
47
+
48
+ def create_code_execution_service(self, endpoint: SandboxEndpoint) -> Codes:
49
+ """
50
+ Create a code execution service for the specified endpoint.
51
+
52
+ Args:
53
+ endpoint: Sandbox endpoint for code execution services.
54
+
55
+ Returns:
56
+ Configured code service instance.
57
+ """
58
+ return CodesAdapter(endpoint, self.connection_config)