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.
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/.gitignore +11 -0
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/PKG-INFO +50 -12
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/README.md +45 -7
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/pyproject.toml +15 -8
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/__init__.py +3 -3
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/adapters/code_adapter.py +96 -3
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/adapters/converter/code_execution_converter.py +2 -2
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/code_interpreter.py +4 -130
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/models/code.py +1 -0
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/services/code.py +67 -2
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/sync/adapters/code_adapter.py +129 -10
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/sync/code_interpreter.py +4 -112
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/sync/services/code.py +41 -2
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/LICENSE +0 -0
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/adapters/__init__.py +0 -0
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/adapters/converter/__init__.py +0 -0
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/adapters/factory.py +0 -0
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/models/__init__.py +0 -0
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/models/code_sync.py +0 -0
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/py.typed +0 -0
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/services/__init__.py +0 -0
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/sync/__init__.py +0 -0
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/sync/adapters/__init__.py +0 -0
- {opensandbox_code_interpreter-0.1.0 → opensandbox_code_interpreter-0.1.2}/src/code_interpreter/sync/adapters/factory.py +0 -0
- {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.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: OpenSandbox Code Interpreter Python SDK - Advanced code execution with persistent contexts
|
|
5
|
-
Project-URL: Homepage, https://
|
|
5
|
+
Project-URL: Homepage, https://open-sandbox.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/alibaba/OpenSandbox
|
|
7
|
-
Project-URL: Documentation, https://
|
|
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.
|
|
227
|
-
Requires-Dist: pydantic<3.0,>=2.
|
|
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
|
-
#
|
|
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.
|
|
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:
|
|
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
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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
|
-
#
|
|
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.
|
|
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:
|
|
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
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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.
|
|
46
|
-
"opensandbox>=0.1.
|
|
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://
|
|
50
|
+
Homepage = "https://open-sandbox.ai"
|
|
51
51
|
Repository = "https://github.com/alibaba/OpenSandbox"
|
|
52
|
-
Documentation = "https://
|
|
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 = "
|
|
108
|
+
typeCheckingMode = "standard"
|
|
109
109
|
pythonVersion = "3.10"
|
|
110
|
+
pythonPlatform = "All"
|
|
111
|
+
|
|
110
112
|
include = ["src"]
|
|
111
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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
|
|
85
|
+
from opensandbox.api.execd.types import Unset
|
|
86
86
|
|
|
87
|
-
context_id =
|
|
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
|
|
91
|
-
await
|
|
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) ->
|
|
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
|
-
|
|
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
|
"""
|
|
@@ -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
|
-
|
|
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__(
|
|
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 = {
|
|
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 = {
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
|
58
|
-
pause/resume/kill/renew/metrics
|
|
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) ->
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|