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