scalebox-sdk 0.1.25__py3-none-any.whl → 1.0.2__py3-none-any.whl

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 (78) hide show
  1. scalebox/__init__.py +2 -2
  2. scalebox/api/__init__.py +3 -1
  3. scalebox/api/client/api/sandboxes/get_sandboxes.py +1 -1
  4. scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
  5. scalebox/api/client/models/connect_sandbox.py +59 -0
  6. scalebox/api/client/models/error.py +2 -2
  7. scalebox/api/client/models/listed_sandbox.py +24 -3
  8. scalebox/api/client/models/new_sandbox.py +10 -0
  9. scalebox/api/client/models/sandbox.py +13 -0
  10. scalebox/api/client/models/sandbox_detail.py +24 -0
  11. scalebox/cli.py +125 -125
  12. scalebox/client/aclient.py +57 -57
  13. scalebox/client/client.py +102 -102
  14. scalebox/code_interpreter/__init__.py +12 -12
  15. scalebox/code_interpreter/charts.py +230 -230
  16. scalebox/code_interpreter/code_interpreter_async.py +3 -1
  17. scalebox/code_interpreter/code_interpreter_sync.py +3 -1
  18. scalebox/code_interpreter/constants.py +3 -3
  19. scalebox/code_interpreter/exceptions.py +13 -13
  20. scalebox/code_interpreter/models.py +485 -485
  21. scalebox/connection_config.py +36 -1
  22. scalebox/csx_connect/__init__.py +1 -1
  23. scalebox/csx_connect/client.py +485 -485
  24. scalebox/csx_desktop/main.py +651 -651
  25. scalebox/exceptions.py +83 -83
  26. scalebox/generated/api.py +61 -61
  27. scalebox/generated/api_pb2.py +203 -203
  28. scalebox/generated/api_pb2.pyi +956 -956
  29. scalebox/generated/api_pb2_connect.py +1407 -1407
  30. scalebox/generated/rpc.py +50 -50
  31. scalebox/sandbox/main.py +146 -139
  32. scalebox/sandbox/sandbox_api.py +105 -91
  33. scalebox/sandbox/signature.py +40 -40
  34. scalebox/sandbox/utils.py +34 -34
  35. scalebox/sandbox_async/main.py +226 -44
  36. scalebox/sandbox_async/sandbox_api.py +124 -3
  37. scalebox/sandbox_sync/main.py +205 -130
  38. scalebox/sandbox_sync/sandbox_api.py +119 -3
  39. scalebox/test/CODE_INTERPRETER_TESTS_READY.md +323 -323
  40. scalebox/test/README.md +329 -329
  41. scalebox/test/bedrock_openai_adapter.py +73 -0
  42. scalebox/test/code_interpreter_test.py +34 -34
  43. scalebox/test/code_interpreter_test_sync.py +34 -34
  44. scalebox/test/run_stress_code_interpreter_sync.py +178 -0
  45. scalebox/test/simple_upload_example.py +131 -0
  46. scalebox/test/stabitiy_test.py +323 -0
  47. scalebox/test/test_browser_use.py +27 -0
  48. scalebox/test/test_browser_use_scalebox.py +62 -0
  49. scalebox/test/test_code_interpreter_execcode.py +289 -211
  50. scalebox/test/test_code_interpreter_sync_comprehensive.py +116 -69
  51. scalebox/test/test_connect_pause_async.py +300 -0
  52. scalebox/test/test_connect_pause_sync.py +300 -0
  53. scalebox/test/test_csx_desktop_examples.py +3 -3
  54. scalebox/test/test_desktop_sandbox_sf.py +112 -0
  55. scalebox/test/test_download_url.py +41 -0
  56. scalebox/test/test_existing_sandbox.py +1037 -0
  57. scalebox/test/test_sandbox_async_comprehensive.py +5 -3
  58. scalebox/test/test_sandbox_object_storage_example.py +151 -0
  59. scalebox/test/test_sandbox_object_storage_example_async.py +159 -0
  60. scalebox/test/test_sandbox_sync_comprehensive.py +1 -1
  61. scalebox/test/test_sf.py +141 -0
  62. scalebox/test/test_watch_dir_async.py +58 -0
  63. scalebox/test/testacreate.py +1 -1
  64. scalebox/test/testagetinfo.py +1 -3
  65. scalebox/test/testcomputeuse.py +243 -243
  66. scalebox/test/testsandbox_api.py +5 -5
  67. scalebox/test/testsandbox_async.py +17 -47
  68. scalebox/test/testsandbox_sync.py +19 -15
  69. scalebox/test/upload_100mb_example.py +377 -0
  70. scalebox/utils/httpcoreclient.py +297 -297
  71. scalebox/utils/httpxclient.py +403 -403
  72. scalebox/version.py +2 -2
  73. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/METADATA +1 -1
  74. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/RECORD +78 -60
  75. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/WHEEL +1 -1
  76. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/entry_points.txt +0 -0
  77. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/licenses/LICENSE +0 -0
  78. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ OpenAI-compatible adapter for Amazon Bedrock Claude 3.7 Sonnet
4
+ POST /v1/chat/completions -> Bedrock invoke-model
5
+ """
6
+ import json, boto3, os
7
+ from fastapi import FastAPI, Response, status
8
+ from pydantic import BaseModel
9
+ from typing import List, Dict # <- 修正语法
10
+
11
+ app = FastAPI()
12
+ BEDROCK = boto3.client("bedrock-runtime", region_name="us-east-1")
13
+ MODEL_ID = "us.anthropic.claude-3-7-sonnet-20250219-v1:0"
14
+
15
+
16
+ class Message(BaseModel):
17
+ role: str
18
+ content: str
19
+
20
+
21
+ class ChatRequest(BaseModel):
22
+ model: str
23
+ messages: List[Message]
24
+ max_tokens: int = 4096
25
+ temperature: float = 0.1
26
+
27
+
28
+ @app.post("/v1/chat/completions")
29
+ def chat_completions(req: ChatRequest):
30
+ # 1. OpenAI -> Claude 格式
31
+ claude_body = {
32
+ "anthropic_version": "bedrock-2023-05-31",
33
+ "max_tokens": req.max_tokens,
34
+ "temperature": req.temperature,
35
+ "messages": [{"role": m.role, "content": m.content} for m in req.messages],
36
+ }
37
+ # 2. 调 Bedrock
38
+ response = BEDROCK.invoke_model(
39
+ modelId=MODEL_ID,
40
+ body=json.dumps(claude_body),
41
+ contentType="application/json",
42
+ )
43
+ # 3. 解析 Claude 响应
44
+ result = json.loads(response["body"].read())
45
+ text = result["content"][0]["text"]
46
+
47
+ # 4. 伪装成 OpenAI 格式返回
48
+ openai_reply = {
49
+ "id": "chatcmpl-bedrock",
50
+ "object": "chat.completion",
51
+ "created": 1234567890,
52
+ "model": "gpt-4",
53
+ "choices": [
54
+ {
55
+ "index": 0,
56
+ "message": {"role": "assistant", "content": text},
57
+ "finish_reason": "stop",
58
+ }
59
+ ],
60
+ }
61
+ return Response(content=json.dumps(openai_reply), media_type="application/json")
62
+
63
+
64
+ # 健康检查
65
+ @app.get("/v1/models")
66
+ def models():
67
+ return {"data": [{"id": "gpt-4", "object": "model"}]}
68
+
69
+
70
+ if __name__ == "__main__":
71
+ import uvicorn
72
+
73
+ uvicorn.run(app, host="0.0.0.0", port=8000)
@@ -1,34 +1,34 @@
1
- import asyncio
2
- import time
3
-
4
- from scalebox.code_interpreter import AsyncSandbox
5
-
6
- # from scalebox.sandbox_async.main import AsyncSandbox
7
-
8
-
9
- async def pty_output_handler(output):
10
- """处理 PTY 输出的回调函数"""
11
- print(f"输出: {output}")
12
-
13
-
14
- async def main():
15
- sandbox = AsyncSandbox.create()
16
- proc = await sandbox.run_code(
17
- """
18
- import time
19
- for i in range(3):
20
- print("Hello Scalebox", i)
21
- time.sleep(50)
22
- """,
23
- language="python3",
24
- request_timeout=3600,
25
- on_stdout=pty_output_handler,
26
- on_stderr=pty_output_handler,
27
- on_result=pty_output_handler,
28
- )
29
- print(proc)
30
- time.sleep(10)
31
-
32
-
33
- if __name__ == "__main__":
34
- asyncio.run(main())
1
+ import asyncio
2
+ import time
3
+
4
+ from scalebox.code_interpreter import AsyncSandbox
5
+
6
+ # from scalebox.sandbox_async.main import AsyncSandbox
7
+
8
+
9
+ async def pty_output_handler(output):
10
+ """处理 PTY 输出的回调函数"""
11
+ print(f"输出: {output}")
12
+
13
+
14
+ async def main():
15
+ sandbox = AsyncSandbox.create()
16
+ proc = await sandbox.run_code(
17
+ """
18
+ import time
19
+ for i in range(3):
20
+ print("Hello Scalebox", i)
21
+ time.sleep(50)
22
+ """,
23
+ language="python3",
24
+ request_timeout=3600,
25
+ on_stdout=pty_output_handler,
26
+ on_stderr=pty_output_handler,
27
+ on_result=pty_output_handler,
28
+ )
29
+ print(proc)
30
+ time.sleep(10)
31
+
32
+
33
+ if __name__ == "__main__":
34
+ asyncio.run(main())
@@ -1,34 +1,34 @@
1
- import asyncio
2
- import time
3
-
4
- from code_interpreter import Sandbox
5
-
6
- # from scalebox.sandbox_async.main import AsyncSandbox
7
-
8
-
9
- def pty_output_handler(output):
10
- """处理 PTY 输出的回调函数"""
11
- print(f"输出: {output}")
12
-
13
-
14
- def main():
15
- sandbox = Sandbox()
16
- proc = sandbox.run_code(
17
- """
18
- import time
19
- for i in range(3):
20
- print("Hello Scalebox", i)
21
- time.sleep(50)
22
- """,
23
- language="python3",
24
- request_timeout=3600,
25
- on_stdout=pty_output_handler,
26
- on_stderr=pty_output_handler,
27
- on_result=pty_output_handler,
28
- )
29
- print(proc)
30
- time.sleep(10)
31
-
32
-
33
- if __name__ == "__main__":
34
- main()
1
+ import asyncio
2
+ import time
3
+
4
+ from code_interpreter import Sandbox
5
+
6
+ # from scalebox.sandbox_async.main import AsyncSandbox
7
+
8
+
9
+ def pty_output_handler(output):
10
+ """处理 PTY 输出的回调函数"""
11
+ print(f"输出: {output}")
12
+
13
+
14
+ def main():
15
+ sandbox = Sandbox()
16
+ proc = sandbox.run_code(
17
+ """
18
+ import time
19
+ for i in range(3):
20
+ print("Hello Scalebox", i)
21
+ time.sleep(50)
22
+ """,
23
+ language="python3",
24
+ request_timeout=3600,
25
+ on_stdout=pty_output_handler,
26
+ on_stderr=pty_output_handler,
27
+ on_result=pty_output_handler,
28
+ )
29
+ print(proc)
30
+ time.sleep(10)
31
+
32
+
33
+ if __name__ == "__main__":
34
+ main()
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env python3
2
+ import asyncio
3
+ import os
4
+ import sys
5
+ import time
6
+ from typing import Tuple
7
+ import re
8
+
9
+
10
+ REPO_ROOT = "/root/scalebox"
11
+ TEST_FILE = os.path.join(
12
+ REPO_ROOT, "test", "test_code_interpreter_sync_comprehensive.py"
13
+ )
14
+
15
+
16
+ def _extract_final_report(stdout_text: str) -> str:
17
+ lines = stdout_text.splitlines()
18
+ if not lines:
19
+ return ""
20
+ # Prefer capturing from the last report title to include full details
21
+ title_indices = [
22
+ i
23
+ for i, line in enumerate(lines)
24
+ if "CodeInterpreter" in line and "测试报告" in line
25
+ ]
26
+ if title_indices:
27
+ start_idx = title_indices[-1]
28
+ return "\n".join(lines[start_idx:]).strip()
29
+ # Fallback: last long '====' separator block
30
+ sep_indices = [
31
+ i
32
+ for i, line in enumerate(lines)
33
+ if line.strip().startswith("=")
34
+ and set(line.strip()) == {"="}
35
+ and len(line.strip()) >= 20
36
+ ]
37
+ if sep_indices:
38
+ start_idx = sep_indices[-1]
39
+ return "\n".join(lines[start_idx:]).strip()
40
+ return stdout_text.strip()
41
+
42
+
43
+ def _extract_sandbox_id(text: str) -> str:
44
+ # Common explicit patterns
45
+ patterns = [
46
+ r"sandbox[_\s-]*id\s*[:=]\s*([A-Za-z0-9_\-]+)",
47
+ r"Sandbox[_\s-]*ID\s*[:=]\s*([A-Za-z0-9_\-]+)",
48
+ r"sandbox\s*:\s*([A-Za-z0-9_\-]+)",
49
+ r"sandboxId\s*[:=]\s*([A-Za-z0-9_\-]+)",
50
+ ]
51
+ for pat in patterns:
52
+ m = re.search(pat, text, flags=re.IGNORECASE)
53
+ if m:
54
+ return m.group(1)
55
+ # UUID-like fallback
56
+ uuid_pat = r"\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}\b"
57
+ m2 = re.search(uuid_pat, text)
58
+ if m2:
59
+ return m2.group(0)
60
+ # sbx_* style fallback
61
+ m3 = re.search(r"\bsbx_[A-Za-z0-9]+\b", text)
62
+ if m3:
63
+ return m3.group(0)
64
+ return ""
65
+
66
+
67
+ async def run_once(run_id: int) -> Tuple[int, float, str, str]:
68
+ start = time.perf_counter()
69
+ process = await asyncio.create_subprocess_exec(
70
+ sys.executable,
71
+ TEST_FILE,
72
+ cwd=REPO_ROOT,
73
+ stdout=asyncio.subprocess.PIPE,
74
+ stderr=asyncio.subprocess.PIPE,
75
+ )
76
+ stdout, stderr = await process.communicate()
77
+ duration = time.perf_counter() - start
78
+ exit_code = process.returncode
79
+ result = "PASS" if exit_code == 0 else "FAIL"
80
+ stdout_text = stdout.decode(errors="ignore") if stdout else ""
81
+ stderr_text = stderr.decode(errors="ignore") if stderr else ""
82
+ report = _extract_final_report(stdout_text) or _extract_final_report(stderr_text)
83
+ if not report:
84
+ # Fallback to a brief tail if no recognizable report
85
+ tail = (stdout_text or stderr_text).strip().splitlines()[-10:]
86
+ report = "\n".join(tail).strip()
87
+ # Determine if success rate is effectively 0%
88
+ combined_text = "\n".join([report, stdout_text, stderr_text])
89
+ zero_rate = False
90
+ # Match ASCII/full-width colon and percent, and allow 0, 0.0, 0.00 etc.
91
+ if re.search(r"成功率\s*[::]\s*0(?:\.0+)?\s*[%%]", combined_text):
92
+ zero_rate = True
93
+ else:
94
+ # Fallback by parsing totals if present (失败数 == 总测试数)
95
+ total_m = re.search(r"总测试数\s*[::]\s*(\d+)", combined_text)
96
+ fail_m = re.search(r"失败数\s*[::]\s*(\d+)", combined_text)
97
+ if total_m and fail_m:
98
+ try:
99
+ total_n = int(total_m.group(1))
100
+ fail_n = int(fail_m.group(1))
101
+ if total_n > 0 and fail_n == total_n:
102
+ zero_rate = True
103
+ except ValueError:
104
+ pass
105
+
106
+ # If success rate is 0%, mark as FAIL and attach detailed errors
107
+ if zero_rate:
108
+ result = "FAIL"
109
+ stderr_lines = stderr_text.strip().splitlines()
110
+ stdout_lines = stdout_text.strip().splitlines()
111
+ # Extract sandbox id from combined text
112
+ sandbox_id = _extract_sandbox_id(combined_text)
113
+ detail = []
114
+ if sandbox_id:
115
+ detail.append(f"Sandbox ID: {sandbox_id}")
116
+ if stderr_lines:
117
+ detail.append("[stderr]")
118
+ detail.extend(stderr_lines)
119
+ if stdout_lines:
120
+ detail.append("[stdout]")
121
+ detail.extend(stdout_lines)
122
+ if detail:
123
+ report = f"{report}\n\n" + "\n".join(detail)
124
+ return run_id, duration, result, report
125
+
126
+
127
+ async def run_concurrent(concurrency: int) -> None:
128
+ if not os.path.isfile(TEST_FILE):
129
+ print(f"Test file not found: {TEST_FILE}")
130
+ sys.exit(2)
131
+
132
+ tasks = [asyncio.create_task(run_once(i + 1)) for i in range(concurrency)]
133
+
134
+ started_at = time.perf_counter()
135
+ completed = 0
136
+ passed = 0
137
+ failed = 0
138
+
139
+ for coro in asyncio.as_completed(tasks):
140
+ run_id, duration, result, report = await coro
141
+ completed += 1
142
+ if result == "PASS":
143
+ passed += 1
144
+ else:
145
+ failed += 1
146
+ print(f"Run {run_id:03d}: {result} in {duration:.2f}s", flush=True)
147
+ if report:
148
+ print(report, flush=True)
149
+ print("-" * 64, flush=True)
150
+
151
+ total_time = time.perf_counter() - started_at
152
+ print("-" * 64)
153
+ print(
154
+ f"Completed: {completed}, Passed: {passed}, Failed: {failed}, Total time: {total_time:.2f}s"
155
+ )
156
+
157
+
158
+ def main() -> None:
159
+ # Default concurrency to 80, but allow override via CLI
160
+ try:
161
+ concurrency = int(sys.argv[1]) if len(sys.argv) > 1 else 80
162
+ except ValueError:
163
+ print("First argument must be an integer concurrency level.")
164
+ sys.exit(2)
165
+
166
+ if concurrency <= 0:
167
+ print("Concurrency must be a positive integer.")
168
+ sys.exit(2)
169
+
170
+ # Use Proactor on Windows; default loop elsewhere
171
+ if sys.platform.startswith("win"):
172
+ asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) # type: ignore[attr-defined]
173
+
174
+ asyncio.run(run_concurrent(concurrency))
175
+
176
+
177
+ if __name__ == "__main__":
178
+ main()
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 简单的 100MB 文件上传示例
4
+ 使用 sandbox_sync.filesystem.filesystem.py 上传文件到沙箱
5
+ """
6
+
7
+ import os
8
+ import time
9
+ import logging
10
+ from scalebox.sandbox_sync.main import Sandbox
11
+ from scalebox.connection_config import ConnectionConfig
12
+
13
+ # 配置日志
14
+ logging.basicConfig(
15
+ level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
16
+ )
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def create_test_file(file_path: str, size_mb: int = 100):
21
+ """创建指定大小的测试文件"""
22
+ logger.info(f"创建 {size_mb}MB 测试文件: {file_path}")
23
+
24
+ file_size = size_mb * 1024 * 1024 # 转换为字节
25
+ chunk_size = 1024 * 1024 # 1MB 块
26
+
27
+ with open(file_path, "wb") as f:
28
+ written = 0
29
+ while written < file_size:
30
+ # 生成测试数据
31
+ chunk_data = bytes(
32
+ [i % 256 for i in range(min(chunk_size, file_size - written))]
33
+ )
34
+ f.write(chunk_data)
35
+ written += len(chunk_data)
36
+
37
+ if written % (10 * 1024 * 1024) == 0: # 每10MB显示进度
38
+ logger.info(f"进度: {written // (1024*1024)}MB")
39
+
40
+ logger.info(f"文件创建完成: {os.path.getsize(file_path) / (1024*1024):.2f}MB")
41
+
42
+
43
+ def main():
44
+ """主函数 - 演示文件上传"""
45
+
46
+ # 1. 创建 100MB 测试文件
47
+ test_file = "/tmp/test_100mb.bin"
48
+ create_test_file(test_file, 100)
49
+
50
+ # 2. 创建沙箱连接配置
51
+ connection_config = ConnectionConfig(
52
+ debug=True, request_timeout=300.0 # 调试模式 # 5分钟超时
53
+ )
54
+
55
+ # 3. 创建沙箱实例
56
+ logger.info("连接沙箱...")
57
+ sandbox = Sandbox(
58
+ sandbox_id="upload_test_sandbox",
59
+ sandbox_domain=None,
60
+ envd_version="v1.0",
61
+ envd_access_token=None,
62
+ connection_config=connection_config,
63
+ )
64
+
65
+ try:
66
+ # 4. 检查沙箱状态
67
+ if not sandbox.is_running():
68
+ logger.error("沙箱未运行")
69
+ return
70
+
71
+ logger.info(f"沙箱连接成功: {sandbox.sandbox_id}")
72
+
73
+ # 5. 读取本地文件
74
+ logger.info("读取本地文件...")
75
+ start_time = time.time()
76
+
77
+ with open(test_file, "rb") as f:
78
+ file_data = f.read()
79
+
80
+ read_time = time.time() - start_time
81
+ logger.info(
82
+ f"文件读取完成: {len(file_data) / (1024*1024):.2f}MB, 耗时: {read_time:.2f}秒"
83
+ )
84
+
85
+ # 6. 上传文件到沙箱
86
+ logger.info("开始上传文件到沙箱...")
87
+ upload_start = time.time()
88
+
89
+ # 使用 filesystem.write 方法上传
90
+ result = sandbox.files.write("/tmp/uploaded_100mb.bin", file_data)
91
+
92
+ upload_time = time.time() - upload_start
93
+ logger.info(f"上传完成: {result.path}")
94
+ logger.info(f"上传耗时: {upload_time:.2f}秒")
95
+ logger.info(f"上传速度: {len(file_data) / (1024*1024) / upload_time:.2f} MB/s")
96
+
97
+ # 7. 验证上传结果
98
+ logger.info("验证上传结果...")
99
+
100
+ # 检查文件是否存在
101
+ if sandbox.files.exists("/tmp/uploaded_100mb.bin"):
102
+ logger.info("✅ 文件上传成功")
103
+
104
+ # 获取文件信息
105
+ file_info = sandbox.files.get_info("/tmp/uploaded_100mb.bin")
106
+ logger.info(f"文件信息: 路径={file_info.path}, 大小={file_info.size}字节")
107
+
108
+ else:
109
+ logger.error("❌ 文件上传失败")
110
+
111
+ except Exception as e:
112
+ logger.error(f"上传过程中发生错误: {e}")
113
+
114
+ finally:
115
+ # 8. 清理资源
116
+ try:
117
+ sandbox.kill()
118
+ logger.info("沙箱已清理")
119
+ except Exception as e:
120
+ logger.warning(f"清理沙箱时发生错误: {e}")
121
+
122
+ # 删除本地测试文件
123
+ try:
124
+ os.remove(test_file)
125
+ logger.info("本地测试文件已删除")
126
+ except Exception as e:
127
+ logger.warning(f"删除本地文件时发生错误: {e}")
128
+
129
+
130
+ if __name__ == "__main__":
131
+ main()