opensandbox-code-interpreter 0.1.0__tar.gz → 0.1.2__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-0.1.0 → opensandbox_code_interpreter-0.1.2}/.gitignore +11 -0
  2. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/PKG-INFO +50 -12
  3. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/README.md +45 -7
  4. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/pyproject.toml +15 -8
  5. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/__init__.py +3 -3
  6. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/adapters/code_adapter.py +96 -3
  7. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/adapters/converter/code_execution_converter.py +2 -2
  8. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/code_interpreter.py +4 -130
  9. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/models/code.py +1 -0
  10. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/services/code.py +67 -2
  11. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/sync/adapters/code_adapter.py +129 -10
  12. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/sync/code_interpreter.py +4 -112
  13. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/sync/services/code.py +41 -2
  14. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/LICENSE +0 -0
  15. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/adapters/__init__.py +0 -0
  16. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/adapters/converter/__init__.py +0 -0
  17. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/adapters/factory.py +0 -0
  18. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/models/__init__.py +0 -0
  19. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/models/code_sync.py +0 -0
  20. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/py.typed +0 -0
  21. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/services/__init__.py +0 -0
  22. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/sync/__init__.py +0 -0
  23. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/sync/adapters/__init__.py +0 -0
  24. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/sync/adapters/factory.py +0 -0
  25. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/sync/services/__init__.py +0 -0
@@ -133,6 +133,8 @@ dist
133
133
  # Gatsby files
134
134
  .cache/
135
135
  public
136
+ !docs/public/
137
+ !docs/public/CNAME
136
138
 
137
139
  # Serverless directories
138
140
  .serverless/
@@ -222,6 +224,9 @@ secrets/
222
224
 
223
225
  # Generated API documentation
224
226
  docs/generated/
227
+ docs/.vitepress/generated/
228
+ docs/.vitepress/dist/
229
+ docs/.vitepress/cache/
225
230
  apidocs/
226
231
 
227
232
  # Test results
@@ -263,3 +268,9 @@ nbdist/
263
268
  # Generated files
264
269
  generated/
265
270
  **/generated/**
271
+
272
+ # gVisor runtime binaries (downloaded dynamically)
273
+ kubernetes/test/kind/gvisor/runsc
274
+ kubernetes/test/kind/gvisor/containerd-shim-runsc-v1
275
+ bin/
276
+ obj/
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensandbox-code-interpreter
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: OpenSandbox Code Interpreter Python SDK - Advanced code execution with persistent contexts
5
- Project-URL: Homepage, https://github.com/alibaba/OpenSandbox
5
+ Project-URL: Homepage, https://open-sandbox.ai
6
6
  Project-URL: Repository, https://github.com/alibaba/OpenSandbox
7
- Project-URL: Documentation, https://docs.opensandbox.io
7
+ Project-URL: Documentation, https://open-sandbox.ai
8
8
  Project-URL: Issues, https://github.com/alibaba/OpenSandbox/issues
9
9
  Author-email: OpenSandbox Team <ninan.nn@alibaba-inc.com>
10
10
  License: Apache License
@@ -223,11 +223,11 @@ 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<0.2.0,>=0.1.0.dev0
227
- Requires-Dist: pydantic<3.0,>=2.0.0
226
+ Requires-Dist: opensandbox<0.2.0,>=0.1.6
227
+ Requires-Dist: pydantic<3.0,>=2.4.2
228
228
  Description-Content-Type: text/markdown
229
229
 
230
- # Alibaba Code Interpreter SDK for Python
230
+ # OpenSandbox Code Interpreter SDK for Python
231
231
 
232
232
  English | [中文](README_zh.md)
233
233
 
@@ -274,14 +274,14 @@ from opensandbox.config import ConnectionConfig
274
274
  async def main() -> None:
275
275
  # 1. Configure connection
276
276
  config = ConnectionConfig(
277
- domain="api.opensandbox.dev",
277
+ domain="api.opensandbox.io",
278
278
  api_key="your-api-key",
279
279
  request_timeout=timedelta(seconds=60),
280
280
  )
281
281
 
282
282
  # 2. Create a Sandbox with the code-interpreter image + runtime versions
283
283
  sandbox = await Sandbox.create(
284
- "opensandbox/code-interpreter:latest",
284
+ "opensandbox/code-interpreter:v1.0.2",
285
285
  connection_config=config,
286
286
  entrypoint=["/opt/opensandbox/code-interpreter.sh"],
287
287
  env={
@@ -306,12 +306,16 @@ async def main() -> None:
306
306
  context=context,
307
307
  )
308
308
 
309
+ # Alternatively, you can pass a language directly (recommended: SupportedLanguage.*).
310
+ # This uses the default context for that language (state can persist across runs).
311
+ # result = await interpreter.codes.run("print('hi')", language=SupportedLanguage.PYTHON)
312
+
309
313
  # 7. Print output
310
314
  if result.result:
311
315
  print(result.result[0].text)
312
316
 
313
317
  # 8. Cleanup remote instance (optional but recommended)
314
- await interpreter.kill()
318
+ await sandbox.kill()
315
319
 
316
320
 
317
321
  if __name__ == "__main__":
@@ -331,14 +335,14 @@ from opensandbox import SandboxSync
331
335
  from opensandbox.config import ConnectionConfigSync
332
336
 
333
337
  config = ConnectionConfigSync(
334
- domain="api.opensandbox.dev",
338
+ domain="api.opensandbox.io",
335
339
  api_key="your-api-key",
336
340
  request_timeout=timedelta(seconds=60),
337
341
  transport=httpx.HTTPTransport(limits=httpx.Limits(max_connections=20)),
338
342
  )
339
343
 
340
344
  sandbox = SandboxSync.create(
341
- "opensandbox/code-interpreter:latest",
345
+ "opensandbox/code-interpreter:v1.0.2",
342
346
  connection_config=config,
343
347
  entrypoint=["/opt/opensandbox/code-interpreter.sh"],
344
348
  env={"PYTHON_VERSION": "3.11"},
@@ -348,7 +352,15 @@ with sandbox:
348
352
  result = interpreter.codes.run("result = 2 + 2\nresult")
349
353
  if result.result:
350
354
  print(result.result[0].text)
351
- interpreter.kill()
355
+ sandbox.kill()
356
+ ```
357
+
358
+ ### Installing Python packages at runtime
359
+
360
+ You can install packages directly via `sandbox.commands.run(...)`:
361
+
362
+ ```python
363
+ execution = await sandbox.commands.run("pip install pandas numpy")
352
364
  ```
353
365
 
354
366
  ## Runtime Configuration
@@ -372,6 +384,32 @@ creating the `Sandbox`.
372
384
 
373
385
  ## Usage Examples
374
386
 
387
+ ### 0. Run with `language` (default language context)
388
+
389
+ You can pass `language` directly (recommended: `SupportedLanguage.*`) and skip `create_context`.
390
+ When `context.id` is omitted, **execd will create/reuse a default session for that language**, so
391
+ state can persist across runs:
392
+
393
+ ```python
394
+ from code_interpreter import SupportedLanguage
395
+
396
+ execution = await interpreter.codes.run(
397
+ "result = 2 + 2\nresult",
398
+ language=SupportedLanguage.PYTHON,
399
+ )
400
+ assert execution.result and execution.result[0].text == "4"
401
+ ```
402
+
403
+ State persistence example (default Python context):
404
+
405
+ ```python
406
+ from code_interpreter import SupportedLanguage
407
+
408
+ await interpreter.codes.run("x = 42", language=SupportedLanguage.PYTHON)
409
+ execution = await interpreter.codes.run("result = x\nresult", language=SupportedLanguage.PYTHON)
410
+ assert execution.result and execution.result[0].text == "42"
411
+ ```
412
+
375
413
  ### 1. Java Code Execution
376
414
 
377
415
  ```python
@@ -1,4 +1,4 @@
1
- # Alibaba Code Interpreter SDK for Python
1
+ # OpenSandbox Code Interpreter SDK for Python
2
2
 
3
3
  English | [中文](README_zh.md)
4
4
 
@@ -45,14 +45,14 @@ from opensandbox.config import ConnectionConfig
45
45
  async def main() -> None:
46
46
  # 1. Configure connection
47
47
  config = ConnectionConfig(
48
- domain="api.opensandbox.dev",
48
+ domain="api.opensandbox.io",
49
49
  api_key="your-api-key",
50
50
  request_timeout=timedelta(seconds=60),
51
51
  )
52
52
 
53
53
  # 2. Create a Sandbox with the code-interpreter image + runtime versions
54
54
  sandbox = await Sandbox.create(
55
- "opensandbox/code-interpreter:latest",
55
+ "opensandbox/code-interpreter:v1.0.2",
56
56
  connection_config=config,
57
57
  entrypoint=["/opt/opensandbox/code-interpreter.sh"],
58
58
  env={
@@ -77,12 +77,16 @@ async def main() -> None:
77
77
  context=context,
78
78
  )
79
79
 
80
+ # Alternatively, you can pass a language directly (recommended: SupportedLanguage.*).
81
+ # This uses the default context for that language (state can persist across runs).
82
+ # result = await interpreter.codes.run("print('hi')", language=SupportedLanguage.PYTHON)
83
+
80
84
  # 7. Print output
81
85
  if result.result:
82
86
  print(result.result[0].text)
83
87
 
84
88
  # 8. Cleanup remote instance (optional but recommended)
85
- await interpreter.kill()
89
+ await sandbox.kill()
86
90
 
87
91
 
88
92
  if __name__ == "__main__":
@@ -102,14 +106,14 @@ from opensandbox import SandboxSync
102
106
  from opensandbox.config import ConnectionConfigSync
103
107
 
104
108
  config = ConnectionConfigSync(
105
- domain="api.opensandbox.dev",
109
+ domain="api.opensandbox.io",
106
110
  api_key="your-api-key",
107
111
  request_timeout=timedelta(seconds=60),
108
112
  transport=httpx.HTTPTransport(limits=httpx.Limits(max_connections=20)),
109
113
  )
110
114
 
111
115
  sandbox = SandboxSync.create(
112
- "opensandbox/code-interpreter:latest",
116
+ "opensandbox/code-interpreter:v1.0.2",
113
117
  connection_config=config,
114
118
  entrypoint=["/opt/opensandbox/code-interpreter.sh"],
115
119
  env={"PYTHON_VERSION": "3.11"},
@@ -119,7 +123,15 @@ with sandbox:
119
123
  result = interpreter.codes.run("result = 2 + 2\nresult")
120
124
  if result.result:
121
125
  print(result.result[0].text)
122
- interpreter.kill()
126
+ sandbox.kill()
127
+ ```
128
+
129
+ ### Installing Python packages at runtime
130
+
131
+ You can install packages directly via `sandbox.commands.run(...)`:
132
+
133
+ ```python
134
+ execution = await sandbox.commands.run("pip install pandas numpy")
123
135
  ```
124
136
 
125
137
  ## Runtime Configuration
@@ -143,6 +155,32 @@ creating the `Sandbox`.
143
155
 
144
156
  ## Usage Examples
145
157
 
158
+ ### 0. Run with `language` (default language context)
159
+
160
+ You can pass `language` directly (recommended: `SupportedLanguage.*`) and skip `create_context`.
161
+ When `context.id` is omitted, **execd will create/reuse a default session for that language**, so
162
+ state can persist across runs:
163
+
164
+ ```python
165
+ from code_interpreter import SupportedLanguage
166
+
167
+ execution = await interpreter.codes.run(
168
+ "result = 2 + 2\nresult",
169
+ language=SupportedLanguage.PYTHON,
170
+ )
171
+ assert execution.result and execution.result[0].text == "4"
172
+ ```
173
+
174
+ State persistence example (default Python context):
175
+
176
+ ```python
177
+ from code_interpreter import SupportedLanguage
178
+
179
+ await interpreter.codes.run("x = 42", language=SupportedLanguage.PYTHON)
180
+ execution = await interpreter.codes.run("result = x\nresult", language=SupportedLanguage.PYTHON)
181
+ assert execution.result and execution.result[0].text == "42"
182
+ ```
183
+
146
184
  ### 1. Java Code Execution
147
185
 
148
186
  ```python
@@ -42,14 +42,14 @@ classifiers = [
42
42
  "Typing :: Typed",
43
43
  ]
44
44
  dependencies = [
45
- "pydantic>=2.0.0,<3.0",
46
- "opensandbox>=0.1.0.dev0,<0.2.0",
45
+ "pydantic>=2.4.2,<3.0",
46
+ "opensandbox>=0.1.6,<0.2.0",
47
47
  ]
48
48
 
49
49
  [project.urls]
50
- Homepage = "https://github.com/alibaba/OpenSandbox"
50
+ Homepage = "https://open-sandbox.ai"
51
51
  Repository = "https://github.com/alibaba/OpenSandbox"
52
- Documentation = "https://docs.opensandbox.io"
52
+ Documentation = "https://open-sandbox.ai"
53
53
  Issues = "https://github.com/alibaba/OpenSandbox/issues"
54
54
 
55
55
  [tool.hatch.version]
@@ -105,17 +105,24 @@ ignore = [
105
105
  "__init__.py" = ["F401"]
106
106
 
107
107
  [tool.pyright]
108
- typeCheckingMode = "strict"
108
+ typeCheckingMode = "standard"
109
109
  pythonVersion = "3.10"
110
+ pythonPlatform = "All"
111
+
110
112
  include = ["src"]
111
- venvPath = "."
112
- venv = ".venv"
113
+
113
114
  exclude = [
114
115
  "**/node_modules",
115
116
  "**/__pycache__",
116
- "**/.*",
117
+ "src/opensandbox/api/**",
117
118
  ]
118
119
 
120
+ venvPath = "."
121
+ venv = ".venv"
122
+
123
+ reportMissingImports = true
124
+ reportMissingTypeStubs = false
125
+
119
126
  [tool.pytest.ini_options]
120
127
  minversion = "6.0"
121
128
  addopts = "-ra -q --strict-markers --strict-config"
@@ -21,6 +21,9 @@ of the OpenSandbox infrastructure. It supports multiple programming languages,
21
21
  session management, and variable persistence across executions.
22
22
  """
23
23
 
24
+ from importlib.metadata import PackageNotFoundError
25
+ from importlib.metadata import version as _pkg_version
26
+
24
27
  from code_interpreter.code_interpreter import CodeInterpreter
25
28
  from code_interpreter.models.code import (
26
29
  CodeContext,
@@ -36,9 +39,6 @@ __all__ = [
36
39
  ]
37
40
 
38
41
  try:
39
- from importlib.metadata import PackageNotFoundError
40
- from importlib.metadata import version as _pkg_version
41
-
42
42
  __version__ = _pkg_version("opensandbox-code-interpreter")
43
43
  except PackageNotFoundError: # pragma: no cover
44
44
  # Fallback for editable/uninstalled source checkouts.
@@ -22,6 +22,7 @@ API clients and handling SSE streaming for real-time code execution.
22
22
 
23
23
  import json
24
24
  import logging
25
+ import time
25
26
 
26
27
  import httpx
27
28
  from opensandbox.adapters.converter.event_node import EventNode
@@ -32,6 +33,7 @@ from opensandbox.adapters.converter.execution_event_dispatcher import (
32
33
  ExecutionEventDispatcher,
33
34
  )
34
35
  from opensandbox.adapters.converter.response_handler import (
36
+ extract_request_id,
35
37
  handle_api_error,
36
38
  require_parsed,
37
39
  )
@@ -49,6 +51,22 @@ from code_interpreter.services.code import Codes
49
51
  logger = logging.getLogger(__name__)
50
52
 
51
53
 
54
+ def _normalize_sse_event(event_dict: dict) -> dict:
55
+ if "type" in event_dict and "timestamp" in event_dict:
56
+ return event_dict
57
+ if "code" in event_dict and "message" in event_dict:
58
+ return {
59
+ "type": "error",
60
+ "timestamp": int(time.time() * 1000),
61
+ "error": {
62
+ "ename": str(event_dict["code"]),
63
+ "evalue": str(event_dict["message"]),
64
+ "traceback": [],
65
+ },
66
+ }
67
+ return event_dict
68
+
69
+
52
70
  class CodesAdapter(Codes):
53
71
  """
54
72
  Adapter implementation for code execution service.
@@ -88,6 +106,7 @@ class CodesAdapter(Codes):
88
106
  headers = {
89
107
  "User-Agent": self.connection_config.user_agent,
90
108
  **self.connection_config.headers,
109
+ **self.execd_endpoint.headers,
91
110
  }
92
111
 
93
112
  # Execd API does not require authentication
@@ -167,10 +186,76 @@ class CodesAdapter(Codes):
167
186
  logger.error("Failed to create context", exc_info=e)
168
187
  raise ExceptionConverter.to_sandbox_exception(e) from e
169
188
 
189
+ async def get_context(self, context_id: str) -> CodeContext:
190
+ try:
191
+ from opensandbox.api.execd.api.code_interpreting import get_context
192
+ from opensandbox.api.execd.models.code_context import (
193
+ CodeContext as ApiCodeContext,
194
+ )
195
+
196
+ client = await self._get_client()
197
+ response_obj = await get_context.asyncio_detailed(
198
+ client=client,
199
+ context_id=context_id,
200
+ )
201
+ handle_api_error(response_obj, "Get code context")
202
+ parsed = require_parsed(response_obj, ApiCodeContext, "Get code context")
203
+ return CodeExecutionConverter.from_api_code_context(parsed)
204
+ except Exception as e:
205
+ logger.error("Failed to get context", exc_info=e)
206
+ raise ExceptionConverter.to_sandbox_exception(e) from e
207
+
208
+ async def list_contexts(self, language: str) -> list[CodeContext]:
209
+ try:
210
+ from opensandbox.api.execd.api.code_interpreting import list_contexts
211
+
212
+ client = await self._get_client()
213
+ response_obj = await list_contexts.asyncio_detailed(
214
+ client=client,
215
+ language=language,
216
+ )
217
+ handle_api_error(response_obj, "List code contexts")
218
+ parsed_list = require_parsed(response_obj, list, "List code contexts")
219
+ return [CodeExecutionConverter.from_api_code_context(c) for c in parsed_list]
220
+ except Exception as e:
221
+ logger.error("Failed to list contexts", exc_info=e)
222
+ raise ExceptionConverter.to_sandbox_exception(e) from e
223
+
224
+ async def delete_context(self, context_id: str) -> None:
225
+ try:
226
+ from opensandbox.api.execd.api.code_interpreting import delete_context
227
+
228
+ client = await self._get_client()
229
+ response_obj = await delete_context.asyncio_detailed(
230
+ client=client,
231
+ context_id=context_id,
232
+ )
233
+ handle_api_error(response_obj, "Delete code context")
234
+ except Exception as e:
235
+ logger.error("Failed to delete context", exc_info=e)
236
+ raise ExceptionConverter.to_sandbox_exception(e) from e
237
+
238
+ async def delete_contexts(self, language: str) -> None:
239
+ try:
240
+ from opensandbox.api.execd.api.code_interpreting import (
241
+ delete_contexts_by_language,
242
+ )
243
+
244
+ client = await self._get_client()
245
+ response_obj = await delete_contexts_by_language.asyncio_detailed(
246
+ client=client,
247
+ language=language,
248
+ )
249
+ handle_api_error(response_obj, "Delete code contexts by language")
250
+ except Exception as e:
251
+ logger.error("Failed to delete contexts", exc_info=e)
252
+ raise ExceptionConverter.to_sandbox_exception(e) from e
253
+
170
254
  async def run(
171
255
  self,
172
256
  code: str,
173
257
  *,
258
+ language: str | None = None,
174
259
  context: CodeContext | None = None,
175
260
  handlers: ExecutionHandlers | None = None,
176
261
  ) -> Execution:
@@ -184,8 +269,15 @@ class CodesAdapter(Codes):
184
269
  raise InvalidArgumentException("Code cannot be empty")
185
270
 
186
271
  try:
187
- # Default context: ephemeral python context (server-side behavior)
188
- context = context or CodeContext(language=SupportedLanguage.PYTHON)
272
+ if context is not None and language is not None and context.language != language:
273
+ raise InvalidArgumentException(
274
+ f"language '{language}' must match context.language '{context.language}'"
275
+ )
276
+
277
+ # Default context: language default context (server-side behavior).
278
+ # When context.id is omitted, execd will create/reuse a default session per language.
279
+ if context is None:
280
+ context = CodeContext(language=language or SupportedLanguage.PYTHON)
189
281
  api_request = CodeExecutionConverter.to_api_run_code_request(code, context)
190
282
 
191
283
  # Prepare URL
@@ -214,6 +306,7 @@ class CodesAdapter(Codes):
214
306
  raise SandboxApiException(
215
307
  message=f"Failed to run code. Status code: {response.status_code}",
216
308
  status_code=response.status_code,
309
+ request_id=extract_request_id(response.headers),
217
310
  )
218
311
 
219
312
  dispatcher = ExecutionEventDispatcher(execution, handlers)
@@ -228,7 +321,7 @@ class CodesAdapter(Codes):
228
321
  data = data[5:].strip()
229
322
 
230
323
  try:
231
- event_dict = json.loads(data)
324
+ event_dict = _normalize_sse_event(json.loads(data))
232
325
  event_node = EventNode(**event_dict)
233
326
  await dispatcher.dispatch(event_node)
234
327
  except json.JSONDecodeError:
@@ -82,9 +82,9 @@ class CodeExecutionConverter:
82
82
  Returns:
83
83
  Domain model code context
84
84
  """
85
- from opensandbox.api.execd.types import UNSET
85
+ from opensandbox.api.execd.types import Unset
86
86
 
87
- context_id = api_context.id if api_context.id is not UNSET else None
87
+ context_id = None if isinstance(api_context.id, Unset) else api_context.id
88
88
 
89
89
  return CodeContext(
90
90
  id=context_id,
@@ -22,19 +22,12 @@ support, session management, and variable persistence.
22
22
  """
23
23
 
24
24
  import logging
25
- from datetime import datetime, timedelta, timezone
26
- from uuid import UUID
27
25
 
28
26
  from opensandbox.exceptions import (
29
27
  InvalidArgumentException,
30
28
  SandboxException,
31
29
  SandboxInternalException,
32
30
  )
33
- from opensandbox.models.sandboxes import (
34
- SandboxEndpoint,
35
- SandboxInfo,
36
- SandboxMetrics,
37
- )
38
31
  from opensandbox.sandbox import Sandbox
39
32
 
40
33
  from code_interpreter.adapters.factory import AdapterFactory
@@ -87,8 +80,8 @@ class CodeInterpreter:
87
80
  )
88
81
 
89
82
  # Always clean up resources
90
- await interpreter.kill()
91
- await interpreter.sandbox.close()
83
+ await sandbox.kill()
84
+ await sandbox.close()
92
85
  ```
93
86
  """
94
87
 
@@ -116,12 +109,12 @@ class CodeInterpreter:
116
109
  return self._sandbox
117
110
 
118
111
  @property
119
- def id(self) -> UUID:
112
+ def id(self) -> str:
120
113
  """
121
114
  Gets the unique identifier of this code interpreter (same as underlying sandbox ID).
122
115
 
123
116
  Returns:
124
- UUID of the code interpreter/sandbox
117
+ ID of the code interpreter/sandbox
125
118
  """
126
119
  return self._sandbox.id
127
120
 
@@ -176,125 +169,6 @@ class CodeInterpreter:
176
169
  """
177
170
  return self._code_service
178
171
 
179
- async def get_endpoint(self, port: int) -> SandboxEndpoint:
180
- """
181
- Gets a specific network endpoint for the underlying sandbox.
182
-
183
- This allows access to specific ports exposed by the sandbox, which can be
184
- useful for connecting to additional services or debugging interfaces.
185
-
186
- Args:
187
- port: The port number to get the endpoint for
188
-
189
- Returns:
190
- Endpoint information including host, port, and connection details
191
-
192
- Raises:
193
- SandboxException: If endpoint cannot be retrieved
194
- """
195
- return await self._sandbox.get_endpoint(port)
196
-
197
- async def get_info(self) -> SandboxInfo:
198
- """
199
- Gets the current status of this sandbox.
200
-
201
- Returns:
202
- Current sandbox status including state and metadata
203
-
204
- Raises:
205
- SandboxException: If status cannot be retrieved
206
- """
207
- return await self._sandbox.get_info()
208
-
209
- async def get_metrics(self) -> SandboxMetrics:
210
- """
211
- Gets the current resource usage metrics for the underlying sandbox.
212
-
213
- Provides real-time information about CPU usage, memory consumption,
214
- disk I/O, and other performance metrics.
215
-
216
- Returns:
217
- Current sandbox metrics including CPU, memory, and I/O statistics
218
-
219
- Raises:
220
- SandboxException: If metrics cannot be retrieved
221
- """
222
- return await self._sandbox.get_metrics()
223
-
224
- async def renew(self, timeout: timedelta | int) -> None:
225
- """
226
- Renew the sandbox expiration time to delay automatic termination.
227
-
228
- The new expiration time will be set to the current time plus the provided duration.
229
-
230
- Args:
231
- timeout: Duration to add to the current time to set the new expiration.
232
- Can be timedelta or seconds as int.
233
-
234
- Raises:
235
- SandboxException: If the operation fails
236
- """
237
- if isinstance(timeout, int):
238
- timeout = timedelta(seconds=timeout)
239
-
240
- logger.info(
241
- "Renew code interpreter %s timeout, estimated expiration to %s",
242
- self.id,
243
- datetime.now(timezone.utc) + timeout,
244
- )
245
- await self._sandbox.renew(timeout)
246
-
247
- async def pause(self) -> None:
248
- """
249
- Pauses the sandbox while preserving its state.
250
-
251
- The sandbox will transition to PAUSED state and can be resumed later.
252
- All running processes will be suspended.
253
-
254
- Raises:
255
- SandboxException: If pause operation fails
256
- """
257
- logger.info("Pausing code interpreter: %s", self.id)
258
- await self._sandbox.pause()
259
-
260
- async def resume(self) -> None:
261
- """
262
- Resumes a previously paused code interpreter.
263
-
264
- The sandbox will transition from PAUSED to RUNNING state and all
265
- suspended processes will be resumed.
266
-
267
- Raises:
268
- SandboxException: If resume operation fails
269
- """
270
- logger.info("Resuming code interpreter: %s", self.id)
271
- await self._sandbox.resume()
272
-
273
- async def kill(self) -> None:
274
- """
275
- This method sends a termination signal to the remote sandbox instance, causing it to stop immediately.
276
- This is an irreversible operation.
277
-
278
- Note: This method does NOT close the local `Sandbox` object resources (like connection pools).
279
- You should call `close()` or use async context manager to clean up local resources.
280
-
281
- Raises:
282
- SandboxException: If termination fails
283
- """
284
- logger.info("Killing code interpreter: %s", self.id)
285
- await self._sandbox.kill()
286
-
287
- async def is_healthy(self) -> bool:
288
- """
289
- Checks if the code interpreter and its underlying sandbox are healthy and responsive.
290
-
291
- This performs health checks on both the sandbox infrastructure and code execution services.
292
-
293
- Returns:
294
- True if both sandbox and code execution services are healthy, False otherwise
295
- """
296
- return await self._sandbox.is_healthy()
297
-
298
172
  @classmethod
299
173
  async def create(cls, sandbox: Sandbox) -> "CodeInterpreter":
300
174
  """
@@ -35,6 +35,7 @@ class SupportedLanguage:
35
35
  GO = "go"
36
36
  TYPESCRIPT = "typescript"
37
37
  BASH = "bash"
38
+ JAVASCRIPT = "javascript"
38
39
 
39
40
 
40
41
  class CodeContext(BaseModel):
@@ -20,7 +20,7 @@ Defines the contract for multi-language code interpretation with context managem
20
20
  session persistence, and real-time execution capabilities.
21
21
  """
22
22
 
23
- from typing import Protocol
23
+ from typing import Protocol, overload
24
24
 
25
25
  from opensandbox.models.execd import Execution, ExecutionHandlers
26
26
 
@@ -91,10 +91,71 @@ class Codes(Protocol):
91
91
  """
92
92
  ...
93
93
 
94
+ async def get_context(self, context_id: str) -> CodeContext:
95
+ """
96
+ Get an existing execution context by id.
97
+
98
+ Args:
99
+ context_id: Context/session id
100
+
101
+ Returns:
102
+ The existing CodeContext
103
+ """
104
+ ...
105
+
106
+ async def list_contexts(self, language: str) -> list[CodeContext]:
107
+ """
108
+ List active contexts under a given language/runtime.
109
+
110
+ Args:
111
+ language: Execution runtime (e.g. "python", "bash")
112
+
113
+ Returns:
114
+ List of contexts
115
+ """
116
+ ...
117
+
118
+ async def delete_context(self, context_id: str) -> None:
119
+ """
120
+ Delete an execution context by id.
121
+
122
+ Args:
123
+ context_id: Context/session id to delete
124
+ """
125
+ ...
126
+
127
+ async def delete_contexts(self, language: str) -> None:
128
+ """
129
+ Delete all execution contexts under a given language/runtime.
130
+
131
+ Args:
132
+ language: Execution runtime (e.g. "python", "bash")
133
+ """
134
+ ...
135
+
136
+ @overload
137
+ async def run(
138
+ self,
139
+ code: str,
140
+ *,
141
+ context: CodeContext,
142
+ handlers: ExecutionHandlers | None = None,
143
+ ) -> Execution: ...
144
+
145
+ @overload
146
+ async def run(
147
+ self,
148
+ code: str,
149
+ *,
150
+ language: str,
151
+ handlers: ExecutionHandlers | None = None,
152
+ ) -> Execution: ...
153
+
94
154
  async def run(
95
155
  self,
96
156
  code: str,
97
157
  *,
158
+ language: str | None = None,
98
159
  context: CodeContext | None = None,
99
160
  handlers: ExecutionHandlers | None = None,
100
161
  ) -> Execution:
@@ -115,7 +176,11 @@ class Codes(Protocol):
115
176
 
116
177
  Args:
117
178
  code: Source code to execute.
118
- context: Execution context (language + optional id). If None, a temporary Python context is used.
179
+ language: Convenience language selector for this run. If provided and ``context`` is None,
180
+ a **default context for this language** is used (execd will create/reuse a default
181
+ session when ``context.id`` is omitted). If both ``language`` and ``context`` are
182
+ provided, they must match.
183
+ context: Execution context (language + optional id). If None, the default Python context is used.
119
184
  handlers: Optional streaming handlers for stdout/stderr/events.
120
185
 
121
186
  Returns:
@@ -19,6 +19,7 @@ Synchronous adapter for code execution service (including SSE streaming).
19
19
 
20
20
  import json
21
21
  import logging
22
+ import time
22
23
 
23
24
  import httpx
24
25
  from opensandbox.adapters.converter.event_node import EventNode
@@ -26,6 +27,7 @@ from opensandbox.adapters.converter.exception_converter import (
26
27
  ExceptionConverter,
27
28
  )
28
29
  from opensandbox.adapters.converter.response_handler import (
30
+ extract_request_id,
29
31
  handle_api_error,
30
32
  require_parsed,
31
33
  )
@@ -44,6 +46,22 @@ from code_interpreter.sync.services.code import CodesSync
44
46
  logger = logging.getLogger(__name__)
45
47
 
46
48
 
49
+ def _normalize_sse_event(event_dict: dict) -> dict:
50
+ if "type" in event_dict and "timestamp" in event_dict:
51
+ return event_dict
52
+ if "code" in event_dict and "message" in event_dict:
53
+ return {
54
+ "type": "error",
55
+ "timestamp": int(time.time() * 1000),
56
+ "error": {
57
+ "ename": str(event_dict["code"]),
58
+ "evalue": str(event_dict["message"]),
59
+ "traceback": [],
60
+ },
61
+ }
62
+ return event_dict
63
+
64
+
47
65
  class CodesAdapterSync(CodesSync):
48
66
  """
49
67
  Synchronous adapter for code execution service.
@@ -63,7 +81,9 @@ class CodesAdapterSync(CodesSync):
63
81
  RUN_CODE_PATH = "/code"
64
82
  CREATE_CONTEXT_PATH = "/code/context"
65
83
 
66
- def __init__(self, execd_endpoint: SandboxEndpoint, connection_config: ConnectionConfigSync) -> None:
84
+ def __init__(
85
+ self, execd_endpoint: SandboxEndpoint, connection_config: ConnectionConfigSync
86
+ ) -> None:
67
87
  """
68
88
  Initialize the code service adapter (sync).
69
89
 
@@ -78,7 +98,11 @@ class CodesAdapterSync(CodesSync):
78
98
  base_url = f"{self.connection_config.protocol}://{self.execd_endpoint.endpoint}"
79
99
  timeout_seconds = self.connection_config.request_timeout.total_seconds()
80
100
  timeout = httpx.Timeout(timeout_seconds)
81
- headers = {"User-Agent": self.connection_config.user_agent, **self.connection_config.headers}
101
+ headers = {
102
+ "User-Agent": self.connection_config.user_agent,
103
+ **self.connection_config.headers,
104
+ **(self.execd_endpoint.headers or {}),
105
+ }
82
106
 
83
107
  self._client = Client(base_url=base_url, timeout=timeout)
84
108
  self._httpx_client = httpx.Client(
@@ -89,16 +113,24 @@ class CodesAdapterSync(CodesSync):
89
113
  )
90
114
  self._client.set_httpx_client(self._httpx_client)
91
115
 
92
- sse_headers = {**headers, "Accept": "text/event-stream", "Cache-Control": "no-cache"}
116
+ sse_headers = {
117
+ **headers,
118
+ "Accept": "text/event-stream",
119
+ "Cache-Control": "no-cache",
120
+ }
93
121
  self._sse_client = httpx.Client(
94
122
  headers=sse_headers,
95
- timeout=httpx.Timeout(connect=timeout_seconds, read=None, write=timeout_seconds, pool=None),
123
+ timeout=httpx.Timeout(
124
+ connect=timeout_seconds, read=None, write=timeout_seconds, pool=None
125
+ ),
96
126
  transport=self.connection_config.transport,
97
127
  )
98
128
 
99
129
  def _get_execd_url(self, path: str) -> str:
100
130
  """Build URL for execd endpoint."""
101
- return f"{self.connection_config.protocol}://{self.execd_endpoint.endpoint}{path}"
131
+ return (
132
+ f"{self.connection_config.protocol}://{self.execd_endpoint.endpoint}{path}"
133
+ )
102
134
 
103
135
  def create_context(self, language: str) -> CodeContextSync:
104
136
  """
@@ -114,7 +146,7 @@ class CodesAdapterSync(CodesSync):
114
146
  from opensandbox.api.execd.models.code_context_request import (
115
147
  CodeContextRequest,
116
148
  )
117
- from opensandbox.api.execd.types import UNSET
149
+ from opensandbox.api.execd.types import Unset
118
150
 
119
151
  response_obj = create_code_context.sync_detailed(
120
152
  client=self._client,
@@ -122,16 +154,86 @@ class CodesAdapterSync(CodesSync):
122
154
  )
123
155
  handle_api_error(response_obj, "Create code context")
124
156
  parsed = require_parsed(response_obj, ApiCodeContext, "Create code context")
125
- context_id = parsed.id if parsed.id is not UNSET else None
157
+ context_id = None if isinstance(parsed.id, Unset) else parsed.id
126
158
  return CodeContextSync(id=context_id, language=parsed.language)
127
159
  except Exception as e:
128
160
  logger.error("Failed to create context", exc_info=e)
129
161
  raise ExceptionConverter.to_sandbox_exception(e) from e
130
162
 
163
+ def get_context(self, context_id: str) -> CodeContextSync:
164
+ try:
165
+ from opensandbox.api.execd.api.code_interpreting import get_context
166
+ from opensandbox.api.execd.models.code_context import (
167
+ CodeContext as ApiCodeContext,
168
+ )
169
+ from opensandbox.api.execd.types import Unset
170
+
171
+ response_obj = get_context.sync_detailed(
172
+ client=self._client,
173
+ context_id=context_id,
174
+ )
175
+ handle_api_error(response_obj, "Get code context")
176
+ parsed = require_parsed(response_obj, ApiCodeContext, "Get code context")
177
+ context_id_val = None if isinstance(parsed.id, Unset) else parsed.id
178
+ return CodeContextSync(id=context_id_val, language=parsed.language)
179
+ except Exception as e:
180
+ logger.error("Failed to get context", exc_info=e)
181
+ raise ExceptionConverter.to_sandbox_exception(e) from e
182
+
183
+ def list_contexts(self, language: str) -> list[CodeContextSync]:
184
+ try:
185
+ from opensandbox.api.execd.api.code_interpreting import list_contexts
186
+ from opensandbox.api.execd.types import UNSET
187
+
188
+ response_obj = list_contexts.sync_detailed(
189
+ client=self._client,
190
+ language=language,
191
+ )
192
+ handle_api_error(response_obj, "List code contexts")
193
+ parsed_list = require_parsed(response_obj, list, "List code contexts")
194
+ result: list[CodeContextSync] = []
195
+ for c in parsed_list:
196
+ # c is an API CodeContext model
197
+ context_id_val = c.id if c.id is not UNSET else None
198
+ result.append(CodeContextSync(id=context_id_val, language=c.language))
199
+ return result
200
+ except Exception as e:
201
+ logger.error("Failed to list contexts", exc_info=e)
202
+ raise ExceptionConverter.to_sandbox_exception(e) from e
203
+
204
+ def delete_context(self, context_id: str) -> None:
205
+ try:
206
+ from opensandbox.api.execd.api.code_interpreting import delete_context
207
+
208
+ response_obj = delete_context.sync_detailed(
209
+ client=self._client,
210
+ context_id=context_id,
211
+ )
212
+ handle_api_error(response_obj, "Delete code context")
213
+ except Exception as e:
214
+ logger.error("Failed to delete context", exc_info=e)
215
+ raise ExceptionConverter.to_sandbox_exception(e) from e
216
+
217
+ def delete_contexts(self, language: str) -> None:
218
+ try:
219
+ from opensandbox.api.execd.api.code_interpreting import (
220
+ delete_contexts_by_language,
221
+ )
222
+
223
+ response_obj = delete_contexts_by_language.sync_detailed(
224
+ client=self._client,
225
+ language=language,
226
+ )
227
+ handle_api_error(response_obj, "Delete code contexts by language")
228
+ except Exception as e:
229
+ logger.error("Failed to delete contexts", exc_info=e)
230
+ raise ExceptionConverter.to_sandbox_exception(e) from e
231
+
131
232
  def run(
132
233
  self,
133
234
  code: str,
134
235
  *,
236
+ language: str | None = None,
135
237
  context: CodeContextSync | None = None,
136
238
  handlers: ExecutionHandlersSync | None = None,
137
239
  ) -> Execution:
@@ -155,7 +257,21 @@ class CodesAdapterSync(CodesSync):
155
257
  raise InvalidArgumentException("Code cannot be empty")
156
258
 
157
259
  try:
158
- context = context or CodeContextSync(language=SupportedLanguageSync.PYTHON)
260
+ if (
261
+ context is not None
262
+ and language is not None
263
+ and context.language != language
264
+ ):
265
+ raise InvalidArgumentException(
266
+ f"language '{language}' must match context.language '{context.language}'"
267
+ )
268
+
269
+ if context is None:
270
+ # Default context: language default context (server-side behavior).
271
+ # When context.id is omitted, execd will create/reuse a default session per language.
272
+ context = CodeContextSync(
273
+ language=language or SupportedLanguageSync.PYTHON
274
+ )
159
275
  api_request = {
160
276
  "code": code,
161
277
  "context": {
@@ -174,6 +290,7 @@ class CodesAdapterSync(CodesSync):
174
290
  raise SandboxApiException(
175
291
  message=f"Failed to run code. Status code: {response.status_code}",
176
292
  status_code=response.status_code,
293
+ request_id=extract_request_id(response.headers),
177
294
  )
178
295
 
179
296
  for line in response.iter_lines():
@@ -183,7 +300,7 @@ class CodesAdapterSync(CodesSync):
183
300
  if data.startswith("data:"):
184
301
  data = data[5:].strip()
185
302
  try:
186
- event_dict = json.loads(data)
303
+ event_dict = _normalize_sse_event(json.loads(data))
187
304
  event_node = EventNode(**event_dict)
188
305
  dispatcher.dispatch(event_node)
189
306
  except json.JSONDecodeError:
@@ -208,7 +325,9 @@ class CodesAdapterSync(CodesSync):
208
325
  try:
209
326
  from opensandbox.api.execd.api.code_interpreting import interrupt_code
210
327
 
211
- response_obj = interrupt_code.sync_detailed(client=self._client, id=execution_id)
328
+ response_obj = interrupt_code.sync_detailed(
329
+ client=self._client, id=execution_id
330
+ )
212
331
  handle_api_error(response_obj, "Interrupt code execution")
213
332
  except Exception as e:
214
333
  logger.error("Failed to interrupt code execution", exc_info=e)
@@ -18,8 +18,6 @@ Synchronous Code Interpreter SDK.
18
18
  """
19
19
 
20
20
  import logging
21
- from datetime import datetime, timedelta, timezone
22
- from uuid import UUID
23
21
 
24
22
  from opensandbox.constants import DEFAULT_EXECD_PORT
25
23
  from opensandbox.exceptions import (
@@ -27,11 +25,6 @@ from opensandbox.exceptions import (
27
25
  SandboxException,
28
26
  SandboxInternalException,
29
27
  )
30
- from opensandbox.models.sandboxes import (
31
- SandboxEndpoint,
32
- SandboxInfo,
33
- SandboxMetrics,
34
- )
35
28
  from opensandbox.sync.sandbox import SandboxSync
36
29
 
37
30
  from code_interpreter.sync.adapters.factory import AdapterFactorySync
@@ -54,8 +47,8 @@ class CodeInterpreterSync:
54
47
 
55
48
  - **Blocking**: Do not call these methods directly from an asyncio event loop thread.
56
49
  If you need non-blocking behavior, prefer the async :class:`~code_interpreter.code_interpreter.CodeInterpreter`.
57
- - **Lifecycle**: Remote lifecycle is owned by the underlying sandbox. This class delegates
58
- pause/resume/kill/renew/metrics to the sandbox.
50
+ - **Lifecycle**: Remote lifecycle is owned by the underlying sandbox; call methods on
51
+ ``interpreter.sandbox`` for pause/resume/kill/renew/metrics/info/endpoints.
59
52
 
60
53
  Usage Example:
61
54
 
@@ -99,12 +92,12 @@ class CodeInterpreterSync:
99
92
  return self._sandbox
100
93
 
101
94
  @property
102
- def id(self) -> UUID:
95
+ def id(self) -> str:
103
96
  """
104
97
  Gets the unique identifier of this code interpreter (same as underlying sandbox ID).
105
98
 
106
99
  Returns:
107
- UUID of the code interpreter/sandbox
100
+ ID of the code interpreter/sandbox
108
101
  """
109
102
  return self._sandbox.id
110
103
 
@@ -153,107 +146,6 @@ class CodeInterpreterSync:
153
146
  """
154
147
  return self._code_service
155
148
 
156
- def get_endpoint(self, port: int) -> SandboxEndpoint:
157
- """
158
- Gets a specific network endpoint for the underlying sandbox.
159
-
160
- Args:
161
- port: The port number to get the endpoint for
162
-
163
- Returns:
164
- Endpoint information including host, port, and connection details
165
-
166
- Raises:
167
- SandboxException: If endpoint cannot be retrieved
168
- """
169
- return self._sandbox.get_endpoint(port)
170
-
171
- def get_info(self) -> SandboxInfo:
172
- """
173
- Gets the current status of this sandbox.
174
-
175
- Returns:
176
- Current sandbox status including state and metadata
177
-
178
- Raises:
179
- SandboxException: If status cannot be retrieved
180
- """
181
- return self._sandbox.get_info()
182
-
183
- def get_metrics(self) -> SandboxMetrics:
184
- """
185
- Gets the current resource usage metrics for the underlying sandbox.
186
-
187
- Returns:
188
- Current sandbox metrics including CPU, memory, and I/O statistics
189
-
190
- Raises:
191
- SandboxException: If metrics cannot be retrieved
192
- """
193
- return self._sandbox.get_metrics()
194
-
195
- def renew(self, timeout: timedelta | int) -> None:
196
- """
197
- Renew the sandbox expiration time to delay automatic termination.
198
-
199
- Args:
200
- timeout: Duration to add to the current time to set the new expiration.
201
- Can be timedelta or seconds as int.
202
-
203
- Raises:
204
- SandboxException: If the operation fails
205
- """
206
- if isinstance(timeout, int):
207
- timeout = timedelta(seconds=timeout)
208
- logger.info(
209
- "Renew code interpreter %s timeout, estimated expiration to %s",
210
- self.id,
211
- datetime.now(timezone.utc) + timeout,
212
- )
213
- self._sandbox.renew(timeout)
214
-
215
- def pause(self) -> None:
216
- """
217
- Pauses the sandbox while preserving its state.
218
-
219
- Raises:
220
- SandboxException: If pause operation fails
221
- """
222
- logger.info("Pausing code interpreter: %s", self.id)
223
- self._sandbox.pause()
224
-
225
- def resume(self) -> None:
226
- """
227
- Resumes a previously paused sandbox.
228
-
229
- Raises:
230
- SandboxException: If resume operation fails
231
- """
232
- logger.info("Resuming code interpreter: %s", self.id)
233
- self._sandbox.resume()
234
-
235
- def kill(self) -> None:
236
- """
237
- Terminate the remote sandbox instance (irreversible).
238
-
239
- Note: This method does NOT close the local `SandboxSync` object resources (like connection pools).
240
- You should call `sandbox().close()` or use the sync context manager on the sandbox to clean up.
241
-
242
- Raises:
243
- SandboxException: If termination fails
244
- """
245
- logger.info("Killing code interpreter: %s", self.id)
246
- self._sandbox.kill()
247
-
248
- def is_healthy(self) -> bool:
249
- """
250
- Checks if the code interpreter and its underlying sandbox are healthy and responsive.
251
-
252
- Returns:
253
- True if sandbox is healthy, False otherwise
254
- """
255
- return self._sandbox.is_healthy()
256
-
257
149
  @classmethod
258
150
  def create(cls, sandbox: SandboxSync) -> "CodeInterpreterSync":
259
151
  """
@@ -22,7 +22,7 @@ session persistence, and real-time execution capabilities (SSE streaming), **in
22
22
  This is the sync counterpart of :mod:`code_interpreter.services.code`.
23
23
  """
24
24
 
25
- from typing import Protocol
25
+ from typing import Protocol, overload
26
26
 
27
27
  from opensandbox.models.execd import Execution
28
28
  from opensandbox.models.execd_sync import ExecutionHandlersSync
@@ -73,10 +73,45 @@ class CodesSync(Protocol):
73
73
  """
74
74
  ...
75
75
 
76
+ def get_context(self, context_id: str) -> CodeContextSync:
77
+ """Get an existing execution context by id (blocking)."""
78
+ ...
79
+
80
+ def list_contexts(self, language: str) -> list[CodeContextSync]:
81
+ """List active contexts under a given language/runtime (blocking)."""
82
+ ...
83
+
84
+ def delete_context(self, context_id: str) -> None:
85
+ """Delete an execution context by id (blocking)."""
86
+ ...
87
+
88
+ def delete_contexts(self, language: str) -> None:
89
+ """Delete all contexts under a language/runtime (blocking)."""
90
+ ...
91
+
92
+ @overload
93
+ def run(
94
+ self,
95
+ code: str,
96
+ *,
97
+ context: CodeContextSync,
98
+ handlers: ExecutionHandlersSync | None = None,
99
+ ) -> Execution: ...
100
+
101
+ @overload
102
+ def run(
103
+ self,
104
+ code: str,
105
+ *,
106
+ language: str,
107
+ handlers: ExecutionHandlersSync | None = None,
108
+ ) -> Execution: ...
109
+
76
110
  def run(
77
111
  self,
78
112
  code: str,
79
113
  *,
114
+ language: str | None = None,
80
115
  context: CodeContextSync | None = None,
81
116
  handlers: ExecutionHandlersSync | None = None,
82
117
  ) -> Execution:
@@ -95,7 +130,11 @@ class CodesSync(Protocol):
95
130
 
96
131
  Args:
97
132
  code: Source code to execute.
98
- context: Execution context (language + optional id). If None, a temporary Python context is used.
133
+ language: Convenience language selector for this run. If provided and ``context`` is None,
134
+ a **default context for this language** is used (execd will create/reuse a default
135
+ session when ``context.id`` is omitted). If both ``language`` and ``context`` are
136
+ provided, they must match.
137
+ context: Execution context (language + optional id). If None, the default Python context is used.
99
138
  handlers: Optional streaming handlers for stdout/stderr/events.
100
139
 
101
140
  Returns: