opensandbox-code-interpreter 0.1.2__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/.gitignore +20 -0
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/PKG-INFO +10 -11
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/README.md +5 -6
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/pyproject.toml +7 -7
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/__init__.py +1 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/adapters/__init__.py +1 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/adapters/code_adapter.py +39 -13
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/adapters/converter/__init__.py +1 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/adapters/converter/code_execution_converter.py +1 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/adapters/factory.py +1 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/code_interpreter.py +139 -6
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/models/__init__.py +1 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/models/code.py +1 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/models/code_sync.py +1 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/services/__init__.py +1 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/services/code.py +15 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/sync/__init__.py +1 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/sync/adapters/__init__.py +1 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/sync/adapters/code_adapter.py +28 -6
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/sync/adapters/factory.py +1 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/sync/code_interpreter.py +131 -6
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/sync/services/__init__.py +1 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/sync/services/code.py +15 -1
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/LICENSE +0 -0
- {opensandbox_code_interpreter-0.1.2 → opensandbox_code_interpreter-1.1.0}/src/code_interpreter/py.typed +0 -0
|
@@ -269,8 +269,28 @@ nbdist/
|
|
|
269
269
|
generated/
|
|
270
270
|
**/generated/**
|
|
271
271
|
|
|
272
|
+
# Fast Sandbox FastPath gRPC stubs are committed intentionally (deterministic protoc
|
|
273
|
+
# output; the server package needs them at install time, so they are checked
|
|
274
|
+
# in rather than regenerated).
|
|
275
|
+
!server/opensandbox_server/services/fast_sandbox/generated/
|
|
276
|
+
!server/opensandbox_server/services/fast_sandbox/generated/**
|
|
277
|
+
server/opensandbox_server/services/fast_sandbox/generated/__pycache__/
|
|
278
|
+
|
|
279
|
+
# execd eBPF kernel BTF dumps (bpf2go build-time only, never committed —
|
|
280
|
+
# see components/execd/pkg/ebpf/prog/audit.bpf.c; regenerate with:
|
|
281
|
+
# bpftool btf dump file /sys/kernel/btf/vmlinux format c > .../vmlinux.h)
|
|
282
|
+
components/execd/pkg/ebpf/prog/vmlinux.h
|
|
283
|
+
components/execd/pkg/ebpf/prog/vmlinux_*.h
|
|
284
|
+
|
|
272
285
|
# gVisor runtime binaries (downloaded dynamically)
|
|
273
286
|
kubernetes/test/kind/gvisor/runsc
|
|
274
287
|
kubernetes/test/kind/gvisor/containerd-shim-runsc-v1
|
|
275
288
|
bin/
|
|
276
289
|
obj/
|
|
290
|
+
|
|
291
|
+
.qoder/
|
|
292
|
+
/.vs
|
|
293
|
+
|
|
294
|
+
# fast-sandbox pinned source checkout (materialized by
|
|
295
|
+
# manifests/release/build-fast-sandbox.sh from manifests/third-party/fast-sandbox.commit)
|
|
296
|
+
.fast-sandbox/
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: opensandbox-code-interpreter
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: OpenSandbox Code Interpreter Python SDK - Advanced code execution with persistent contexts
|
|
5
5
|
Project-URL: Homepage, https://open-sandbox.ai
|
|
6
|
-
Project-URL: Repository, https://github.com/
|
|
6
|
+
Project-URL: Repository, https://github.com/opensandbox-group/OpenSandbox
|
|
7
7
|
Project-URL: Documentation, https://open-sandbox.ai
|
|
8
|
-
Project-URL: Issues, https://github.com/
|
|
8
|
+
Project-URL: Issues, https://github.com/opensandbox-group/OpenSandbox/issues
|
|
9
9
|
Author-email: OpenSandbox Team <ninan.nn@alibaba-inc.com>
|
|
10
10
|
License: Apache License
|
|
11
11
|
Version 2.0, January 2004
|
|
@@ -223,13 +223,12 @@ 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<
|
|
226
|
+
Requires-Dist: opensandbox<2.0.0,>=1.1.0
|
|
227
227
|
Requires-Dist: pydantic<3.0,>=2.4.2
|
|
228
228
|
Description-Content-Type: text/markdown
|
|
229
229
|
|
|
230
230
|
# OpenSandbox Code Interpreter SDK for Python
|
|
231
231
|
|
|
232
|
-
English | [中文](README_zh.md)
|
|
233
232
|
|
|
234
233
|
A Python SDK for executing code in secure, isolated sandboxes. It provides a high-level API for running Python, Java,
|
|
235
234
|
Go, TypeScript, and other languages safely, with support for code execution contexts.
|
|
@@ -241,7 +240,7 @@ This SDK requires a Docker image containing the Code Interpreter runtime environ
|
|
|
241
240
|
Node.js, etc.
|
|
242
241
|
|
|
243
242
|
For detailed information about supported languages and versions, refer to the
|
|
244
|
-
[Environment Documentation](
|
|
243
|
+
[Environment Documentation](https://github.com/opensandbox-group/sandbox-images).
|
|
245
244
|
|
|
246
245
|
## Installation
|
|
247
246
|
|
|
@@ -281,9 +280,9 @@ async def main() -> None:
|
|
|
281
280
|
|
|
282
281
|
# 2. Create a Sandbox with the code-interpreter image + runtime versions
|
|
283
282
|
sandbox = await Sandbox.create(
|
|
284
|
-
"opensandbox/code-interpreter:v1.0
|
|
283
|
+
"opensandbox/code-interpreter:v1.1.0",
|
|
285
284
|
connection_config=config,
|
|
286
|
-
entrypoint=["/opt/
|
|
285
|
+
entrypoint=["/opt/code-interpreter/code-interpreter.sh"],
|
|
287
286
|
env={
|
|
288
287
|
"PYTHON_VERSION": "3.11",
|
|
289
288
|
"JAVA_VERSION": "17",
|
|
@@ -342,9 +341,9 @@ config = ConnectionConfigSync(
|
|
|
342
341
|
)
|
|
343
342
|
|
|
344
343
|
sandbox = SandboxSync.create(
|
|
345
|
-
"opensandbox/code-interpreter:v1.0
|
|
344
|
+
"opensandbox/code-interpreter:v1.1.0",
|
|
346
345
|
connection_config=config,
|
|
347
|
-
entrypoint=["/opt/
|
|
346
|
+
entrypoint=["/opt/code-interpreter/code-interpreter.sh"],
|
|
348
347
|
env={"PYTHON_VERSION": "3.11"},
|
|
349
348
|
)
|
|
350
349
|
with sandbox:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# OpenSandbox Code Interpreter SDK for Python
|
|
2
2
|
|
|
3
|
-
English | [中文](README_zh.md)
|
|
4
3
|
|
|
5
4
|
A Python SDK for executing code in secure, isolated sandboxes. It provides a high-level API for running Python, Java,
|
|
6
5
|
Go, TypeScript, and other languages safely, with support for code execution contexts.
|
|
@@ -12,7 +11,7 @@ This SDK requires a Docker image containing the Code Interpreter runtime environ
|
|
|
12
11
|
Node.js, etc.
|
|
13
12
|
|
|
14
13
|
For detailed information about supported languages and versions, refer to the
|
|
15
|
-
[Environment Documentation](
|
|
14
|
+
[Environment Documentation](https://github.com/opensandbox-group/sandbox-images).
|
|
16
15
|
|
|
17
16
|
## Installation
|
|
18
17
|
|
|
@@ -52,9 +51,9 @@ async def main() -> None:
|
|
|
52
51
|
|
|
53
52
|
# 2. Create a Sandbox with the code-interpreter image + runtime versions
|
|
54
53
|
sandbox = await Sandbox.create(
|
|
55
|
-
"opensandbox/code-interpreter:v1.0
|
|
54
|
+
"opensandbox/code-interpreter:v1.1.0",
|
|
56
55
|
connection_config=config,
|
|
57
|
-
entrypoint=["/opt/
|
|
56
|
+
entrypoint=["/opt/code-interpreter/code-interpreter.sh"],
|
|
58
57
|
env={
|
|
59
58
|
"PYTHON_VERSION": "3.11",
|
|
60
59
|
"JAVA_VERSION": "17",
|
|
@@ -113,9 +112,9 @@ config = ConnectionConfigSync(
|
|
|
113
112
|
)
|
|
114
113
|
|
|
115
114
|
sandbox = SandboxSync.create(
|
|
116
|
-
"opensandbox/code-interpreter:v1.0
|
|
115
|
+
"opensandbox/code-interpreter:v1.1.0",
|
|
117
116
|
connection_config=config,
|
|
118
|
-
entrypoint=["/opt/
|
|
117
|
+
entrypoint=["/opt/code-interpreter/code-interpreter.sh"],
|
|
119
118
|
env={"PYTHON_VERSION": "3.11"},
|
|
120
119
|
)
|
|
121
120
|
with sandbox:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2025
|
|
1
|
+
# Copyright 2025 The OpenSandbox Authors
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -43,14 +43,14 @@ classifiers = [
|
|
|
43
43
|
]
|
|
44
44
|
dependencies = [
|
|
45
45
|
"pydantic>=2.4.2,<3.0",
|
|
46
|
-
"opensandbox>=
|
|
46
|
+
"opensandbox>=1.1.0,<2.0.0",
|
|
47
47
|
]
|
|
48
48
|
|
|
49
49
|
[project.urls]
|
|
50
50
|
Homepage = "https://open-sandbox.ai"
|
|
51
|
-
Repository = "https://github.com/
|
|
51
|
+
Repository = "https://github.com/opensandbox-group/OpenSandbox"
|
|
52
52
|
Documentation = "https://open-sandbox.ai"
|
|
53
|
-
Issues = "https://github.com/
|
|
53
|
+
Issues = "https://github.com/opensandbox-group/OpenSandbox/issues"
|
|
54
54
|
|
|
55
55
|
[tool.hatch.version]
|
|
56
56
|
source = "vcs"
|
|
@@ -58,9 +58,9 @@ source = "vcs"
|
|
|
58
58
|
[tool.hatch.version.raw-options]
|
|
59
59
|
# This package is in a subdirectory; explicitly point setuptools-scm at the git root.
|
|
60
60
|
root = "../../.."
|
|
61
|
-
tag_regex = "^
|
|
62
|
-
git_describe_command = 'git describe --dirty --tags --long --match "
|
|
63
|
-
fallback_version = "0.1.
|
|
61
|
+
tag_regex = "^release-(?P<version>\\d+\\.\\d+\\.\\d+(?:[\\.\\w\\+\\-]*)?)$"
|
|
62
|
+
git_describe_command = 'git describe --dirty --tags --long --match "release-*"'
|
|
63
|
+
fallback_version = "0.1.3"
|
|
64
64
|
|
|
65
65
|
[tool.hatch.build]
|
|
66
66
|
include = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright 2025
|
|
2
|
+
# Copyright 2025 The OpenSandbox Authors
|
|
3
3
|
#
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
|
@@ -41,6 +41,7 @@ from opensandbox.config import ConnectionConfig
|
|
|
41
41
|
from opensandbox.exceptions import InvalidArgumentException, SandboxApiException
|
|
42
42
|
from opensandbox.models.execd import Execution, ExecutionHandlers
|
|
43
43
|
from opensandbox.models.sandboxes import SandboxEndpoint
|
|
44
|
+
from opensandbox.transport import unwrap_retry_transport
|
|
44
45
|
|
|
45
46
|
from code_interpreter.adapters.converter.code_execution_converter import (
|
|
46
47
|
CodeExecutionConverter,
|
|
@@ -130,6 +131,9 @@ class CodesAdapter(Codes):
|
|
|
130
131
|
"Accept": "text/event-stream",
|
|
131
132
|
"Cache-Control": "no-cache",
|
|
132
133
|
}
|
|
134
|
+
# SSE bootstraps bypass the retry wrapper: request bodies are
|
|
135
|
+
# not replayable and a non-idempotent status opt-in would cause
|
|
136
|
+
# duplicate execution on a resent SSE POST.
|
|
133
137
|
self._sse_client = httpx.AsyncClient(
|
|
134
138
|
headers=sse_headers,
|
|
135
139
|
timeout=httpx.Timeout(
|
|
@@ -138,7 +142,7 @@ class CodesAdapter(Codes):
|
|
|
138
142
|
write=timeout_seconds,
|
|
139
143
|
pool=None,
|
|
140
144
|
),
|
|
141
|
-
transport=self.connection_config.transport,
|
|
145
|
+
transport=unwrap_retry_transport(self.connection_config.transport),
|
|
142
146
|
)
|
|
143
147
|
|
|
144
148
|
async def _get_client(self):
|
|
@@ -216,7 +220,9 @@ class CodesAdapter(Codes):
|
|
|
216
220
|
)
|
|
217
221
|
handle_api_error(response_obj, "List code contexts")
|
|
218
222
|
parsed_list = require_parsed(response_obj, list, "List code contexts")
|
|
219
|
-
return [
|
|
223
|
+
return [
|
|
224
|
+
CodeExecutionConverter.from_api_code_context(c) for c in parsed_list
|
|
225
|
+
]
|
|
220
226
|
except Exception as e:
|
|
221
227
|
logger.error("Failed to list contexts", exc_info=e)
|
|
222
228
|
raise ExceptionConverter.to_sandbox_exception(e) from e
|
|
@@ -269,7 +275,11 @@ class CodesAdapter(Codes):
|
|
|
269
275
|
raise InvalidArgumentException("Code cannot be empty")
|
|
270
276
|
|
|
271
277
|
try:
|
|
272
|
-
if
|
|
278
|
+
if (
|
|
279
|
+
context is not None
|
|
280
|
+
and language is not None
|
|
281
|
+
and context.language != language
|
|
282
|
+
):
|
|
273
283
|
raise InvalidArgumentException(
|
|
274
284
|
f"language '{language}' must match context.language '{context.language}'"
|
|
275
285
|
)
|
|
@@ -299,12 +309,10 @@ class CodesAdapter(Codes):
|
|
|
299
309
|
await response.aread()
|
|
300
310
|
error_body = response.text
|
|
301
311
|
logger.error(
|
|
302
|
-
"Failed to run code. Status:
|
|
303
|
-
response.status_code,
|
|
304
|
-
error_body,
|
|
312
|
+
f"Failed to run code. Status: {response.status_code}, Body: {error_body}"
|
|
305
313
|
)
|
|
306
314
|
raise SandboxApiException(
|
|
307
|
-
message=f"Failed to run code. Status code: {response.status_code}",
|
|
315
|
+
message=f"Failed to run code. Status code: {response.status_code}, Body: {error_body}",
|
|
308
316
|
status_code=response.status_code,
|
|
309
317
|
request_id=extract_request_id(response.headers),
|
|
310
318
|
)
|
|
@@ -325,18 +333,16 @@ class CodesAdapter(Codes):
|
|
|
325
333
|
event_node = EventNode(**event_dict)
|
|
326
334
|
await dispatcher.dispatch(event_node)
|
|
327
335
|
except json.JSONDecodeError:
|
|
328
|
-
logger.debug("Failed to parse SSE line:
|
|
336
|
+
logger.debug(f"Failed to parse SSE line: {line}")
|
|
329
337
|
continue
|
|
330
338
|
except Exception as e:
|
|
331
|
-
logger.error("Error processing event:
|
|
339
|
+
logger.error(f"Error processing event: {data}", exc_info=e)
|
|
332
340
|
continue
|
|
333
341
|
|
|
334
342
|
return execution
|
|
335
343
|
|
|
336
344
|
except Exception as e:
|
|
337
|
-
logger.error(
|
|
338
|
-
"Failed to run code (length: %s)", len(code), exc_info=e
|
|
339
|
-
)
|
|
345
|
+
logger.error(f"Failed to run code (length: {len(code)})", exc_info=e)
|
|
340
346
|
raise ExceptionConverter.to_sandbox_exception(e) from e
|
|
341
347
|
|
|
342
348
|
async def interrupt(self, execution_id: str) -> None:
|
|
@@ -359,3 +365,23 @@ class CodesAdapter(Codes):
|
|
|
359
365
|
except Exception as e:
|
|
360
366
|
logger.error("Failed to interrupt code execution", exc_info=e)
|
|
361
367
|
raise ExceptionConverter.to_sandbox_exception(e) from e
|
|
368
|
+
|
|
369
|
+
async def ping(self) -> bool:
|
|
370
|
+
"""
|
|
371
|
+
Check if the execd code execution service is alive.
|
|
372
|
+
|
|
373
|
+
Uses the shared generated API client so the ping exercises the same
|
|
374
|
+
HTTP client and endpoint that serve code execution requests.
|
|
375
|
+
"""
|
|
376
|
+
try:
|
|
377
|
+
from opensandbox.api.execd.api.health import ping as ping_api
|
|
378
|
+
|
|
379
|
+
client = await self._get_client()
|
|
380
|
+
response_obj = await ping_api.asyncio_detailed(client=client)
|
|
381
|
+
|
|
382
|
+
handle_api_error(response_obj, "Ping code interpreter")
|
|
383
|
+
return True
|
|
384
|
+
|
|
385
|
+
except Exception as e:
|
|
386
|
+
logger.debug(f"Code interpreter ping failed: {e}")
|
|
387
|
+
return False
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright 2025
|
|
2
|
+
# Copyright 2025 The OpenSandbox Authors
|
|
3
3
|
#
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
|
@@ -21,12 +21,16 @@ functionality with specialized code execution features, including multi-language
|
|
|
21
21
|
support, session management, and variable persistence.
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
+
import asyncio
|
|
24
25
|
import logging
|
|
26
|
+
import time
|
|
27
|
+
from datetime import timedelta
|
|
25
28
|
|
|
26
29
|
from opensandbox.exceptions import (
|
|
27
30
|
InvalidArgumentException,
|
|
28
31
|
SandboxException,
|
|
29
32
|
SandboxInternalException,
|
|
33
|
+
SandboxReadyTimeoutException,
|
|
30
34
|
)
|
|
31
35
|
from opensandbox.sandbox import Sandbox
|
|
32
36
|
|
|
@@ -35,6 +39,21 @@ from code_interpreter.services.code import Codes
|
|
|
35
39
|
|
|
36
40
|
logger = logging.getLogger(__name__)
|
|
37
41
|
|
|
42
|
+
DEFAULT_READY_TIMEOUT = timedelta(seconds=30)
|
|
43
|
+
DEFAULT_HEALTH_CHECK_POLLING_INTERVAL = timedelta(milliseconds=200)
|
|
44
|
+
|
|
45
|
+
# Strict health check script: verifies the code interpreter runtime (Jupyter
|
|
46
|
+
# kernel gateway) is actually serving inside the sandbox. execd starts serving
|
|
47
|
+
# /ping before the entrypoint launches Jupyter, and the setup stage may run
|
|
48
|
+
# short-lived "jupyter kernelspec" helpers, so a daemon ping or a process-name
|
|
49
|
+
# grep cannot prove the runtime is ready. Probing the Jupyter listen port
|
|
50
|
+
# (127.0.0.1:${JUPYTER_PORT:-44771}, same default as the entrypoint) only
|
|
51
|
+
# passes once the server accepts connections.
|
|
52
|
+
RUNTIME_PROCESS_CHECK_COMMAND = (
|
|
53
|
+
"bash -c 'exec 3<>/dev/tcp/127.0.0.1/${JUPYTER_PORT:-44771}' "
|
|
54
|
+
"&& exit 0 || exit 1"
|
|
55
|
+
)
|
|
56
|
+
|
|
38
57
|
|
|
39
58
|
class CodeInterpreter:
|
|
40
59
|
"""
|
|
@@ -169,14 +188,114 @@ class CodeInterpreter:
|
|
|
169
188
|
"""
|
|
170
189
|
return self._code_service
|
|
171
190
|
|
|
191
|
+
async def ping(self) -> bool:
|
|
192
|
+
"""
|
|
193
|
+
Check if the code execution service (execd) is responsive.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
True if the code execution service is responsive, False otherwise
|
|
197
|
+
"""
|
|
198
|
+
return await self._code_service.ping()
|
|
199
|
+
|
|
200
|
+
async def is_healthy(self) -> bool:
|
|
201
|
+
"""
|
|
202
|
+
Check if the code interpreter is healthy (strict check).
|
|
203
|
+
|
|
204
|
+
Healthy means both:
|
|
205
|
+
|
|
206
|
+
- the code execution service (execd) answers ``GET /ping``; and
|
|
207
|
+
- the code interpreter runtime (Jupyter kernel gateway) is serving
|
|
208
|
+
inside the sandbox, verified by probing its listen port through the
|
|
209
|
+
execd command API.
|
|
210
|
+
|
|
211
|
+
Exceptions raised by either leg are treated as unhealthy.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
True if healthy, False otherwise
|
|
215
|
+
"""
|
|
216
|
+
try:
|
|
217
|
+
return await self.ping() and await self._is_runtime_process_alive()
|
|
218
|
+
except Exception:
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
async def _is_runtime_process_alive(self) -> bool:
|
|
222
|
+
"""
|
|
223
|
+
Check if the code interpreter runtime (Jupyter) is serving.
|
|
224
|
+
|
|
225
|
+
Probes the runtime listen port through the sandbox command service and
|
|
226
|
+
treats a failed command (non-zero exit surfaced as an execution error)
|
|
227
|
+
as an unhealthy runtime.
|
|
228
|
+
"""
|
|
229
|
+
try:
|
|
230
|
+
execution = await self._sandbox.commands.run(
|
|
231
|
+
RUNTIME_PROCESS_CHECK_COMMAND
|
|
232
|
+
)
|
|
233
|
+
return execution.error is None
|
|
234
|
+
except Exception:
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
async def check_ready(
|
|
238
|
+
self,
|
|
239
|
+
timeout: timedelta,
|
|
240
|
+
polling_interval: timedelta,
|
|
241
|
+
) -> None:
|
|
242
|
+
"""
|
|
243
|
+
Wait for the code interpreter to pass the strict health check with polling.
|
|
244
|
+
|
|
245
|
+
Raises:
|
|
246
|
+
SandboxReadyTimeoutException: if the health check doesn't pass within timeout
|
|
247
|
+
"""
|
|
248
|
+
logger.info(
|
|
249
|
+
f"Waiting for code interpreter {self.id} to pass health check "
|
|
250
|
+
f"(timeout: {timeout.total_seconds()}s)"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
deadline = time.monotonic() + timeout.total_seconds()
|
|
254
|
+
attempt = 0
|
|
255
|
+
|
|
256
|
+
while time.monotonic() < deadline:
|
|
257
|
+
attempt += 1
|
|
258
|
+
if await self.is_healthy():
|
|
259
|
+
logger.info(
|
|
260
|
+
f"Code interpreter {self.id} passed health check "
|
|
261
|
+
f"after {attempt} attempts"
|
|
262
|
+
)
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
remaining = deadline - time.monotonic()
|
|
266
|
+
if remaining <= 0:
|
|
267
|
+
break
|
|
268
|
+
await asyncio.sleep(min(polling_interval.total_seconds(), remaining))
|
|
269
|
+
|
|
270
|
+
raise SandboxReadyTimeoutException(
|
|
271
|
+
f"Code interpreter {self.id} health check timed out after "
|
|
272
|
+
f"{timeout.total_seconds()}s ({attempt} attempts). The code execution "
|
|
273
|
+
f"service (execd) or the interpreter runtime (Jupyter) did not become "
|
|
274
|
+
f"ready. Pass skip_health_check=True to skip this check."
|
|
275
|
+
)
|
|
276
|
+
|
|
172
277
|
@classmethod
|
|
173
|
-
async def create(
|
|
278
|
+
async def create(
|
|
279
|
+
cls,
|
|
280
|
+
sandbox: Sandbox,
|
|
281
|
+
*,
|
|
282
|
+
ready_timeout: timedelta = DEFAULT_READY_TIMEOUT,
|
|
283
|
+
health_check_polling_interval: timedelta = DEFAULT_HEALTH_CHECK_POLLING_INTERVAL,
|
|
284
|
+
skip_health_check: bool = False,
|
|
285
|
+
) -> "CodeInterpreter":
|
|
174
286
|
"""
|
|
175
287
|
Creates a CodeInterpreter from an existing Sandbox instance.
|
|
176
288
|
|
|
177
289
|
This factory method handles the creation and initialization of CodeInterpreter
|
|
178
290
|
services, including the code execution service and language configuration.
|
|
179
291
|
|
|
292
|
+
By default a strict health check runs before the interpreter is returned:
|
|
293
|
+
the code execution service (execd) must answer ``GET /ping`` AND the
|
|
294
|
+
code interpreter runtime (Jupyter kernel gateway) must be serving,
|
|
295
|
+
both within ``ready_timeout``. execd starts serving before the runtime
|
|
296
|
+
launches, so the daemon ping alone is not enough. Set
|
|
297
|
+
``skip_health_check=True`` to opt out.
|
|
298
|
+
|
|
180
299
|
CodeInterpreter must be created by wrapping an existing Sandbox instance with
|
|
181
300
|
code execution capabilities. This design ensures clear separation of concerns:
|
|
182
301
|
- Sandbox handles infrastructure (containers, resources, networking)
|
|
@@ -184,6 +303,10 @@ class CodeInterpreter:
|
|
|
184
303
|
|
|
185
304
|
Args:
|
|
186
305
|
sandbox: Existing sandbox instance to wrap with code execution capabilities
|
|
306
|
+
ready_timeout: Maximum time to wait for the code execution service health check
|
|
307
|
+
health_check_polling_interval: Time between health check attempts
|
|
308
|
+
skip_health_check: If True, do not wait for the code execution service
|
|
309
|
+
to become ready; the returned interpreter may fail on first use
|
|
187
310
|
|
|
188
311
|
Returns:
|
|
189
312
|
CodeInterpreter instance wrapping the sandbox
|
|
@@ -191,24 +314,34 @@ class CodeInterpreter:
|
|
|
191
314
|
Raises:
|
|
192
315
|
InvalidArgumentException: If sandbox is not provided
|
|
193
316
|
SandboxException: If creation fails
|
|
317
|
+
SandboxReadyTimeoutException: If the code execution service health check
|
|
318
|
+
times out and ``skip_health_check`` is False
|
|
194
319
|
SandboxInternalException: If internal service initialization fails
|
|
195
320
|
"""
|
|
196
321
|
if sandbox is None:
|
|
197
322
|
raise InvalidArgumentException("Sandbox instance must be provided")
|
|
198
323
|
|
|
199
|
-
logger.info("Creating code interpreter from sandbox:
|
|
324
|
+
logger.info(f"Creating code interpreter from sandbox: {sandbox.id}")
|
|
200
325
|
|
|
201
326
|
factory = AdapterFactory(sandbox.connection_config)
|
|
202
327
|
|
|
203
328
|
try:
|
|
204
329
|
# Connect to the execd daemon endpoint for code execution services
|
|
205
330
|
from opensandbox.constants import DEFAULT_EXECD_PORT
|
|
331
|
+
|
|
206
332
|
code_interpreter_endpoint = await sandbox.get_endpoint(DEFAULT_EXECD_PORT)
|
|
207
|
-
code_execution_service = factory.create_code_execution_service(
|
|
333
|
+
code_execution_service = factory.create_code_execution_service(
|
|
334
|
+
code_interpreter_endpoint
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
interpreter = cls(sandbox, code_execution_service)
|
|
338
|
+
|
|
339
|
+
if not skip_health_check:
|
|
340
|
+
await interpreter.check_ready(ready_timeout, health_check_polling_interval)
|
|
208
341
|
|
|
209
|
-
logger.info("Code interpreter
|
|
342
|
+
logger.info(f"Code interpreter {sandbox.id} created successfully")
|
|
210
343
|
|
|
211
|
-
return
|
|
344
|
+
return interpreter
|
|
212
345
|
except Exception as e:
|
|
213
346
|
if isinstance(e, SandboxException):
|
|
214
347
|
raise
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright 2025
|
|
2
|
+
# Copyright 2025 The OpenSandbox Authors
|
|
3
3
|
#
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
|
@@ -212,3 +212,17 @@ class Codes(Protocol):
|
|
|
212
212
|
SandboxException: If interruption fails
|
|
213
213
|
"""
|
|
214
214
|
...
|
|
215
|
+
|
|
216
|
+
async def ping(self) -> bool:
|
|
217
|
+
"""
|
|
218
|
+
Check if the code execution service (execd) is alive.
|
|
219
|
+
|
|
220
|
+
The ping targets the execd daemon endpoint used by this service
|
|
221
|
+
(``GET /ping``). It does not verify that a specific language runtime
|
|
222
|
+
is ready, only that the daemon serving code execution requests is
|
|
223
|
+
responsive.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
True if the code execution service is responsive, False otherwise
|
|
227
|
+
"""
|
|
228
|
+
...
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright 2025
|
|
2
|
+
# Copyright 2025 The OpenSandbox Authors
|
|
3
3
|
#
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
|
@@ -39,6 +39,7 @@ from opensandbox.models.sandboxes import SandboxEndpoint
|
|
|
39
39
|
from opensandbox.sync.adapters.converter.execution_event_dispatcher import (
|
|
40
40
|
ExecutionEventDispatcherSync,
|
|
41
41
|
)
|
|
42
|
+
from opensandbox.transport import unwrap_retry_transport
|
|
42
43
|
|
|
43
44
|
from code_interpreter.models.code_sync import CodeContextSync, SupportedLanguageSync
|
|
44
45
|
from code_interpreter.sync.services.code import CodesSync
|
|
@@ -118,12 +119,15 @@ class CodesAdapterSync(CodesSync):
|
|
|
118
119
|
"Accept": "text/event-stream",
|
|
119
120
|
"Cache-Control": "no-cache",
|
|
120
121
|
}
|
|
122
|
+
# SSE bootstraps bypass the retry wrapper: request bodies are
|
|
123
|
+
# not replayable and a non-idempotent status opt-in would cause
|
|
124
|
+
# duplicate execution on a resent SSE POST.
|
|
121
125
|
self._sse_client = httpx.Client(
|
|
122
126
|
headers=sse_headers,
|
|
123
127
|
timeout=httpx.Timeout(
|
|
124
128
|
connect=timeout_seconds, read=None, write=timeout_seconds, pool=None
|
|
125
129
|
),
|
|
126
|
-
transport=self.connection_config.transport,
|
|
130
|
+
transport=unwrap_retry_transport(self.connection_config.transport),
|
|
127
131
|
)
|
|
128
132
|
|
|
129
133
|
def _get_execd_url(self, path: str) -> str:
|
|
@@ -287,8 +291,9 @@ class CodesAdapterSync(CodesSync):
|
|
|
287
291
|
with self._sse_client.stream("POST", url, json=api_request) as response:
|
|
288
292
|
if response.status_code != 200:
|
|
289
293
|
response.read()
|
|
294
|
+
error_body = response.text
|
|
290
295
|
raise SandboxApiException(
|
|
291
|
-
message=f"Failed to run code. Status code: {response.status_code}",
|
|
296
|
+
message=f"Failed to run code. Status code: {response.status_code}, Body: {error_body}",
|
|
292
297
|
status_code=response.status_code,
|
|
293
298
|
request_id=extract_request_id(response.headers),
|
|
294
299
|
)
|
|
@@ -304,15 +309,15 @@ class CodesAdapterSync(CodesSync):
|
|
|
304
309
|
event_node = EventNode(**event_dict)
|
|
305
310
|
dispatcher.dispatch(event_node)
|
|
306
311
|
except json.JSONDecodeError:
|
|
307
|
-
logger.debug("Failed to parse SSE line:
|
|
312
|
+
logger.debug(f"Failed to parse SSE line: {line}")
|
|
308
313
|
continue
|
|
309
314
|
except Exception as e:
|
|
310
|
-
logger.error("Error processing event:
|
|
315
|
+
logger.error(f"Error processing event: {data}", exc_info=e)
|
|
311
316
|
continue
|
|
312
317
|
|
|
313
318
|
return execution
|
|
314
319
|
except Exception as e:
|
|
315
|
-
logger.error("Failed to run code (length:
|
|
320
|
+
logger.error(f"Failed to run code (length: {len(code)})", exc_info=e)
|
|
316
321
|
raise ExceptionConverter.to_sandbox_exception(e) from e
|
|
317
322
|
|
|
318
323
|
def interrupt(self, execution_id: str) -> None:
|
|
@@ -332,3 +337,20 @@ class CodesAdapterSync(CodesSync):
|
|
|
332
337
|
except Exception as e:
|
|
333
338
|
logger.error("Failed to interrupt code execution", exc_info=e)
|
|
334
339
|
raise ExceptionConverter.to_sandbox_exception(e) from e
|
|
340
|
+
|
|
341
|
+
def ping(self) -> bool:
|
|
342
|
+
"""
|
|
343
|
+
Check if the execd code execution service is alive (blocking).
|
|
344
|
+
|
|
345
|
+
Uses the shared generated API client so the ping exercises the same
|
|
346
|
+
HTTP client and endpoint that serve code execution requests.
|
|
347
|
+
"""
|
|
348
|
+
try:
|
|
349
|
+
from opensandbox.api.execd.api.health import ping as ping_api
|
|
350
|
+
|
|
351
|
+
response_obj = ping_api.sync_detailed(client=self._client)
|
|
352
|
+
handle_api_error(response_obj, "Ping code interpreter")
|
|
353
|
+
return True
|
|
354
|
+
except Exception as e:
|
|
355
|
+
logger.debug(f"Code interpreter ping failed: {e}")
|
|
356
|
+
return False
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright 2025
|
|
2
|
+
# Copyright 2025 The OpenSandbox Authors
|
|
3
3
|
#
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
|
@@ -18,12 +18,15 @@ Synchronous Code Interpreter SDK.
|
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
|
+
import time
|
|
22
|
+
from datetime import timedelta
|
|
21
23
|
|
|
22
24
|
from opensandbox.constants import DEFAULT_EXECD_PORT
|
|
23
25
|
from opensandbox.exceptions import (
|
|
24
26
|
InvalidArgumentException,
|
|
25
27
|
SandboxException,
|
|
26
28
|
SandboxInternalException,
|
|
29
|
+
SandboxReadyTimeoutException,
|
|
27
30
|
)
|
|
28
31
|
from opensandbox.sync.sandbox import SandboxSync
|
|
29
32
|
|
|
@@ -32,6 +35,21 @@ from code_interpreter.sync.services.code import CodesSync
|
|
|
32
35
|
|
|
33
36
|
logger = logging.getLogger(__name__)
|
|
34
37
|
|
|
38
|
+
DEFAULT_READY_TIMEOUT = timedelta(seconds=30)
|
|
39
|
+
DEFAULT_HEALTH_CHECK_POLLING_INTERVAL = timedelta(milliseconds=200)
|
|
40
|
+
|
|
41
|
+
# Strict health check script: verifies the code interpreter runtime (Jupyter
|
|
42
|
+
# kernel gateway) is actually serving inside the sandbox. execd starts serving
|
|
43
|
+
# /ping before the entrypoint launches Jupyter, and the setup stage may run
|
|
44
|
+
# short-lived "jupyter kernelspec" helpers, so a daemon ping or a process-name
|
|
45
|
+
# grep cannot prove the runtime is ready. Probing the Jupyter listen port
|
|
46
|
+
# (127.0.0.1:${JUPYTER_PORT:-44771}, same default as the entrypoint) only
|
|
47
|
+
# passes once the server accepts connections.
|
|
48
|
+
RUNTIME_PROCESS_CHECK_COMMAND = (
|
|
49
|
+
"bash -c 'exec 3<>/dev/tcp/127.0.0.1/${JUPYTER_PORT:-44771}' "
|
|
50
|
+
"&& exit 0 || exit 1"
|
|
51
|
+
)
|
|
52
|
+
|
|
35
53
|
|
|
36
54
|
class CodeInterpreterSync:
|
|
37
55
|
"""
|
|
@@ -146,13 +164,110 @@ class CodeInterpreterSync:
|
|
|
146
164
|
"""
|
|
147
165
|
return self._code_service
|
|
148
166
|
|
|
167
|
+
def ping(self) -> bool:
|
|
168
|
+
"""
|
|
169
|
+
Check if the code execution service (execd) is responsive.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
True if the code execution service is responsive, False otherwise
|
|
173
|
+
"""
|
|
174
|
+
return self._code_service.ping()
|
|
175
|
+
|
|
176
|
+
def is_healthy(self) -> bool:
|
|
177
|
+
"""
|
|
178
|
+
Check if the code interpreter is healthy (strict check).
|
|
179
|
+
|
|
180
|
+
Healthy means both:
|
|
181
|
+
|
|
182
|
+
- the code execution service (execd) answers ``GET /ping``; and
|
|
183
|
+
- the code interpreter runtime (Jupyter kernel gateway) is serving
|
|
184
|
+
inside the sandbox, verified by probing its listen port through the
|
|
185
|
+
execd command API.
|
|
186
|
+
|
|
187
|
+
Exceptions raised by either leg are treated as unhealthy.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
True if healthy, False otherwise
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
return self.ping() and self._is_runtime_process_alive()
|
|
194
|
+
except Exception:
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
def _is_runtime_process_alive(self) -> bool:
|
|
198
|
+
"""
|
|
199
|
+
Check if the code interpreter runtime (Jupyter) is serving.
|
|
200
|
+
"""
|
|
201
|
+
try:
|
|
202
|
+
execution = self._sandbox.commands.run(RUNTIME_PROCESS_CHECK_COMMAND)
|
|
203
|
+
return execution.error is None
|
|
204
|
+
except Exception:
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
def check_ready(
|
|
208
|
+
self,
|
|
209
|
+
timeout: timedelta,
|
|
210
|
+
polling_interval: timedelta,
|
|
211
|
+
) -> None:
|
|
212
|
+
"""
|
|
213
|
+
Wait for the code interpreter to pass the strict health check with polling (blocking).
|
|
214
|
+
|
|
215
|
+
Raises:
|
|
216
|
+
SandboxReadyTimeoutException: if the health check doesn't pass within timeout
|
|
217
|
+
"""
|
|
218
|
+
logger.info(
|
|
219
|
+
f"Waiting for code interpreter {self.id} to pass health check "
|
|
220
|
+
f"(timeout: {timeout.total_seconds()}s)"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
deadline = time.monotonic() + timeout.total_seconds()
|
|
224
|
+
attempt = 0
|
|
225
|
+
|
|
226
|
+
while time.monotonic() < deadline:
|
|
227
|
+
attempt += 1
|
|
228
|
+
if self.is_healthy():
|
|
229
|
+
logger.info(
|
|
230
|
+
f"Code interpreter {self.id} passed health check "
|
|
231
|
+
f"after {attempt} attempts"
|
|
232
|
+
)
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
remaining = deadline - time.monotonic()
|
|
236
|
+
if remaining <= 0:
|
|
237
|
+
break
|
|
238
|
+
time.sleep(min(polling_interval.total_seconds(), remaining))
|
|
239
|
+
|
|
240
|
+
raise SandboxReadyTimeoutException(
|
|
241
|
+
f"Code interpreter {self.id} health check timed out after "
|
|
242
|
+
f"{timeout.total_seconds()}s ({attempt} attempts). The code execution "
|
|
243
|
+
f"service (execd) or the interpreter runtime (Jupyter) did not become "
|
|
244
|
+
f"ready. Pass skip_health_check=True to skip this check."
|
|
245
|
+
)
|
|
246
|
+
|
|
149
247
|
@classmethod
|
|
150
|
-
def create(
|
|
248
|
+
def create(
|
|
249
|
+
cls,
|
|
250
|
+
sandbox: SandboxSync,
|
|
251
|
+
*,
|
|
252
|
+
ready_timeout: timedelta = DEFAULT_READY_TIMEOUT,
|
|
253
|
+
health_check_polling_interval: timedelta = DEFAULT_HEALTH_CHECK_POLLING_INTERVAL,
|
|
254
|
+
skip_health_check: bool = False,
|
|
255
|
+
) -> "CodeInterpreterSync":
|
|
151
256
|
"""
|
|
152
257
|
Create a CodeInterpreterSync from an existing SandboxSync instance (blocking).
|
|
153
258
|
|
|
259
|
+
By default a strict health check runs before the interpreter is returned:
|
|
260
|
+
the code execution service (execd) must answer ``GET /ping`` AND the
|
|
261
|
+
code interpreter runtime process (Jupyter kernel gateway) must be
|
|
262
|
+
running inside the sandbox, both within ``ready_timeout``. Set
|
|
263
|
+
``skip_health_check=True`` to opt out.
|
|
264
|
+
|
|
154
265
|
Args:
|
|
155
266
|
sandbox: Existing sandbox instance to wrap with code execution capabilities
|
|
267
|
+
ready_timeout: Maximum time to wait for the code execution service health check
|
|
268
|
+
health_check_polling_interval: Time between health check attempts
|
|
269
|
+
skip_health_check: If True, do not wait for the code execution service
|
|
270
|
+
to become ready; the returned interpreter may fail on first use
|
|
156
271
|
|
|
157
272
|
Returns:
|
|
158
273
|
CodeInterpreterSync instance wrapping the sandbox
|
|
@@ -160,19 +275,29 @@ class CodeInterpreterSync:
|
|
|
160
275
|
Raises:
|
|
161
276
|
InvalidArgumentException: If sandbox is not provided
|
|
162
277
|
SandboxException: If creation fails
|
|
278
|
+
SandboxReadyTimeoutException: If the code execution service health check
|
|
279
|
+
times out and ``skip_health_check`` is False
|
|
163
280
|
SandboxInternalException: If internal service initialization fails
|
|
164
281
|
"""
|
|
165
282
|
if sandbox is None:
|
|
166
283
|
raise InvalidArgumentException("Sandbox instance must be provided")
|
|
167
284
|
|
|
168
|
-
logger.info("Creating code interpreter from sandbox:
|
|
285
|
+
logger.info(f"Creating code interpreter from sandbox: {sandbox.id}")
|
|
169
286
|
factory = AdapterFactorySync(sandbox.connection_config)
|
|
170
287
|
try:
|
|
171
288
|
endpoint = sandbox.get_endpoint(DEFAULT_EXECD_PORT)
|
|
172
289
|
code_service = factory.create_code_execution_service(endpoint)
|
|
173
|
-
|
|
174
|
-
|
|
290
|
+
|
|
291
|
+
interpreter = cls(sandbox, code_service)
|
|
292
|
+
|
|
293
|
+
if not skip_health_check:
|
|
294
|
+
interpreter.check_ready(ready_timeout, health_check_polling_interval)
|
|
295
|
+
|
|
296
|
+
logger.info(f"Code interpreter {sandbox.id} created successfully")
|
|
297
|
+
return interpreter
|
|
175
298
|
except Exception as e:
|
|
176
299
|
if isinstance(e, SandboxException):
|
|
177
300
|
raise
|
|
178
|
-
raise SandboxInternalException(
|
|
301
|
+
raise SandboxInternalException(
|
|
302
|
+
f"Failed to create code interpreter: {e}", cause=e
|
|
303
|
+
) from e
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright 2025
|
|
2
|
+
# Copyright 2025 The OpenSandbox Authors
|
|
3
3
|
#
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
# you may not use this file except in compliance with the License.
|
|
@@ -159,3 +159,17 @@ class CodesSync(Protocol):
|
|
|
159
159
|
SandboxException: If interruption fails.
|
|
160
160
|
"""
|
|
161
161
|
...
|
|
162
|
+
|
|
163
|
+
def ping(self) -> bool:
|
|
164
|
+
"""
|
|
165
|
+
Check if the code execution service (execd) is alive (blocking).
|
|
166
|
+
|
|
167
|
+
The ping targets the execd daemon endpoint used by this service
|
|
168
|
+
(``GET /ping``). It does not verify that a specific language runtime
|
|
169
|
+
is ready, only that the daemon serving code execution requests is
|
|
170
|
+
responsive.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
True if the code execution service is responsive, False otherwise.
|
|
174
|
+
"""
|
|
175
|
+
...
|
|
File without changes
|
|
File without changes
|