opensandbox-code-interpreter 1.1.0__tar.gz → 1.2.0__tar.gz

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.
Files changed (25) hide show
  1. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/PKG-INFO +9 -18
  2. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/README.md +7 -16
  3. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/pyproject.toml +1 -1
  4. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/adapters/code_adapter.py +42 -30
  5. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/code_interpreter.py +44 -10
  6. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/models/code_sync.py +1 -0
  7. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/services/code.py +3 -3
  8. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/sync/adapters/code_adapter.py +39 -20
  9. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/sync/code_interpreter.py +34 -6
  10. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/.gitignore +0 -0
  11. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/LICENSE +0 -0
  12. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/__init__.py +0 -0
  13. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/adapters/__init__.py +0 -0
  14. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/adapters/converter/__init__.py +0 -0
  15. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/adapters/converter/code_execution_converter.py +0 -0
  16. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/adapters/factory.py +0 -0
  17. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/models/__init__.py +0 -0
  18. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/models/code.py +0 -0
  19. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/py.typed +0 -0
  20. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/services/__init__.py +0 -0
  21. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/sync/__init__.py +0 -0
  22. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/sync/adapters/__init__.py +0 -0
  23. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/sync/adapters/factory.py +0 -0
  24. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/sync/services/__init__.py +0 -0
  25. {opensandbox_code_interpreter-1.1.0 → opensandbox_code_interpreter-1.2.0}/src/code_interpreter/sync/services/code.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: opensandbox-code-interpreter
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: OpenSandbox Code Interpreter Python SDK - Advanced code execution with persistent contexts
5
5
  Project-URL: Homepage, https://open-sandbox.ai
6
6
  Project-URL: Repository, https://github.com/opensandbox-group/OpenSandbox
@@ -223,24 +223,20 @@ Classifier: Programming Language :: Python :: 3.13
223
223
  Classifier: Topic :: Software Development :: Libraries
224
224
  Classifier: Typing :: Typed
225
225
  Requires-Python: >=3.10
226
- Requires-Dist: opensandbox<2.0.0,>=1.1.0
226
+ Requires-Dist: opensandbox<2.0.0,>=1.2.0
227
227
  Requires-Dist: pydantic<3.0,>=2.4.2
228
228
  Description-Content-Type: text/markdown
229
229
 
230
230
  # OpenSandbox Code Interpreter SDK for Python
231
231
 
232
232
 
233
- A Python SDK for executing code in secure, isolated sandboxes. It provides a high-level API for running Python, Java,
234
- Go, TypeScript, and other languages safely, with support for code execution contexts.
233
+ A Python SDK for executing code in secure, isolated sandboxes. It provides a high-level API for running Python, Java, Go, TypeScript, and other languages safely, with support for code execution contexts.
235
234
 
236
235
  ## Prerequisites
237
236
 
238
- This SDK requires a Docker image containing the Code Interpreter runtime environment. You must use the
239
- `opensandbox/code-interpreter` image (or a derivative) which includes pre-installed runtimes for Python, Java, Go,
240
- Node.js, etc.
237
+ This SDK requires a Docker image containing the Code Interpreter runtime environment. You must use the `opensandbox/code-interpreter` image (or a derivative) which includes pre-installed runtimes for Python, Java, Go, Node.js, etc.
241
238
 
242
- For detailed information about supported languages and versions, refer to the
243
- [Environment Documentation](https://github.com/opensandbox-group/sandbox-images).
239
+ For detailed information about supported languages and versions, refer to the [Environment Documentation](https://github.com/opensandbox-group/sandbox-images).
244
240
 
245
241
  ## Installation
246
242
 
@@ -258,8 +254,7 @@ uv add opensandbox-code-interpreter
258
254
 
259
255
  ## Quick Start
260
256
 
261
- The following example demonstrates how to create a sandbox with a specific runtime configuration and execute a simple
262
- script.
257
+ The following example demonstrates how to create a sandbox with a specific runtime configuration and execute a simple script.
263
258
 
264
259
  ```python
265
260
  import asyncio
@@ -366,13 +361,11 @@ execution = await sandbox.commands.run("pip install pandas numpy")
366
361
 
367
362
  ### Docker Image
368
363
 
369
- The Code Interpreter SDK relies on a specialized environment. Ensure your sandbox provider has the
370
- `opensandbox/code-interpreter` image available.
364
+ The Code Interpreter SDK relies on a specialized environment. Ensure your sandbox provider has the `opensandbox/code-interpreter` image available.
371
365
 
372
366
  ### Language Version Selection
373
367
 
374
- You can specify the desired version of a programming language by setting the corresponding environment variable when
375
- creating the `Sandbox`.
368
+ You can specify the desired version of a programming language by setting the corresponding environment variable when creating the `Sandbox`.
376
369
 
377
370
  | Language | Environment Variable | Example Value | Default (if unset) |
378
371
  | -------- | -------------------- | ------------- | ------------------ |
@@ -385,9 +378,7 @@ creating the `Sandbox`.
385
378
 
386
379
  ### 0. Run with `language` (default language context)
387
380
 
388
- You can pass `language` directly (recommended: `SupportedLanguage.*`) and skip `create_context`.
389
- When `context.id` is omitted, **execd will create/reuse a default session for that language**, so
390
- state can persist across runs:
381
+ You can pass `language` directly (recommended: `SupportedLanguage.*`) and skip `create_context`. When `context.id` is omitted, **execd will create/reuse a default session for that language**, so state can persist across runs:
391
382
 
392
383
  ```python
393
384
  from code_interpreter import SupportedLanguage
@@ -1,17 +1,13 @@
1
1
  # OpenSandbox Code Interpreter SDK for Python
2
2
 
3
3
 
4
- A Python SDK for executing code in secure, isolated sandboxes. It provides a high-level API for running Python, Java,
5
- Go, TypeScript, and other languages safely, with support for code execution contexts.
4
+ A Python SDK for executing code in secure, isolated sandboxes. It provides a high-level API for running Python, Java, Go, TypeScript, and other languages safely, with support for code execution contexts.
6
5
 
7
6
  ## Prerequisites
8
7
 
9
- This SDK requires a Docker image containing the Code Interpreter runtime environment. You must use the
10
- `opensandbox/code-interpreter` image (or a derivative) which includes pre-installed runtimes for Python, Java, Go,
11
- Node.js, etc.
8
+ This SDK requires a Docker image containing the Code Interpreter runtime environment. You must use the `opensandbox/code-interpreter` image (or a derivative) which includes pre-installed runtimes for Python, Java, Go, Node.js, etc.
12
9
 
13
- For detailed information about supported languages and versions, refer to the
14
- [Environment Documentation](https://github.com/opensandbox-group/sandbox-images).
10
+ For detailed information about supported languages and versions, refer to the [Environment Documentation](https://github.com/opensandbox-group/sandbox-images).
15
11
 
16
12
  ## Installation
17
13
 
@@ -29,8 +25,7 @@ uv add opensandbox-code-interpreter
29
25
 
30
26
  ## Quick Start
31
27
 
32
- The following example demonstrates how to create a sandbox with a specific runtime configuration and execute a simple
33
- script.
28
+ The following example demonstrates how to create a sandbox with a specific runtime configuration and execute a simple script.
34
29
 
35
30
  ```python
36
31
  import asyncio
@@ -137,13 +132,11 @@ execution = await sandbox.commands.run("pip install pandas numpy")
137
132
 
138
133
  ### Docker Image
139
134
 
140
- The Code Interpreter SDK relies on a specialized environment. Ensure your sandbox provider has the
141
- `opensandbox/code-interpreter` image available.
135
+ The Code Interpreter SDK relies on a specialized environment. Ensure your sandbox provider has the `opensandbox/code-interpreter` image available.
142
136
 
143
137
  ### Language Version Selection
144
138
 
145
- You can specify the desired version of a programming language by setting the corresponding environment variable when
146
- creating the `Sandbox`.
139
+ You can specify the desired version of a programming language by setting the corresponding environment variable when creating the `Sandbox`.
147
140
 
148
141
  | Language | Environment Variable | Example Value | Default (if unset) |
149
142
  | -------- | -------------------- | ------------- | ------------------ |
@@ -156,9 +149,7 @@ creating the `Sandbox`.
156
149
 
157
150
  ### 0. Run with `language` (default language context)
158
151
 
159
- You can pass `language` directly (recommended: `SupportedLanguage.*`) and skip `create_context`.
160
- When `context.id` is omitted, **execd will create/reuse a default session for that language**, so
161
- state can persist across runs:
152
+ You can pass `language` directly (recommended: `SupportedLanguage.*`) and skip `create_context`. When `context.id` is omitted, **execd will create/reuse a default session for that language**, so state can persist across runs:
162
153
 
163
154
  ```python
164
155
  from code_interpreter import SupportedLanguage
@@ -43,7 +43,7 @@ classifiers = [
43
43
  ]
44
44
  dependencies = [
45
45
  "pydantic>=2.4.2,<3.0",
46
- "opensandbox>=1.1.0,<2.0.0",
46
+ "opensandbox>=1.2.0,<2.0.0",
47
47
  ]
48
48
 
49
49
  [project.urls]
@@ -33,15 +33,15 @@ from opensandbox.adapters.converter.execution_event_dispatcher import (
33
33
  ExecutionEventDispatcher,
34
34
  )
35
35
  from opensandbox.adapters.converter.response_handler import (
36
- extract_request_id,
36
+ build_api_exception_from_httpx,
37
37
  handle_api_error,
38
38
  require_parsed,
39
39
  )
40
40
  from opensandbox.config import ConnectionConfig
41
- from opensandbox.exceptions import InvalidArgumentException, SandboxApiException
41
+ from opensandbox.exceptions import InvalidArgumentException
42
42
  from opensandbox.models.execd import Execution, ExecutionHandlers
43
43
  from opensandbox.models.sandboxes import SandboxEndpoint
44
- from opensandbox.transport import unwrap_retry_transport
44
+ from opensandbox.transport import streaming_transport
45
45
 
46
46
  from code_interpreter.adapters.converter.code_execution_converter import (
47
47
  CodeExecutionConverter,
@@ -72,14 +72,8 @@ class CodesAdapter(Codes):
72
72
  """
73
73
  Adapter implementation for code execution service.
74
74
 
75
- This adapter wraps auto-generated API clients and provides the concrete
76
- implementation of the Codes interface. It handles both standard
77
- API calls and SSE streaming for real-time code execution output.
78
-
79
- Similar to CommandServiceAdapter, this adapter uses:
80
- - Generated API clients for simple operations (create_context, interrupt)
81
- - Direct httpx SSE streaming for run
82
- - ExceptionConverter for unified exception handling
75
+ Handles standard API calls plus direct httpx SSE streaming for
76
+ real-time code execution output.
83
77
  """
84
78
 
85
79
  RUN_CODE_PATH = "/code"
@@ -104,11 +98,7 @@ class CodesAdapter(Codes):
104
98
  timeout_seconds = self.connection_config.request_timeout.total_seconds()
105
99
  timeout = httpx.Timeout(timeout_seconds)
106
100
 
107
- headers = {
108
- "User-Agent": self.connection_config.user_agent,
109
- **self.connection_config.headers,
110
- **self.execd_endpoint.headers,
111
- }
101
+ headers = self.execd_endpoint.build_request_headers(self.connection_config)
112
102
 
113
103
  # Execd API does not require authentication
114
104
  self._client = Client(
@@ -116,12 +106,19 @@ class CodesAdapter(Codes):
116
106
  timeout=timeout,
117
107
  )
118
108
 
119
- # Inject httpx client (adapter-owned)
109
+ # Adapter clients must own their transports: aclose() closes them,
110
+ # and the shared/user connection_config transport must survive that.
111
+ self._owns_clients = self.connection_config.owns_transport
112
+ main_transport: httpx.AsyncBaseTransport | None
113
+ if self._owns_clients:
114
+ main_transport = self.connection_config.new_owned_transport()
115
+ else:
116
+ main_transport = self.connection_config.transport
120
117
  self._httpx_client = httpx.AsyncClient(
121
118
  base_url=base_url,
122
119
  headers=headers,
123
120
  timeout=timeout,
124
- transport=self.connection_config.transport,
121
+ transport=main_transport,
125
122
  )
126
123
  self._client.set_async_httpx_client(self._httpx_client)
127
124
 
@@ -131,9 +128,13 @@ class CodesAdapter(Codes):
131
128
  "Accept": "text/event-stream",
132
129
  "Cache-Control": "no-cache",
133
130
  }
134
- # SSE bootstraps bypass the retry wrapper: request bodies are
135
- # not replayable and a non-idempotent status opt-in would cause
136
- # duplicate execution on a resent SSE POST.
131
+ # SSE bootstraps keep only the wake-timeout retry: a non-idempotent
132
+ # status opt-in would cause duplicate execution on a resent SSE POST.
133
+ sse_transport = (
134
+ self.connection_config.new_owned_transport()
135
+ if self._owns_clients
136
+ else self.connection_config.transport
137
+ )
137
138
  self._sse_client = httpx.AsyncClient(
138
139
  headers=sse_headers,
139
140
  timeout=httpx.Timeout(
@@ -142,7 +143,7 @@ class CodesAdapter(Codes):
142
143
  write=timeout_seconds,
143
144
  pool=None,
144
145
  ),
145
- transport=unwrap_retry_transport(self.connection_config.transport),
146
+ transport=streaming_transport(sse_transport),
146
147
  )
147
148
 
148
149
  async def _get_client(self):
@@ -158,6 +159,21 @@ class CodesAdapter(Codes):
158
159
  """Return SSE client (read timeout disabled) for execd streaming."""
159
160
  return self._sse_client
160
161
 
162
+ async def aclose(self) -> None:
163
+ """Release the adapter-owned HTTP clients.
164
+
165
+ Never closes the shared ``connection_config.transport``; the sandbox
166
+ stays usable after ``aclose()``. Clients wrapping a user-supplied
167
+ transport are left open (httpx would close the caller's transport
168
+ with them).
169
+ """
170
+ if not self._owns_clients:
171
+ return
172
+ try:
173
+ await self._httpx_client.aclose()
174
+ finally:
175
+ await self._sse_client.aclose()
176
+
161
177
  async def create_context(self, language: str) -> CodeContext:
162
178
  """
163
179
  Creates a new execution context for code interpretation.
@@ -268,7 +284,7 @@ class CodesAdapter(Codes):
268
284
  """
269
285
  Executes code within the specified context using SSE streaming.
270
286
 
271
- Similar to CommandServiceAdapter.run, this uses direct httpx
287
+ Uses direct httpx
272
288
  streaming to handle SSE responses from the execd service.
273
289
  """
274
290
  if not code.strip():
@@ -299,6 +315,7 @@ class CodesAdapter(Codes):
299
315
  result=[],
300
316
  error=None,
301
317
  )
318
+ execution._is_code_execution = True
302
319
 
303
320
  # Use SSE client for streaming responses (read timeout disabled)
304
321
  client = await self._get_sse_client()
@@ -307,15 +324,10 @@ class CodesAdapter(Codes):
307
324
  async with client.stream("POST", url, json=api_request) as response:
308
325
  if response.status_code != 200:
309
326
  await response.aread()
310
- error_body = response.text
311
327
  logger.error(
312
- f"Failed to run code. Status: {response.status_code}, Body: {error_body}"
313
- )
314
- raise SandboxApiException(
315
- message=f"Failed to run code. Status code: {response.status_code}, Body: {error_body}",
316
- status_code=response.status_code,
317
- request_id=extract_request_id(response.headers),
328
+ f"Failed to run code. Status: {response.status_code}, Body: {response.text}"
318
329
  )
330
+ raise build_api_exception_from_httpx(response, "Run code")
319
331
 
320
332
  dispatcher = ExecutionEventDispatcher(execution, handlers)
321
333
 
@@ -22,6 +22,7 @@ support, session management, and variable persistence.
22
22
  """
23
23
 
24
24
  import asyncio
25
+ import contextlib
25
26
  import logging
26
27
  import time
27
28
  from datetime import timedelta
@@ -64,7 +65,7 @@ class CodeInterpreter:
64
65
 
65
66
  Key Features:
66
67
 
67
- - Multi-language Code Execution: Support for Python, JavaScript, Bash, Java, Kotlin
68
+ - Multi-language Code Execution: Python, JavaScript, TypeScript, Go, Bash
68
69
  - Session Management: Persistent execution contexts with variable state
69
70
  - Sandbox Integration: Full access to underlying sandbox file system and command execution
70
71
  - Streaming Execution: Real-time code execution with output streaming
@@ -73,11 +74,12 @@ class CodeInterpreter:
73
74
  Usage Example:
74
75
 
75
76
  ```python
76
- # First create a sandbox instance
77
-
77
+ # The sandbox must use the opensandbox/code-interpreter image (or a
78
+ # derivative); the strict health check requires the Jupyter runtime.
78
79
  sandbox = await Sandbox.create(
79
- "python:3.11",
80
- resource={"cpu": "1", "memory": "2Gi"}
80
+ "opensandbox/code-interpreter:latest",
81
+ entrypoint=["/opt/code-interpreter/code-interpreter.sh"],
82
+ resource={"cpu": "1", "memory": "2Gi"},
81
83
  )
82
84
 
83
85
  # Then create a code interpreter wrapping the sandbox
@@ -87,9 +89,10 @@ class CodeInterpreter:
87
89
  from code_interpreter.models.code import SupportedLanguage
88
90
  context = await interpreter.codes.create_context(SupportedLanguage.PYTHON)
89
91
  result = await interpreter.codes.run("print('Hello World')", context=context)
90
- print(result.logs.stdout) # Output: Hello World
92
+ print(result.text) # Output: Hello World
91
93
 
92
94
  # Access underlying sandbox for file operations
95
+ from opensandbox.models.filesystem import WriteEntry
93
96
  await interpreter.sandbox.files.write_files([
94
97
  WriteEntry(path="data.txt", data="Hello")
95
98
  ])
@@ -98,10 +101,16 @@ class CodeInterpreter:
98
101
  context=context,
99
102
  )
100
103
 
101
- # Always clean up resources
104
+ # Clean up: closing the interpreter releases its HTTP clients; the
105
+ # underlying sandbox lifecycle stays caller-owned.
106
+ await interpreter.aclose()
102
107
  await sandbox.kill()
103
108
  await sandbox.close()
104
109
  ```
110
+
111
+ CodeInterpreter also supports the async context-manager protocol; exiting
112
+ the ``async with`` block releases the interpreter's HTTP clients (not the
113
+ sandbox).
105
114
  """
106
115
 
107
116
  def __init__(self, sandbox: Sandbox, code_service: Codes) -> None:
@@ -161,6 +170,24 @@ class CodeInterpreter:
161
170
  """
162
171
  return self._sandbox.commands
163
172
 
173
+ async def aclose(self) -> None:
174
+ """
175
+ Release resources owned by the code interpreter.
176
+
177
+ Closes the HTTP clients used by the code execution service. The
178
+ underlying sandbox is NOT affected; manage its lifecycle separately
179
+ (``sandbox.kill()`` / ``sandbox.close()``). Safe to call multiple times.
180
+ """
181
+ aclose = getattr(self._code_service, "aclose", None)
182
+ if aclose is not None:
183
+ await aclose()
184
+
185
+ async def __aenter__(self) -> "CodeInterpreter":
186
+ return self
187
+
188
+ async def __aexit__(self, exc_type, exc, tb) -> None:
189
+ await self.aclose()
190
+
164
191
  @property
165
192
  def metrics(self):
166
193
  """
@@ -335,9 +362,16 @@ class CodeInterpreter:
335
362
  )
336
363
 
337
364
  interpreter = cls(sandbox, code_execution_service)
338
-
339
- if not skip_health_check:
340
- await interpreter.check_ready(ready_timeout, health_check_polling_interval)
365
+ try:
366
+ if not skip_health_check:
367
+ await interpreter.check_ready(
368
+ ready_timeout, health_check_polling_interval
369
+ )
370
+ except Exception:
371
+ # Don't leak the service's clients on a failed creation.
372
+ with contextlib.suppress(Exception):
373
+ await interpreter.aclose()
374
+ raise
341
375
 
342
376
  logger.info(f"Code interpreter {sandbox.id} created successfully")
343
377
 
@@ -27,6 +27,7 @@ class SupportedLanguageSync:
27
27
  GO = "go"
28
28
  TYPESCRIPT = "typescript"
29
29
  BASH = "bash"
30
+ JAVASCRIPT = "javascript"
30
31
 
31
32
 
32
33
  class CodeContextSync(BaseModel):
@@ -38,10 +38,10 @@ class Codes(Protocol):
38
38
  Supported Languages:
39
39
 
40
40
  - Python: Full Python 3.x support with package management
41
- - JavaScript/Node.js: ES6+ with npm package support
41
+ - JavaScript: ES6+ with npm package support
42
+ - TypeScript
43
+ - Go
42
44
  - Bash: Shell scripting with full system access
43
- - Java: Compilation and execution with classpath management
44
- - Kotlin: Script and compiled Kotlin execution
45
45
 
46
46
  Key Features:
47
47
 
@@ -27,19 +27,19 @@ from opensandbox.adapters.converter.exception_converter import (
27
27
  ExceptionConverter,
28
28
  )
29
29
  from opensandbox.adapters.converter.response_handler import (
30
- extract_request_id,
30
+ build_api_exception_from_httpx,
31
31
  handle_api_error,
32
32
  require_parsed,
33
33
  )
34
34
  from opensandbox.config.connection_sync import ConnectionConfigSync
35
- from opensandbox.exceptions import InvalidArgumentException, SandboxApiException
35
+ from opensandbox.exceptions import InvalidArgumentException
36
36
  from opensandbox.models.execd import Execution
37
37
  from opensandbox.models.execd_sync import ExecutionHandlersSync
38
38
  from opensandbox.models.sandboxes import SandboxEndpoint
39
39
  from opensandbox.sync.adapters.converter.execution_event_dispatcher import (
40
40
  ExecutionEventDispatcherSync,
41
41
  )
42
- from opensandbox.transport import unwrap_retry_transport
42
+ from opensandbox.transport import streaming_transport
43
43
 
44
44
  from code_interpreter.models.code_sync import CodeContextSync, SupportedLanguageSync
45
45
  from code_interpreter.sync.services.code import CodesSync
@@ -99,18 +99,22 @@ class CodesAdapterSync(CodesSync):
99
99
  base_url = f"{self.connection_config.protocol}://{self.execd_endpoint.endpoint}"
100
100
  timeout_seconds = self.connection_config.request_timeout.total_seconds()
101
101
  timeout = httpx.Timeout(timeout_seconds)
102
- headers = {
103
- "User-Agent": self.connection_config.user_agent,
104
- **self.connection_config.headers,
105
- **(self.execd_endpoint.headers or {}),
106
- }
107
-
102
+ headers = self.execd_endpoint.build_request_headers(self.connection_config)
103
+
104
+ # Adapter clients must own their transports: close() closes them,
105
+ # and the shared/user connection_config transport must survive that.
106
+ self._owns_clients = self.connection_config.owns_transport
107
+ main_transport: httpx.BaseTransport | None
108
+ if self._owns_clients:
109
+ main_transport = self.connection_config.new_owned_transport()
110
+ else:
111
+ main_transport = self.connection_config.transport
108
112
  self._client = Client(base_url=base_url, timeout=timeout)
109
113
  self._httpx_client = httpx.Client(
110
114
  base_url=base_url,
111
115
  headers=headers,
112
116
  timeout=timeout,
113
- transport=self.connection_config.transport,
117
+ transport=main_transport,
114
118
  )
115
119
  self._client.set_httpx_client(self._httpx_client)
116
120
 
@@ -119,15 +123,19 @@ class CodesAdapterSync(CodesSync):
119
123
  "Accept": "text/event-stream",
120
124
  "Cache-Control": "no-cache",
121
125
  }
122
- # SSE bootstraps bypass the retry wrapper: request bodies are
123
- # not replayable and a non-idempotent status opt-in would cause
124
- # duplicate execution on a resent SSE POST.
126
+ # SSE bootstraps keep only the wake-timeout retry: a non-idempotent
127
+ # status opt-in would cause duplicate execution on a resent SSE POST.
128
+ sse_transport = (
129
+ self.connection_config.new_owned_transport()
130
+ if self._owns_clients
131
+ else self.connection_config.transport
132
+ )
125
133
  self._sse_client = httpx.Client(
126
134
  headers=sse_headers,
127
135
  timeout=httpx.Timeout(
128
136
  connect=timeout_seconds, read=None, write=timeout_seconds, pool=None
129
137
  ),
130
- transport=unwrap_retry_transport(self.connection_config.transport),
138
+ transport=streaming_transport(sse_transport),
131
139
  )
132
140
 
133
141
  def _get_execd_url(self, path: str) -> str:
@@ -136,6 +144,21 @@ class CodesAdapterSync(CodesSync):
136
144
  f"{self.connection_config.protocol}://{self.execd_endpoint.endpoint}{path}"
137
145
  )
138
146
 
147
+ def close(self) -> None:
148
+ """Release the adapter-owned HTTP clients.
149
+
150
+ Never closes the shared ``connection_config.transport``; the sandbox
151
+ stays usable after ``close()``. Clients wrapping a user-supplied
152
+ transport are left open (httpx would close the caller's transport
153
+ with them).
154
+ """
155
+ if not self._owns_clients:
156
+ return
157
+ try:
158
+ self._httpx_client.close()
159
+ finally:
160
+ self._sse_client.close()
161
+
139
162
  def create_context(self, language: str) -> CodeContextSync:
140
163
  """
141
164
  Create a new execution context for code interpretation (sync).
@@ -286,17 +309,13 @@ class CodesAdapterSync(CodesSync):
286
309
 
287
310
  url = self._get_execd_url(self.RUN_CODE_PATH)
288
311
  execution = Execution(id=None, execution_count=None, result=[], error=None)
312
+ execution._is_code_execution = True
289
313
  dispatcher = ExecutionEventDispatcherSync(execution, handlers)
290
314
 
291
315
  with self._sse_client.stream("POST", url, json=api_request) as response:
292
316
  if response.status_code != 200:
293
317
  response.read()
294
- error_body = response.text
295
- raise SandboxApiException(
296
- message=f"Failed to run code. Status code: {response.status_code}, Body: {error_body}",
297
- status_code=response.status_code,
298
- request_id=extract_request_id(response.headers),
299
- )
318
+ raise build_api_exception_from_httpx(response, "Run code")
300
319
 
301
320
  for line in response.iter_lines():
302
321
  if not line or not line.strip():
@@ -17,6 +17,7 @@
17
17
  Synchronous Code Interpreter SDK.
18
18
  """
19
19
 
20
+ import contextlib
20
21
  import logging
21
22
  import time
22
23
  from datetime import timedelta
@@ -73,14 +74,18 @@ class CodeInterpreterSync:
73
74
  ```python
74
75
  from opensandbox.sync.sandbox import SandboxSync
75
76
  from code_interpreter.sync.code_interpreter import CodeInterpreterSync
76
- from code_interpreter.models.code import SupportedLanguage
77
+ from code_interpreter.models.code_sync import SupportedLanguageSync
77
78
 
78
- sandbox = SandboxSync.create("python:3.11")
79
+ sandbox = SandboxSync.create(
80
+ "opensandbox/code-interpreter:latest",
81
+ entrypoint=["/opt/code-interpreter/code-interpreter.sh"],
82
+ )
79
83
  interpreter = CodeInterpreterSync.create(sandbox=sandbox)
80
84
 
81
- ctx = interpreter.codes.create_context(SupportedLanguage.PYTHON)
85
+ ctx = interpreter.codes.create_context(SupportedLanguageSync.PYTHON)
82
86
  result = interpreter.codes.run("print('hi')", context=ctx)
83
87
 
88
+ interpreter.close()
84
89
  sandbox.kill()
85
90
  sandbox.close()
86
91
  ```
@@ -139,6 +144,24 @@ class CodeInterpreterSync:
139
144
  """
140
145
  return self._sandbox.commands
141
146
 
147
+ def close(self) -> None:
148
+ """
149
+ Release resources owned by the code interpreter.
150
+
151
+ Closes the HTTP clients used by the code execution service. The
152
+ underlying sandbox is NOT affected; manage its lifecycle separately
153
+ (``sandbox.kill()`` / ``sandbox.close()``). Safe to call multiple times.
154
+ """
155
+ close = getattr(self._code_service, "close", None)
156
+ if close is not None:
157
+ close()
158
+
159
+ def __enter__(self) -> "CodeInterpreterSync":
160
+ return self
161
+
162
+ def __exit__(self, exc_type, exc, tb) -> None:
163
+ self.close()
164
+
142
165
  @property
143
166
  def metrics(self):
144
167
  """
@@ -289,9 +312,14 @@ class CodeInterpreterSync:
289
312
  code_service = factory.create_code_execution_service(endpoint)
290
313
 
291
314
  interpreter = cls(sandbox, code_service)
292
-
293
- if not skip_health_check:
294
- interpreter.check_ready(ready_timeout, health_check_polling_interval)
315
+ try:
316
+ if not skip_health_check:
317
+ interpreter.check_ready(ready_timeout, health_check_polling_interval)
318
+ except Exception:
319
+ # Don't leak the service's clients on a failed creation.
320
+ with contextlib.suppress(Exception):
321
+ interpreter.close()
322
+ raise
295
323
 
296
324
  logger.info(f"Code interpreter {sandbox.id} created successfully")
297
325
  return interpreter