opensandbox-code-interpreter 0.1.0__tar.gz → 0.1.1__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.1}/PKG-INFO +37 -7
  2. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/README.md +35 -5
  3. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/pyproject.toml +12 -5
  4. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/__init__.py +3 -3
  5. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/adapters/code_adapter.py +75 -2
  6. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/adapters/converter/code_execution_converter.py +2 -2
  7. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/code_interpreter.py +4 -130
  8. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/models/code.py +1 -0
  9. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/services/code.py +67 -2
  10. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/sync/adapters/code_adapter.py +81 -3
  11. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/sync/code_interpreter.py +4 -112
  12. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/sync/services/code.py +41 -2
  13. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/.gitignore +0 -0
  14. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/LICENSE +0 -0
  15. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/adapters/__init__.py +0 -0
  16. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/adapters/converter/__init__.py +0 -0
  17. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/adapters/factory.py +0 -0
  18. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/models/__init__.py +0 -0
  19. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/models/code_sync.py +0 -0
  20. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/py.typed +0 -0
  21. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/services/__init__.py +0 -0
  22. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/sync/__init__.py +0 -0
  23. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/sync/adapters/__init__.py +0 -0
  24. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/sync/adapters/factory.py +0 -0
  25. {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.1}/src/code_interpreter/sync/services/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensandbox-code-interpreter
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: OpenSandbox Code Interpreter Python SDK - Advanced code execution with persistent contexts
5
5
  Project-URL: Homepage, https://github.com/alibaba/OpenSandbox
6
6
  Project-URL: Repository, https://github.com/alibaba/OpenSandbox
@@ -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
226
+ Requires-Dist: opensandbox<0.2.0,>=0.1.1
227
227
  Requires-Dist: pydantic<3.0,>=2.0.0
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,7 +274,7 @@ 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
  )
@@ -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,7 +335,7 @@ 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)),
@@ -348,7 +352,7 @@ 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()
352
356
  ```
353
357
 
354
358
  ## Runtime Configuration
@@ -372,6 +376,32 @@ creating the `Sandbox`.
372
376
 
373
377
  ## Usage Examples
374
378
 
379
+ ### 0. Run with `language` (default language context)
380
+
381
+ You can pass `language` directly (recommended: `SupportedLanguage.*`) and skip `create_context`.
382
+ When `context.id` is omitted, **execd will create/reuse a default session for that language**, so
383
+ state can persist across runs:
384
+
385
+ ```python
386
+ from code_interpreter import SupportedLanguage
387
+
388
+ execution = await interpreter.codes.run(
389
+ "result = 2 + 2\nresult",
390
+ language=SupportedLanguage.PYTHON,
391
+ )
392
+ assert execution.result and execution.result[0].text == "4"
393
+ ```
394
+
395
+ State persistence example (default Python context):
396
+
397
+ ```python
398
+ from code_interpreter import SupportedLanguage
399
+
400
+ await interpreter.codes.run("x = 42", language=SupportedLanguage.PYTHON)
401
+ execution = await interpreter.codes.run("result = x\nresult", language=SupportedLanguage.PYTHON)
402
+ assert execution.result and execution.result[0].text == "42"
403
+ ```
404
+
375
405
  ### 1. Java Code Execution
376
406
 
377
407
  ```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,7 +45,7 @@ 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
  )
@@ -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,7 +106,7 @@ 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)),
@@ -119,7 +123,7 @@ 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()
123
127
  ```
124
128
 
125
129
  ## Runtime Configuration
@@ -143,6 +147,32 @@ creating the `Sandbox`.
143
147
 
144
148
  ## Usage Examples
145
149
 
150
+ ### 0. Run with `language` (default language context)
151
+
152
+ You can pass `language` directly (recommended: `SupportedLanguage.*`) and skip `create_context`.
153
+ When `context.id` is omitted, **execd will create/reuse a default session for that language**, so
154
+ state can persist across runs:
155
+
156
+ ```python
157
+ from code_interpreter import SupportedLanguage
158
+
159
+ execution = await interpreter.codes.run(
160
+ "result = 2 + 2\nresult",
161
+ language=SupportedLanguage.PYTHON,
162
+ )
163
+ assert execution.result and execution.result[0].text == "4"
164
+ ```
165
+
166
+ State persistence example (default Python context):
167
+
168
+ ```python
169
+ from code_interpreter import SupportedLanguage
170
+
171
+ await interpreter.codes.run("x = 42", language=SupportedLanguage.PYTHON)
172
+ execution = await interpreter.codes.run("result = x\nresult", language=SupportedLanguage.PYTHON)
173
+ assert execution.result and execution.result[0].text == "42"
174
+ ```
175
+
146
176
  ### 1. Java Code Execution
147
177
 
148
178
  ```python
@@ -43,7 +43,7 @@ classifiers = [
43
43
  ]
44
44
  dependencies = [
45
45
  "pydantic>=2.0.0,<3.0",
46
- "opensandbox>=0.1.0.dev0,<0.2.0",
46
+ "opensandbox>=0.1.1,<0.2.0",
47
47
  ]
48
48
 
49
49
  [project.urls]
@@ -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.
@@ -167,10 +167,76 @@ class CodesAdapter(Codes):
167
167
  logger.error("Failed to create context", exc_info=e)
168
168
  raise ExceptionConverter.to_sandbox_exception(e) from e
169
169
 
170
+ async def get_context(self, context_id: str) -> CodeContext:
171
+ try:
172
+ from opensandbox.api.execd.api.code_interpreting import get_context
173
+ from opensandbox.api.execd.models.code_context import (
174
+ CodeContext as ApiCodeContext,
175
+ )
176
+
177
+ client = await self._get_client()
178
+ response_obj = await get_context.asyncio_detailed(
179
+ client=client,
180
+ context_id=context_id,
181
+ )
182
+ handle_api_error(response_obj, "Get code context")
183
+ parsed = require_parsed(response_obj, ApiCodeContext, "Get code context")
184
+ return CodeExecutionConverter.from_api_code_context(parsed)
185
+ except Exception as e:
186
+ logger.error("Failed to get context", exc_info=e)
187
+ raise ExceptionConverter.to_sandbox_exception(e) from e
188
+
189
+ async def list_contexts(self, language: str) -> list[CodeContext]:
190
+ try:
191
+ from opensandbox.api.execd.api.code_interpreting import list_contexts
192
+
193
+ client = await self._get_client()
194
+ response_obj = await list_contexts.asyncio_detailed(
195
+ client=client,
196
+ language=language,
197
+ )
198
+ handle_api_error(response_obj, "List code contexts")
199
+ parsed_list = require_parsed(response_obj, list, "List code contexts")
200
+ return [CodeExecutionConverter.from_api_code_context(c) for c in parsed_list]
201
+ except Exception as e:
202
+ logger.error("Failed to list contexts", exc_info=e)
203
+ raise ExceptionConverter.to_sandbox_exception(e) from e
204
+
205
+ async def delete_context(self, context_id: str) -> None:
206
+ try:
207
+ from opensandbox.api.execd.api.code_interpreting import delete_context
208
+
209
+ client = await self._get_client()
210
+ response_obj = await delete_context.asyncio_detailed(
211
+ client=client,
212
+ context_id=context_id,
213
+ )
214
+ handle_api_error(response_obj, "Delete code context")
215
+ except Exception as e:
216
+ logger.error("Failed to delete context", exc_info=e)
217
+ raise ExceptionConverter.to_sandbox_exception(e) from e
218
+
219
+ async def delete_contexts(self, language: str) -> None:
220
+ try:
221
+ from opensandbox.api.execd.api.code_interpreting import (
222
+ delete_contexts_by_language,
223
+ )
224
+
225
+ client = await self._get_client()
226
+ response_obj = await delete_contexts_by_language.asyncio_detailed(
227
+ client=client,
228
+ language=language,
229
+ )
230
+ handle_api_error(response_obj, "Delete code contexts by language")
231
+ except Exception as e:
232
+ logger.error("Failed to delete contexts", exc_info=e)
233
+ raise ExceptionConverter.to_sandbox_exception(e) from e
234
+
170
235
  async def run(
171
236
  self,
172
237
  code: str,
173
238
  *,
239
+ language: str | None = None,
174
240
  context: CodeContext | None = None,
175
241
  handlers: ExecutionHandlers | None = None,
176
242
  ) -> Execution:
@@ -184,8 +250,15 @@ class CodesAdapter(Codes):
184
250
  raise InvalidArgumentException("Code cannot be empty")
185
251
 
186
252
  try:
187
- # Default context: ephemeral python context (server-side behavior)
188
- context = context or CodeContext(language=SupportedLanguage.PYTHON)
253
+ if context is not None and language is not None and context.language != language:
254
+ raise InvalidArgumentException(
255
+ f"language '{language}' must match context.language '{context.language}'"
256
+ )
257
+
258
+ # Default context: language default context (server-side behavior).
259
+ # When context.id is omitted, execd will create/reuse a default session per language.
260
+ if context is None:
261
+ context = CodeContext(language=language or SupportedLanguage.PYTHON)
189
262
  api_request = CodeExecutionConverter.to_api_run_code_request(code, context)
190
263
 
191
264
  # Prepare URL
@@ -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:
@@ -114,7 +114,7 @@ class CodesAdapterSync(CodesSync):
114
114
  from opensandbox.api.execd.models.code_context_request import (
115
115
  CodeContextRequest,
116
116
  )
117
- from opensandbox.api.execd.types import UNSET
117
+ from opensandbox.api.execd.types import Unset
118
118
 
119
119
  response_obj = create_code_context.sync_detailed(
120
120
  client=self._client,
@@ -122,16 +122,86 @@ class CodesAdapterSync(CodesSync):
122
122
  )
123
123
  handle_api_error(response_obj, "Create code context")
124
124
  parsed = require_parsed(response_obj, ApiCodeContext, "Create code context")
125
- context_id = parsed.id if parsed.id is not UNSET else None
125
+ context_id = None if isinstance(parsed.id, Unset) else parsed.id
126
126
  return CodeContextSync(id=context_id, language=parsed.language)
127
127
  except Exception as e:
128
128
  logger.error("Failed to create context", exc_info=e)
129
129
  raise ExceptionConverter.to_sandbox_exception(e) from e
130
130
 
131
+ def get_context(self, context_id: str) -> CodeContextSync:
132
+ try:
133
+ from opensandbox.api.execd.api.code_interpreting import get_context
134
+ from opensandbox.api.execd.models.code_context import (
135
+ CodeContext as ApiCodeContext,
136
+ )
137
+ from opensandbox.api.execd.types import Unset
138
+
139
+ response_obj = get_context.sync_detailed(
140
+ client=self._client,
141
+ context_id=context_id,
142
+ )
143
+ handle_api_error(response_obj, "Get code context")
144
+ parsed = require_parsed(response_obj, ApiCodeContext, "Get code context")
145
+ context_id_val = None if isinstance(parsed.id, Unset) else parsed.id
146
+ return CodeContextSync(id=context_id_val, language=parsed.language)
147
+ except Exception as e:
148
+ logger.error("Failed to get context", exc_info=e)
149
+ raise ExceptionConverter.to_sandbox_exception(e) from e
150
+
151
+ def list_contexts(self, language: str) -> list[CodeContextSync]:
152
+ try:
153
+ from opensandbox.api.execd.api.code_interpreting import list_contexts
154
+ from opensandbox.api.execd.types import UNSET
155
+
156
+ response_obj = list_contexts.sync_detailed(
157
+ client=self._client,
158
+ language=language,
159
+ )
160
+ handle_api_error(response_obj, "List code contexts")
161
+ parsed_list = require_parsed(response_obj, list, "List code contexts")
162
+ result: list[CodeContextSync] = []
163
+ for c in parsed_list:
164
+ # c is an API CodeContext model
165
+ context_id_val = c.id if c.id is not UNSET else None
166
+ result.append(CodeContextSync(id=context_id_val, language=c.language))
167
+ return result
168
+ except Exception as e:
169
+ logger.error("Failed to list contexts", exc_info=e)
170
+ raise ExceptionConverter.to_sandbox_exception(e) from e
171
+
172
+ def delete_context(self, context_id: str) -> None:
173
+ try:
174
+ from opensandbox.api.execd.api.code_interpreting import delete_context
175
+
176
+ response_obj = delete_context.sync_detailed(
177
+ client=self._client,
178
+ context_id=context_id,
179
+ )
180
+ handle_api_error(response_obj, "Delete code context")
181
+ except Exception as e:
182
+ logger.error("Failed to delete context", exc_info=e)
183
+ raise ExceptionConverter.to_sandbox_exception(e) from e
184
+
185
+ def delete_contexts(self, language: str) -> None:
186
+ try:
187
+ from opensandbox.api.execd.api.code_interpreting import (
188
+ delete_contexts_by_language,
189
+ )
190
+
191
+ response_obj = delete_contexts_by_language.sync_detailed(
192
+ client=self._client,
193
+ language=language,
194
+ )
195
+ handle_api_error(response_obj, "Delete code contexts by language")
196
+ except Exception as e:
197
+ logger.error("Failed to delete contexts", exc_info=e)
198
+ raise ExceptionConverter.to_sandbox_exception(e) from e
199
+
131
200
  def run(
132
201
  self,
133
202
  code: str,
134
203
  *,
204
+ language: str | None = None,
135
205
  context: CodeContextSync | None = None,
136
206
  handlers: ExecutionHandlersSync | None = None,
137
207
  ) -> Execution:
@@ -155,7 +225,15 @@ class CodesAdapterSync(CodesSync):
155
225
  raise InvalidArgumentException("Code cannot be empty")
156
226
 
157
227
  try:
158
- context = context or CodeContextSync(language=SupportedLanguageSync.PYTHON)
228
+ if context is not None and language is not None and context.language != language:
229
+ raise InvalidArgumentException(
230
+ f"language '{language}' must match context.language '{context.language}'"
231
+ )
232
+
233
+ if context is None:
234
+ # Default context: language default context (server-side behavior).
235
+ # When context.id is omitted, execd will create/reuse a default session per language.
236
+ context = CodeContextSync(language=language or SupportedLanguageSync.PYTHON)
159
237
  api_request = {
160
238
  "code": code,
161
239
  "context": {
@@ -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: