agentscope-runtime 0.1.0__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.
- agentscope_runtime/__init__.py +4 -0
- agentscope_runtime/engine/__init__.py +9 -0
- agentscope_runtime/engine/agents/__init__.py +2 -0
- agentscope_runtime/engine/agents/agentscope_agent/__init__.py +6 -0
- agentscope_runtime/engine/agents/agentscope_agent/agent.py +342 -0
- agentscope_runtime/engine/agents/agentscope_agent/hooks.py +156 -0
- agentscope_runtime/engine/agents/agno_agent.py +220 -0
- agentscope_runtime/engine/agents/base_agent.py +29 -0
- agentscope_runtime/engine/agents/langgraph_agent.py +59 -0
- agentscope_runtime/engine/agents/llm_agent.py +51 -0
- agentscope_runtime/engine/deployers/__init__.py +3 -0
- agentscope_runtime/engine/deployers/adapter/__init__.py +0 -0
- agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +2 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_adapter_utils.py +425 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_agent_adapter.py +69 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +60 -0
- agentscope_runtime/engine/deployers/adapter/protocol_adapter.py +24 -0
- agentscope_runtime/engine/deployers/base.py +17 -0
- agentscope_runtime/engine/deployers/local_deployer.py +586 -0
- agentscope_runtime/engine/helpers/helper.py +127 -0
- agentscope_runtime/engine/llms/__init__.py +3 -0
- agentscope_runtime/engine/llms/base_llm.py +60 -0
- agentscope_runtime/engine/llms/qwen_llm.py +47 -0
- agentscope_runtime/engine/misc/__init__.py +0 -0
- agentscope_runtime/engine/runner.py +186 -0
- agentscope_runtime/engine/schemas/__init__.py +0 -0
- agentscope_runtime/engine/schemas/agent_schemas.py +551 -0
- agentscope_runtime/engine/schemas/context.py +54 -0
- agentscope_runtime/engine/services/__init__.py +9 -0
- agentscope_runtime/engine/services/base.py +77 -0
- agentscope_runtime/engine/services/context_manager.py +129 -0
- agentscope_runtime/engine/services/environment_manager.py +50 -0
- agentscope_runtime/engine/services/manager.py +174 -0
- agentscope_runtime/engine/services/memory_service.py +270 -0
- agentscope_runtime/engine/services/sandbox_service.py +198 -0
- agentscope_runtime/engine/services/session_history_service.py +256 -0
- agentscope_runtime/engine/tracing/__init__.py +40 -0
- agentscope_runtime/engine/tracing/base.py +309 -0
- agentscope_runtime/engine/tracing/local_logging_handler.py +356 -0
- agentscope_runtime/engine/tracing/tracing_metric.py +69 -0
- agentscope_runtime/engine/tracing/wrapper.py +321 -0
- agentscope_runtime/sandbox/__init__.py +14 -0
- agentscope_runtime/sandbox/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/base/__init__.py +0 -0
- agentscope_runtime/sandbox/box/base/base_sandbox.py +37 -0
- agentscope_runtime/sandbox/box/base/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +176 -0
- agentscope_runtime/sandbox/box/dummy/__init__.py +0 -0
- agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +26 -0
- agentscope_runtime/sandbox/box/filesystem/__init__.py +0 -0
- agentscope_runtime/sandbox/box/filesystem/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +87 -0
- agentscope_runtime/sandbox/box/sandbox.py +115 -0
- agentscope_runtime/sandbox/box/shared/__init__.py +0 -0
- agentscope_runtime/sandbox/box/shared/app.py +44 -0
- agentscope_runtime/sandbox/box/shared/dependencies/__init__.py +5 -0
- agentscope_runtime/sandbox/box/shared/dependencies/deps.py +22 -0
- agentscope_runtime/sandbox/box/shared/routers/__init__.py +12 -0
- agentscope_runtime/sandbox/box/shared/routers/generic.py +173 -0
- agentscope_runtime/sandbox/box/shared/routers/mcp.py +207 -0
- agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +153 -0
- agentscope_runtime/sandbox/box/shared/routers/runtime_watcher.py +187 -0
- agentscope_runtime/sandbox/box/shared/routers/workspace.py +325 -0
- agentscope_runtime/sandbox/box/training_box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/training_box/base.py +120 -0
- agentscope_runtime/sandbox/box/training_box/env_service.py +752 -0
- agentscope_runtime/sandbox/box/training_box/environments/__init__.py +0 -0
- agentscope_runtime/sandbox/box/training_box/environments/appworld/appworld_env.py +987 -0
- agentscope_runtime/sandbox/box/training_box/registry.py +54 -0
- agentscope_runtime/sandbox/box/training_box/src/trajectory.py +278 -0
- agentscope_runtime/sandbox/box/training_box/training_box.py +219 -0
- agentscope_runtime/sandbox/build.py +213 -0
- agentscope_runtime/sandbox/client/__init__.py +5 -0
- agentscope_runtime/sandbox/client/http_client.py +527 -0
- agentscope_runtime/sandbox/client/training_client.py +265 -0
- agentscope_runtime/sandbox/constant.py +5 -0
- agentscope_runtime/sandbox/custom/__init__.py +16 -0
- agentscope_runtime/sandbox/custom/custom_sandbox.py +40 -0
- agentscope_runtime/sandbox/custom/example.py +37 -0
- agentscope_runtime/sandbox/enums.py +68 -0
- agentscope_runtime/sandbox/manager/__init__.py +4 -0
- agentscope_runtime/sandbox/manager/collections/__init__.py +22 -0
- agentscope_runtime/sandbox/manager/collections/base_mapping.py +20 -0
- agentscope_runtime/sandbox/manager/collections/base_queue.py +25 -0
- agentscope_runtime/sandbox/manager/collections/base_set.py +25 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +22 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_queue.py +28 -0
- agentscope_runtime/sandbox/manager/collections/in_memory_set.py +27 -0
- agentscope_runtime/sandbox/manager/collections/redis_mapping.py +26 -0
- agentscope_runtime/sandbox/manager/collections/redis_queue.py +27 -0
- agentscope_runtime/sandbox/manager/collections/redis_set.py +23 -0
- agentscope_runtime/sandbox/manager/container_clients/__init__.py +8 -0
- agentscope_runtime/sandbox/manager/container_clients/base_client.py +39 -0
- agentscope_runtime/sandbox/manager/container_clients/docker_client.py +170 -0
- agentscope_runtime/sandbox/manager/sandbox_manager.py +694 -0
- agentscope_runtime/sandbox/manager/server/__init__.py +0 -0
- agentscope_runtime/sandbox/manager/server/app.py +194 -0
- agentscope_runtime/sandbox/manager/server/config.py +68 -0
- agentscope_runtime/sandbox/manager/server/models.py +17 -0
- agentscope_runtime/sandbox/manager/storage/__init__.py +10 -0
- agentscope_runtime/sandbox/manager/storage/data_storage.py +16 -0
- agentscope_runtime/sandbox/manager/storage/local_storage.py +44 -0
- agentscope_runtime/sandbox/manager/storage/oss_storage.py +89 -0
- agentscope_runtime/sandbox/manager/utils.py +78 -0
- agentscope_runtime/sandbox/mcp_server.py +192 -0
- agentscope_runtime/sandbox/model/__init__.py +12 -0
- agentscope_runtime/sandbox/model/api.py +16 -0
- agentscope_runtime/sandbox/model/container.py +72 -0
- agentscope_runtime/sandbox/model/manager_config.py +158 -0
- agentscope_runtime/sandbox/registry.py +129 -0
- agentscope_runtime/sandbox/tools/__init__.py +12 -0
- agentscope_runtime/sandbox/tools/base/__init__.py +8 -0
- agentscope_runtime/sandbox/tools/base/tool.py +52 -0
- agentscope_runtime/sandbox/tools/browser/__init__.py +57 -0
- agentscope_runtime/sandbox/tools/browser/tool.py +597 -0
- agentscope_runtime/sandbox/tools/filesystem/__init__.py +32 -0
- agentscope_runtime/sandbox/tools/filesystem/tool.py +319 -0
- agentscope_runtime/sandbox/tools/function_tool.py +321 -0
- agentscope_runtime/sandbox/tools/mcp_tool.py +191 -0
- agentscope_runtime/sandbox/tools/sandbox_tool.py +104 -0
- agentscope_runtime/sandbox/tools/tool.py +123 -0
- agentscope_runtime/sandbox/tools/utils.py +68 -0
- agentscope_runtime/version.py +2 -0
- agentscope_runtime-0.1.0.dist-info/METADATA +327 -0
- agentscope_runtime-0.1.0.dist-info/RECORD +131 -0
- agentscope_runtime-0.1.0.dist-info/WHEEL +5 -0
- agentscope_runtime-0.1.0.dist-info/entry_points.txt +4 -0
- agentscope_runtime-0.1.0.dist-info/licenses/LICENSE +202 -0
- agentscope_runtime-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import argparse
|
|
3
|
+
import inspect
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from mcp.server.fastmcp import FastMCP
|
|
7
|
+
from .box.base.base_sandbox import BaseSandbox
|
|
8
|
+
from .box.browser.browser_sandbox import BrowserSandbox
|
|
9
|
+
from .box.filesystem.filesystem_sandbox import FilesystemSandbox
|
|
10
|
+
from .enums import SandboxType
|
|
11
|
+
|
|
12
|
+
logging.basicConfig(level=logging.INFO)
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
mcp = FastMCP("AgentRuntime Sandbox MCP Server")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def json_type_to_python_type(json_type: str):
|
|
19
|
+
"""Convert JSON schema type to Python type annotation."""
|
|
20
|
+
type_mapping = {
|
|
21
|
+
"string": str,
|
|
22
|
+
"integer": int,
|
|
23
|
+
"number": float,
|
|
24
|
+
"boolean": bool,
|
|
25
|
+
"array": list,
|
|
26
|
+
"object": dict,
|
|
27
|
+
}
|
|
28
|
+
return type_mapping.get(json_type, str)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def extract_content_from_mcp_response(response):
|
|
32
|
+
"""Extract actual content from MCP protocol response."""
|
|
33
|
+
if isinstance(response, dict):
|
|
34
|
+
if "content" in response:
|
|
35
|
+
content = response["content"]
|
|
36
|
+
if isinstance(content, list) and len(content) > 0:
|
|
37
|
+
first_item = content[0]
|
|
38
|
+
if isinstance(first_item, dict) and "text" in first_item:
|
|
39
|
+
return first_item["text"]
|
|
40
|
+
elif isinstance(first_item, dict) and "data" in first_item:
|
|
41
|
+
return first_item["data"]
|
|
42
|
+
return content
|
|
43
|
+
elif "result" in response:
|
|
44
|
+
return response["result"]
|
|
45
|
+
elif "data" in response:
|
|
46
|
+
return response["data"]
|
|
47
|
+
return response
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def create_dynamic_function(schema, method_name, box):
|
|
51
|
+
"""Safely create a function with dynamic signature using inspect API."""
|
|
52
|
+
func_name = schema["function"]["name"]
|
|
53
|
+
func_description = schema["function"]["description"]
|
|
54
|
+
parameters = schema["function"]["parameters"]
|
|
55
|
+
properties = parameters.get("properties", {})
|
|
56
|
+
required = parameters.get("required", [])
|
|
57
|
+
|
|
58
|
+
# Create parameter list using inspect.Parameter
|
|
59
|
+
params = []
|
|
60
|
+
for param_name, param_info in properties.items():
|
|
61
|
+
param_type = json_type_to_python_type(param_info.get("type", "string"))
|
|
62
|
+
if param_name in required:
|
|
63
|
+
param = inspect.Parameter(
|
|
64
|
+
param_name,
|
|
65
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
66
|
+
annotation=param_type,
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
param = inspect.Parameter(
|
|
70
|
+
param_name,
|
|
71
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
72
|
+
default=None,
|
|
73
|
+
annotation=param_type,
|
|
74
|
+
)
|
|
75
|
+
params.append(param)
|
|
76
|
+
|
|
77
|
+
# Create new signature
|
|
78
|
+
new_signature = inspect.Signature(params)
|
|
79
|
+
|
|
80
|
+
# Create wrapper function
|
|
81
|
+
def wrapper(*args, **kwargs):
|
|
82
|
+
"""Dynamically generated wrapper function."""
|
|
83
|
+
# Bind arguments to signature
|
|
84
|
+
try:
|
|
85
|
+
bound = new_signature.bind(*args, **kwargs)
|
|
86
|
+
bound.apply_defaults()
|
|
87
|
+
except TypeError as e:
|
|
88
|
+
raise TypeError(f"Invalid arguments for {func_name}: {e}") from e
|
|
89
|
+
|
|
90
|
+
# Filter out None values for optional parameters
|
|
91
|
+
filtered_kwargs = {
|
|
92
|
+
k: v for k, v in bound.arguments.items() if v is not None
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Call the actual method and extract content from MCP response
|
|
96
|
+
mcp_response = getattr(box, method_name)(**filtered_kwargs)
|
|
97
|
+
# Extract the actual content, not the MCP protocol wrapper
|
|
98
|
+
actual_result = extract_content_from_mcp_response(mcp_response)
|
|
99
|
+
|
|
100
|
+
return actual_result
|
|
101
|
+
|
|
102
|
+
# Set function metadata
|
|
103
|
+
wrapper.__signature__ = new_signature
|
|
104
|
+
wrapper.__name__ = func_name
|
|
105
|
+
wrapper.__doc__ = func_description
|
|
106
|
+
wrapper.__qualname__ = func_name
|
|
107
|
+
return wrapper
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def register_tools(box):
|
|
111
|
+
"""Register all tools with the MCP server using FastMCP decorators."""
|
|
112
|
+
try:
|
|
113
|
+
tools_json = box.list_tools()
|
|
114
|
+
|
|
115
|
+
for server_name, tool_dict in tools_json.items():
|
|
116
|
+
logger.info(f"Registering tools from server: {server_name}")
|
|
117
|
+
|
|
118
|
+
for tool_name, tool_info in tool_dict.items():
|
|
119
|
+
json_schema = tool_info["json_schema"]
|
|
120
|
+
|
|
121
|
+
# Create dynamic function with proper signature
|
|
122
|
+
dynamic_func = create_dynamic_function(
|
|
123
|
+
json_schema,
|
|
124
|
+
tool_name,
|
|
125
|
+
box,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Apply MCP decorator
|
|
129
|
+
_ = mcp.tool(
|
|
130
|
+
name=json_schema["function"]["name"],
|
|
131
|
+
description=json_schema["function"]["description"],
|
|
132
|
+
)(dynamic_func)
|
|
133
|
+
|
|
134
|
+
logger.info(
|
|
135
|
+
f"Registered tool: {json_schema['function']['name']}",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.error(f"Error registering tools: {e}")
|
|
140
|
+
raise
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def main():
|
|
144
|
+
parser = argparse.ArgumentParser(
|
|
145
|
+
description="Run the AgentRuntime MCP Server.",
|
|
146
|
+
)
|
|
147
|
+
parser.add_argument(
|
|
148
|
+
"--type",
|
|
149
|
+
required=False,
|
|
150
|
+
default="base",
|
|
151
|
+
choices=["base", "browser", "filesystem"],
|
|
152
|
+
help="Type of sandbox to run",
|
|
153
|
+
)
|
|
154
|
+
parser.add_argument(
|
|
155
|
+
"--base_url",
|
|
156
|
+
required=False,
|
|
157
|
+
default=None,
|
|
158
|
+
help="Base URL for the server",
|
|
159
|
+
)
|
|
160
|
+
parser.add_argument(
|
|
161
|
+
"--bearer_token",
|
|
162
|
+
required=False,
|
|
163
|
+
default=None,
|
|
164
|
+
help="Bearer token for authentication",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
args = parser.parse_args()
|
|
168
|
+
|
|
169
|
+
logger.info(
|
|
170
|
+
f"Running {args.type} sandbox with base URL `{args.base_url}` and "
|
|
171
|
+
f"bearer token `{args.bearer_token}`",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
sandbox_type = SandboxType(args.type)
|
|
175
|
+
|
|
176
|
+
if sandbox_type == SandboxType.BASE:
|
|
177
|
+
sandbox_cls = BaseSandbox
|
|
178
|
+
elif sandbox_type == SandboxType.BROWSER:
|
|
179
|
+
sandbox_cls = BrowserSandbox
|
|
180
|
+
elif sandbox_type == SandboxType.FILESYSTEM:
|
|
181
|
+
sandbox_cls = FilesystemSandbox
|
|
182
|
+
|
|
183
|
+
with sandbox_cls(
|
|
184
|
+
base_url=args.base_url,
|
|
185
|
+
bearer_token=args.bearer_token,
|
|
186
|
+
) as box:
|
|
187
|
+
register_tools(box)
|
|
188
|
+
mcp.run()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
main()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from .container import ContainerModel
|
|
3
|
+
from .manager_config import (
|
|
4
|
+
SandboxManagerEnvConfig,
|
|
5
|
+
DEFAULT_LOCAL_MANAGER_CONFIG,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"ContainerModel",
|
|
10
|
+
"SandboxManagerEnvConfig",
|
|
11
|
+
"DEFAULT_LOCAL_MANAGER_CONFIG",
|
|
12
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RuntimeDiffResult(BaseModel):
|
|
8
|
+
diff: Optional[str] = Field(
|
|
9
|
+
None,
|
|
10
|
+
description="The modifications in the filesystem since the "
|
|
11
|
+
"last check.",
|
|
12
|
+
)
|
|
13
|
+
url: Optional[str] = Field(
|
|
14
|
+
None,
|
|
15
|
+
description="The current position where the browser is located.",
|
|
16
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# TODO: support k8s version
|
|
8
|
+
class ContainerModel(BaseModel):
|
|
9
|
+
session_id: str = Field(
|
|
10
|
+
...,
|
|
11
|
+
description="Unique identifier for the session",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
container_id: str = Field(
|
|
15
|
+
...,
|
|
16
|
+
description="Unique identifier for the container instance",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
container_name: str = Field(
|
|
20
|
+
...,
|
|
21
|
+
description="Human-readable name for the container",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
base_url: str = Field(
|
|
25
|
+
...,
|
|
26
|
+
description="Base URL for accessing the container",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
browser_url: str = Field(
|
|
30
|
+
...,
|
|
31
|
+
description="URL for browser interface within the container",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
front_browser_ws: str = Field(
|
|
35
|
+
...,
|
|
36
|
+
description="WebSocket URL for the browser used by frontend",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
client_browser_ws: str = Field(
|
|
40
|
+
...,
|
|
41
|
+
description="WebSocket URL for the browser used by runtime client",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
artifacts_sio: str = Field(
|
|
45
|
+
...,
|
|
46
|
+
description="Socketio URL for the artifacts used by frontend",
|
|
47
|
+
)
|
|
48
|
+
ports: List[int] = Field(
|
|
49
|
+
...,
|
|
50
|
+
description="List of occupied port numbers",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
mount_dir: str | None = Field(
|
|
54
|
+
None,
|
|
55
|
+
description="The mount directory of workspace.",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
storage_path: str | None = Field(
|
|
59
|
+
None,
|
|
60
|
+
description="The oss_path of workspace.",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
runtime_token: str | None = Field(
|
|
64
|
+
None,
|
|
65
|
+
description="Runtime token used for authentication or secure "
|
|
66
|
+
"communication",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
version: str | None = Field(
|
|
70
|
+
None,
|
|
71
|
+
description="Image version of the container",
|
|
72
|
+
)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint: disable=no-self-argument
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional, Literal, Tuple
|
|
5
|
+
from pydantic import BaseModel, Field, model_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SandboxManagerEnvConfig(BaseModel):
|
|
9
|
+
container_prefix_key: str = Field(
|
|
10
|
+
"runtime_sandbox_container_",
|
|
11
|
+
description="Prefix for keys related to Container models.",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
file_system: Literal["local", "oss"] = Field(
|
|
15
|
+
...,
|
|
16
|
+
description="Type of file system to use: 'local' or 'oss'.",
|
|
17
|
+
)
|
|
18
|
+
storage_folder: Optional[str] = Field(
|
|
19
|
+
"",
|
|
20
|
+
description="Folder path in storage.",
|
|
21
|
+
)
|
|
22
|
+
redis_enabled: bool = Field(
|
|
23
|
+
...,
|
|
24
|
+
description="Indicates if Redis is enabled.",
|
|
25
|
+
)
|
|
26
|
+
container_deployment: Literal["docker", "cloud"] = Field(
|
|
27
|
+
...,
|
|
28
|
+
description="container_deployment: 'docker'.",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
default_mount_dir: Optional[str] = Field(
|
|
32
|
+
None,
|
|
33
|
+
description="Path for local file system storage.",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
port_range: Tuple[int, int] = Field(
|
|
37
|
+
(49152, 59152),
|
|
38
|
+
description="Range of ports to be used by the manager.",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
pool_size: int = Field(
|
|
42
|
+
0,
|
|
43
|
+
description="Number of containers to be kept in the pool.",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# OSS settings
|
|
47
|
+
oss_endpoint: Optional[str] = Field(
|
|
48
|
+
"http://oss-cn-hangzhou.aliyuncs.com",
|
|
49
|
+
description="OSS endpoint URL. Required if file_system is 'oss'.",
|
|
50
|
+
)
|
|
51
|
+
oss_access_key_id: Optional[str] = Field(
|
|
52
|
+
None,
|
|
53
|
+
description="Access key ID for OSS. Required if file_system is 'oss'.",
|
|
54
|
+
)
|
|
55
|
+
oss_access_key_secret: Optional[str] = Field(
|
|
56
|
+
None,
|
|
57
|
+
description="Access key secret for OSS. Required if file_system is "
|
|
58
|
+
"'oss'.",
|
|
59
|
+
)
|
|
60
|
+
oss_bucket_name: Optional[str] = Field(
|
|
61
|
+
None,
|
|
62
|
+
description="Bucket name in OSS. Required if file_system is 'oss'.",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Redis settings
|
|
66
|
+
redis_server: Optional[str] = Field(
|
|
67
|
+
"localhost",
|
|
68
|
+
description="Redis server address. Required if Redis is enabled.",
|
|
69
|
+
)
|
|
70
|
+
redis_port: Optional[int] = Field(
|
|
71
|
+
6379,
|
|
72
|
+
description="Port for connecting to Redis. Required if Redis is "
|
|
73
|
+
"enabled.",
|
|
74
|
+
)
|
|
75
|
+
redis_db: Optional[int] = Field(
|
|
76
|
+
0,
|
|
77
|
+
description="Database index to use in Redis. Required if Redis is "
|
|
78
|
+
"enabled.",
|
|
79
|
+
)
|
|
80
|
+
redis_user: Optional[str] = Field(
|
|
81
|
+
None,
|
|
82
|
+
description="Username for Redis authentication.",
|
|
83
|
+
)
|
|
84
|
+
redis_password: Optional[str] = Field(
|
|
85
|
+
None,
|
|
86
|
+
description="Password for Redis authentication.",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
redis_port_key: str = Field(
|
|
90
|
+
"_runtime_sandbox_container_occupied_ports",
|
|
91
|
+
description="Prefix for Redis keys related to occupied ports.",
|
|
92
|
+
)
|
|
93
|
+
redis_container_pool_key: str = Field(
|
|
94
|
+
"_runtime_sandbox_container_container_pool",
|
|
95
|
+
description="Prefix for Redis keys related to container pool.",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@model_validator(mode="after")
|
|
99
|
+
def check_settings(cls, self):
|
|
100
|
+
if not self.default_mount_dir:
|
|
101
|
+
raise ValueError("default_mount_dir must be set")
|
|
102
|
+
|
|
103
|
+
os.makedirs(self.default_mount_dir, exist_ok=True)
|
|
104
|
+
|
|
105
|
+
if self.file_system == "oss":
|
|
106
|
+
required_oss_fields = [
|
|
107
|
+
self.oss_endpoint,
|
|
108
|
+
self.oss_access_key_id,
|
|
109
|
+
self.oss_access_key_secret,
|
|
110
|
+
self.oss_bucket_name,
|
|
111
|
+
]
|
|
112
|
+
for field_name, field_value in zip(
|
|
113
|
+
[
|
|
114
|
+
"oss_endpoint",
|
|
115
|
+
"oss_access_key_id",
|
|
116
|
+
"oss_access_key_secret",
|
|
117
|
+
"oss_bucket_name",
|
|
118
|
+
],
|
|
119
|
+
required_oss_fields,
|
|
120
|
+
):
|
|
121
|
+
if not field_value:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"{field_name} must be set when file_system is 'oss'",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if self.redis_enabled:
|
|
127
|
+
required_redis_fields = [
|
|
128
|
+
self.redis_server,
|
|
129
|
+
self.redis_port,
|
|
130
|
+
self.redis_db,
|
|
131
|
+
self.redis_port_key,
|
|
132
|
+
self.redis_container_pool_key,
|
|
133
|
+
]
|
|
134
|
+
for field_name, field_value in zip(
|
|
135
|
+
[
|
|
136
|
+
"redis_server",
|
|
137
|
+
"redis_port",
|
|
138
|
+
"redis_db",
|
|
139
|
+
"redis_port_key",
|
|
140
|
+
"redis_container_pool_key",
|
|
141
|
+
],
|
|
142
|
+
required_redis_fields,
|
|
143
|
+
):
|
|
144
|
+
if field_value is None:
|
|
145
|
+
raise ValueError(
|
|
146
|
+
f"{field_name} must be set when redis is enabled",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return self
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
DEFAULT_LOCAL_MANAGER_CONFIG = SandboxManagerEnvConfig(
|
|
153
|
+
file_system="local",
|
|
154
|
+
redis_enabled=False,
|
|
155
|
+
container_deployment="docker",
|
|
156
|
+
pool_size=0,
|
|
157
|
+
default_mount_dir="sessions_mount_dir",
|
|
158
|
+
)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from typing import Dict, Type, Optional
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from .enums import SandboxType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class SandboxConfig:
|
|
10
|
+
"""sandbox configuration information"""
|
|
11
|
+
|
|
12
|
+
image_name: str
|
|
13
|
+
sandbox_type: SandboxType | str
|
|
14
|
+
resource_limits: Optional[Dict] = None
|
|
15
|
+
security_level: str = "medium"
|
|
16
|
+
timeout: int = 60 # Default timeout of 5 minutes
|
|
17
|
+
description: str = ""
|
|
18
|
+
environment: Optional[Dict] = None
|
|
19
|
+
runtime_config: Optional[Dict] = None
|
|
20
|
+
|
|
21
|
+
def __post_init__(self):
|
|
22
|
+
if self.runtime_config is None:
|
|
23
|
+
self.runtime_config = {}
|
|
24
|
+
if "memory" in self.resource_limits:
|
|
25
|
+
self.runtime_config["mem_limit"] = self.resource_limits["memory"]
|
|
26
|
+
if "cpu" in self.resource_limits:
|
|
27
|
+
self.runtime_config["nano_cpus"] = int(
|
|
28
|
+
float(self.resource_limits["cpu"]) * 1e9,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SandboxRegistry:
|
|
33
|
+
"""Docker image registry for sandboxes"""
|
|
34
|
+
|
|
35
|
+
_registry: Dict[Type, SandboxConfig] = {}
|
|
36
|
+
_type_registry: Dict[SandboxType, Type] = {}
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def register(
|
|
40
|
+
cls,
|
|
41
|
+
image_name: str,
|
|
42
|
+
sandbox_type: SandboxType | str,
|
|
43
|
+
resource_limits: Dict = None,
|
|
44
|
+
security_level: str = "medium", # Not used for now
|
|
45
|
+
timeout: int = 300,
|
|
46
|
+
description: str = "",
|
|
47
|
+
environment: Dict = None,
|
|
48
|
+
runtime_config: Optional[Dict] = None,
|
|
49
|
+
):
|
|
50
|
+
"""
|
|
51
|
+
Decorator to register sandbox classes
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
image_name: Docker image name
|
|
55
|
+
sandbox_type: Sandbox type
|
|
56
|
+
resource_limits: Resource limit configuration
|
|
57
|
+
security_level: Security level (low/medium/high)
|
|
58
|
+
timeout: Timeout in seconds
|
|
59
|
+
description: Description
|
|
60
|
+
environment: Environment variables
|
|
61
|
+
runtime_config: runtime configurations
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def decorator(target_class: Type) -> Type:
|
|
65
|
+
if isinstance(sandbox_type, str) and sandbox_type not in [
|
|
66
|
+
x.value for x in SandboxType
|
|
67
|
+
]:
|
|
68
|
+
SandboxType.add_member(
|
|
69
|
+
sandbox_type.upper(),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
_sandbox_type = SandboxType(sandbox_type)
|
|
73
|
+
|
|
74
|
+
config = SandboxConfig(
|
|
75
|
+
image_name=image_name,
|
|
76
|
+
sandbox_type=_sandbox_type,
|
|
77
|
+
resource_limits=resource_limits or {},
|
|
78
|
+
security_level=security_level,
|
|
79
|
+
timeout=timeout,
|
|
80
|
+
description=description,
|
|
81
|
+
environment=environment,
|
|
82
|
+
runtime_config=runtime_config,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
cls._registry[target_class] = config
|
|
86
|
+
cls._type_registry[_sandbox_type] = target_class
|
|
87
|
+
|
|
88
|
+
return target_class
|
|
89
|
+
|
|
90
|
+
return decorator
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def get_config(cls, target_class: Type) -> Optional[SandboxConfig]:
|
|
94
|
+
"""Get the sandbox configuration for a class"""
|
|
95
|
+
return cls._registry.get(target_class)
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def get_image(cls, target_class: Type) -> Optional[str]:
|
|
99
|
+
"""Get the Docker image name for a class"""
|
|
100
|
+
config = cls.get_config(target_class)
|
|
101
|
+
return config.image_name if config else None
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def get_classes_by_type(cls, sandbox_type: SandboxType | str):
|
|
105
|
+
"""Get all related classes by sandbox type"""
|
|
106
|
+
sandbox_type = SandboxType(sandbox_type)
|
|
107
|
+
return cls._type_registry.get(sandbox_type)
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def list_all_sandboxes(cls) -> Dict[Type, SandboxConfig]:
|
|
111
|
+
"""List all registered sandboxes"""
|
|
112
|
+
return cls._registry.copy()
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def get_config_by_type(
|
|
116
|
+
cls,
|
|
117
|
+
sandbox_type: SandboxType | str,
|
|
118
|
+
):
|
|
119
|
+
"""Get all configurations by sandbox type"""
|
|
120
|
+
sandbox_type = SandboxType(sandbox_type)
|
|
121
|
+
cls_ = cls.get_classes_by_type(sandbox_type)
|
|
122
|
+
return cls.get_config(cls_)
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def get_image_by_type(cls, sandbox_type: SandboxType | str):
|
|
126
|
+
"""Get all Docker image names by sandbox type"""
|
|
127
|
+
sandbox_type = SandboxType(sandbox_type)
|
|
128
|
+
cls_ = cls.get_classes_by_type(sandbox_type)
|
|
129
|
+
return cls.get_image(cls_)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from .sandbox_tool import SandboxTool
|
|
3
|
+
from .mcp_tool import MCPTool
|
|
4
|
+
from .function_tool import FunctionTool, function_tool, create_function_tool
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"SandboxTool",
|
|
8
|
+
"MCPTool",
|
|
9
|
+
"FunctionTool",
|
|
10
|
+
"function_tool",
|
|
11
|
+
"create_function_tool",
|
|
12
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from typing import Dict
|
|
3
|
+
|
|
4
|
+
from ..sandbox_tool import SandboxTool
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RunIPythonCellTool(SandboxTool):
|
|
8
|
+
"""Tool for running IPython cells."""
|
|
9
|
+
|
|
10
|
+
_name: str = "run_ipython_cell"
|
|
11
|
+
_sandbox_type: str = "base"
|
|
12
|
+
_tool_type: str = "generic"
|
|
13
|
+
_schema: Dict = {
|
|
14
|
+
"name": "run_ipython_cell",
|
|
15
|
+
"description": "Run an IPython cell.",
|
|
16
|
+
"parameters": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {
|
|
19
|
+
"code": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "IPython code to execute",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
"required": ["code"],
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RunShellCommandTool(SandboxTool):
|
|
30
|
+
"""Tool for running shell commands."""
|
|
31
|
+
|
|
32
|
+
_name: str = "run_shell_command"
|
|
33
|
+
_sandbox_type: str = "base"
|
|
34
|
+
_tool_type: str = "generic"
|
|
35
|
+
_schema: Dict = {
|
|
36
|
+
"name": "run_shell_command",
|
|
37
|
+
"description": "Run a shell command.",
|
|
38
|
+
"parameters": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"properties": {
|
|
41
|
+
"command": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "Shell command to execute",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
"required": ["command"],
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
run_ipython_cell = RunIPythonCellTool()
|
|
52
|
+
run_shell_command = RunShellCommandTool()
|