agentrun-inner-test 0.0.46__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.
- agentrun/__init__.py +325 -0
- agentrun/agent_runtime/__client_async_template.py +466 -0
- agentrun/agent_runtime/__endpoint_async_template.py +345 -0
- agentrun/agent_runtime/__init__.py +53 -0
- agentrun/agent_runtime/__runtime_async_template.py +477 -0
- agentrun/agent_runtime/api/__data_async_template.py +58 -0
- agentrun/agent_runtime/api/__init__.py +6 -0
- agentrun/agent_runtime/api/control.py +1362 -0
- agentrun/agent_runtime/api/data.py +98 -0
- agentrun/agent_runtime/client.py +868 -0
- agentrun/agent_runtime/endpoint.py +649 -0
- agentrun/agent_runtime/model.py +362 -0
- agentrun/agent_runtime/runtime.py +904 -0
- agentrun/credential/__client_async_template.py +177 -0
- agentrun/credential/__credential_async_template.py +216 -0
- agentrun/credential/__init__.py +28 -0
- agentrun/credential/api/__init__.py +5 -0
- agentrun/credential/api/control.py +606 -0
- agentrun/credential/client.py +319 -0
- agentrun/credential/credential.py +381 -0
- agentrun/credential/model.py +248 -0
- agentrun/integration/__init__.py +21 -0
- agentrun/integration/agentscope/__init__.py +12 -0
- agentrun/integration/agentscope/adapter.py +17 -0
- agentrun/integration/agentscope/builtin.py +65 -0
- agentrun/integration/agentscope/message_adapter.py +185 -0
- agentrun/integration/agentscope/model_adapter.py +60 -0
- agentrun/integration/agentscope/tool_adapter.py +59 -0
- agentrun/integration/builtin/__init__.py +16 -0
- agentrun/integration/builtin/model.py +93 -0
- agentrun/integration/builtin/sandbox.py +1234 -0
- agentrun/integration/builtin/toolset.py +47 -0
- agentrun/integration/crewai/__init__.py +12 -0
- agentrun/integration/crewai/adapter.py +9 -0
- agentrun/integration/crewai/builtin.py +65 -0
- agentrun/integration/crewai/model_adapter.py +31 -0
- agentrun/integration/crewai/tool_adapter.py +26 -0
- agentrun/integration/google_adk/__init__.py +12 -0
- agentrun/integration/google_adk/adapter.py +15 -0
- agentrun/integration/google_adk/builtin.py +65 -0
- agentrun/integration/google_adk/message_adapter.py +144 -0
- agentrun/integration/google_adk/model_adapter.py +46 -0
- agentrun/integration/google_adk/tool_adapter.py +235 -0
- agentrun/integration/langchain/__init__.py +30 -0
- agentrun/integration/langchain/adapter.py +15 -0
- agentrun/integration/langchain/builtin.py +71 -0
- agentrun/integration/langchain/message_adapter.py +141 -0
- agentrun/integration/langchain/model_adapter.py +37 -0
- agentrun/integration/langchain/tool_adapter.py +50 -0
- agentrun/integration/langgraph/__init__.py +35 -0
- agentrun/integration/langgraph/adapter.py +20 -0
- agentrun/integration/langgraph/agent_converter.py +1073 -0
- agentrun/integration/langgraph/builtin.py +65 -0
- agentrun/integration/pydantic_ai/__init__.py +12 -0
- agentrun/integration/pydantic_ai/adapter.py +13 -0
- agentrun/integration/pydantic_ai/builtin.py +65 -0
- agentrun/integration/pydantic_ai/model_adapter.py +44 -0
- agentrun/integration/pydantic_ai/tool_adapter.py +19 -0
- agentrun/integration/utils/__init__.py +112 -0
- agentrun/integration/utils/adapter.py +560 -0
- agentrun/integration/utils/canonical.py +164 -0
- agentrun/integration/utils/converter.py +134 -0
- agentrun/integration/utils/model.py +110 -0
- agentrun/integration/utils/tool.py +1759 -0
- agentrun/model/__client_async_template.py +357 -0
- agentrun/model/__init__.py +57 -0
- agentrun/model/__model_proxy_async_template.py +270 -0
- agentrun/model/__model_service_async_template.py +267 -0
- agentrun/model/api/__init__.py +6 -0
- agentrun/model/api/control.py +1173 -0
- agentrun/model/api/data.py +196 -0
- agentrun/model/client.py +674 -0
- agentrun/model/model.py +235 -0
- agentrun/model/model_proxy.py +439 -0
- agentrun/model/model_service.py +438 -0
- agentrun/sandbox/__aio_sandbox_async_template.py +523 -0
- agentrun/sandbox/__browser_sandbox_async_template.py +110 -0
- agentrun/sandbox/__client_async_template.py +491 -0
- agentrun/sandbox/__code_interpreter_sandbox_async_template.py +463 -0
- agentrun/sandbox/__init__.py +69 -0
- agentrun/sandbox/__sandbox_async_template.py +463 -0
- agentrun/sandbox/__template_async_template.py +152 -0
- agentrun/sandbox/aio_sandbox.py +905 -0
- agentrun/sandbox/api/__aio_data_async_template.py +335 -0
- agentrun/sandbox/api/__browser_data_async_template.py +140 -0
- agentrun/sandbox/api/__code_interpreter_data_async_template.py +206 -0
- agentrun/sandbox/api/__init__.py +19 -0
- agentrun/sandbox/api/__sandbox_data_async_template.py +107 -0
- agentrun/sandbox/api/aio_data.py +551 -0
- agentrun/sandbox/api/browser_data.py +172 -0
- agentrun/sandbox/api/code_interpreter_data.py +396 -0
- agentrun/sandbox/api/control.py +1051 -0
- agentrun/sandbox/api/playwright_async.py +492 -0
- agentrun/sandbox/api/playwright_sync.py +492 -0
- agentrun/sandbox/api/sandbox_data.py +154 -0
- agentrun/sandbox/browser_sandbox.py +185 -0
- agentrun/sandbox/client.py +925 -0
- agentrun/sandbox/code_interpreter_sandbox.py +823 -0
- agentrun/sandbox/model.py +397 -0
- agentrun/sandbox/sandbox.py +848 -0
- agentrun/sandbox/template.py +217 -0
- agentrun/server/__init__.py +191 -0
- agentrun/server/agui_normalizer.py +180 -0
- agentrun/server/agui_protocol.py +797 -0
- agentrun/server/invoker.py +309 -0
- agentrun/server/model.py +427 -0
- agentrun/server/openai_protocol.py +535 -0
- agentrun/server/protocol.py +140 -0
- agentrun/server/server.py +208 -0
- agentrun/toolset/__client_async_template.py +62 -0
- agentrun/toolset/__init__.py +51 -0
- agentrun/toolset/__toolset_async_template.py +204 -0
- agentrun/toolset/api/__init__.py +17 -0
- agentrun/toolset/api/control.py +262 -0
- agentrun/toolset/api/mcp.py +100 -0
- agentrun/toolset/api/openapi.py +1251 -0
- agentrun/toolset/client.py +102 -0
- agentrun/toolset/model.py +321 -0
- agentrun/toolset/toolset.py +270 -0
- agentrun/utils/__data_api_async_template.py +720 -0
- agentrun/utils/__init__.py +5 -0
- agentrun/utils/__resource_async_template.py +158 -0
- agentrun/utils/config.py +258 -0
- agentrun/utils/control_api.py +78 -0
- agentrun/utils/data_api.py +1120 -0
- agentrun/utils/exception.py +151 -0
- agentrun/utils/helper.py +108 -0
- agentrun/utils/log.py +77 -0
- agentrun/utils/model.py +168 -0
- agentrun/utils/resource.py +291 -0
- agentrun_inner_test-0.0.46.dist-info/METADATA +263 -0
- agentrun_inner_test-0.0.46.dist-info/RECORD +135 -0
- agentrun_inner_test-0.0.46.dist-info/WHEEL +5 -0
- agentrun_inner_test-0.0.46.dist-info/licenses/LICENSE +201 -0
- agentrun_inner_test-0.0.46.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1234 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import threading
|
|
5
|
+
from typing import Any, Callable, Dict, Optional
|
|
6
|
+
|
|
7
|
+
from agentrun.integration.utils.tool import CommonToolSet, tool
|
|
8
|
+
from agentrun.sandbox import Sandbox, TemplateType
|
|
9
|
+
from agentrun.sandbox.browser_sandbox import BrowserSandbox
|
|
10
|
+
from agentrun.sandbox.client import SandboxClient
|
|
11
|
+
from agentrun.sandbox.code_interpreter_sandbox import CodeInterpreterSandbox
|
|
12
|
+
from agentrun.utils.config import Config
|
|
13
|
+
from agentrun.utils.log import logger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SandboxToolSet(CommonToolSet):
|
|
17
|
+
"""沙箱工具集基类
|
|
18
|
+
|
|
19
|
+
提供沙箱生命周期管理和工具执行的基础设施。
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
template_name: str,
|
|
25
|
+
template_type: TemplateType,
|
|
26
|
+
*,
|
|
27
|
+
sandbox_idle_timeout_seconds: int,
|
|
28
|
+
config: Optional[Config],
|
|
29
|
+
):
|
|
30
|
+
super().__init__()
|
|
31
|
+
|
|
32
|
+
self.config = config
|
|
33
|
+
self.client = SandboxClient(config)
|
|
34
|
+
self.lock = threading.Lock()
|
|
35
|
+
|
|
36
|
+
self.template_name = template_name
|
|
37
|
+
self.template_type = template_type
|
|
38
|
+
self.sandbox_idle_timeout_seconds = sandbox_idle_timeout_seconds
|
|
39
|
+
|
|
40
|
+
self.sandbox: Optional[Sandbox] = None
|
|
41
|
+
self.sandbox_id = ""
|
|
42
|
+
|
|
43
|
+
def close(self):
|
|
44
|
+
"""关闭并释放沙箱资源"""
|
|
45
|
+
if self.sandbox:
|
|
46
|
+
try:
|
|
47
|
+
self.sandbox.stop()
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.debug("delete sandbox failed, due to %s", e)
|
|
50
|
+
|
|
51
|
+
def _ensure_sandbox(self):
|
|
52
|
+
"""确保沙箱实例存在,如果不存在则创建"""
|
|
53
|
+
if self.sandbox is not None:
|
|
54
|
+
return self.sandbox
|
|
55
|
+
|
|
56
|
+
with self.lock:
|
|
57
|
+
if self.sandbox is None:
|
|
58
|
+
self.sandbox = Sandbox.create(
|
|
59
|
+
template_type=self.template_type,
|
|
60
|
+
template_name=self.template_name,
|
|
61
|
+
sandbox_idle_timeout_seconds=self.sandbox_idle_timeout_seconds,
|
|
62
|
+
config=self.config,
|
|
63
|
+
)
|
|
64
|
+
self.sandbox_id = self.sandbox.sandbox_id
|
|
65
|
+
self.sandbox.__enter__()
|
|
66
|
+
|
|
67
|
+
return self.sandbox
|
|
68
|
+
|
|
69
|
+
def _run_in_sandbox(self, callback: Callable[[Sandbox], Any]):
|
|
70
|
+
"""在沙箱中执行操作,失败时自动重试"""
|
|
71
|
+
sb = self._ensure_sandbox()
|
|
72
|
+
try:
|
|
73
|
+
return callback(sb)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
try:
|
|
76
|
+
logger.debug(
|
|
77
|
+
"run in sandbox failed, due to %s, try to re-create"
|
|
78
|
+
" sandbox",
|
|
79
|
+
e,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self.sandbox = None
|
|
83
|
+
sb = self._ensure_sandbox()
|
|
84
|
+
return callback(sb)
|
|
85
|
+
except Exception as e2:
|
|
86
|
+
logger.debug("re-created sandbox run failed, due to %s", e2)
|
|
87
|
+
return {"error": f"{e!s}"}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class CodeInterpreterToolSet(SandboxToolSet):
|
|
91
|
+
"""代码解释器沙箱工具集 / Code Interpreter Sandbox ToolSet
|
|
92
|
+
|
|
93
|
+
提供代码执行、文件操作、进程管理等功能,兼容官方 MCP 工具接口。
|
|
94
|
+
Provides code execution, file operations, and process management capabilities,
|
|
95
|
+
compatible with official MCP tool interfaces.
|
|
96
|
+
|
|
97
|
+
功能分类 / Feature Categories:
|
|
98
|
+
- 健康检查 / Health Check: health
|
|
99
|
+
- 代码执行 / Code Execution: run_code
|
|
100
|
+
- 上下文管理 / Context Management: list_contexts, create_context,
|
|
101
|
+
get_context, delete_context
|
|
102
|
+
- 文件操作 / File Operations: read_file, write_file
|
|
103
|
+
- 文件系统 / File System: file_system_list, file_system_stat,
|
|
104
|
+
file_system_mkdir, file_system_move, file_system_remove
|
|
105
|
+
- 进程管理 / Process Management: process_exec_cmd, process_list,
|
|
106
|
+
process_stat, process_kill
|
|
107
|
+
|
|
108
|
+
使用指南 / Usage Guide:
|
|
109
|
+
============================================================
|
|
110
|
+
|
|
111
|
+
## 代码执行最佳实践 / Code Execution Best Practices
|
|
112
|
+
|
|
113
|
+
1. **简单代码执行 / Simple Code Execution**:
|
|
114
|
+
- 使用 `run_code` 执行一次性代码
|
|
115
|
+
- Use `run_code` for one-time code execution
|
|
116
|
+
- 代码执行后上下文会自动清理
|
|
117
|
+
- Context is automatically cleaned up after execution
|
|
118
|
+
|
|
119
|
+
2. **有状态代码执行 / Stateful Code Execution**:
|
|
120
|
+
- 先使用 `create_context` 创建上下文
|
|
121
|
+
- First create a context using `create_context`
|
|
122
|
+
- 使用 `run_code` 并传入 `context_id` 在同一上下文中执行多段代码
|
|
123
|
+
- Use `run_code` with `context_id` to execute multiple code segments
|
|
124
|
+
- 变量和导入会在同一上下文中保持
|
|
125
|
+
- Variables and imports persist within the same context
|
|
126
|
+
- 完成后使用 `delete_context` 清理
|
|
127
|
+
- Clean up with `delete_context` when done
|
|
128
|
+
|
|
129
|
+
3. **错误处理 / Error Handling**:
|
|
130
|
+
- 检查返回结果中的 `stderr` 和 `exit_code`
|
|
131
|
+
- Check `stderr` and `exit_code` in the response
|
|
132
|
+
- `exit_code` 为 0 表示执行成功
|
|
133
|
+
- `exit_code` of 0 indicates successful execution
|
|
134
|
+
|
|
135
|
+
## 文件操作指南 / File Operations Guide
|
|
136
|
+
|
|
137
|
+
1. **读写文件 / Read/Write Files**:
|
|
138
|
+
- 使用 `read_file` 读取文件内容
|
|
139
|
+
- Use `read_file` to read file contents
|
|
140
|
+
- 使用 `write_file` 写入文件,支持指定编码和权限
|
|
141
|
+
- Use `write_file` to write files with encoding and permissions
|
|
142
|
+
|
|
143
|
+
2. **目录操作 / Directory Operations**:
|
|
144
|
+
- 使用 `file_system_list` 列出目录内容
|
|
145
|
+
- Use `file_system_list` to list directory contents
|
|
146
|
+
- 使用 `file_system_mkdir` 创建目录
|
|
147
|
+
- Use `file_system_mkdir` to create directories
|
|
148
|
+
- 使用 `file_system_move` 移动或重命名
|
|
149
|
+
- Use `file_system_move` to move or rename
|
|
150
|
+
|
|
151
|
+
## 进程管理指南 / Process Management Guide
|
|
152
|
+
|
|
153
|
+
1. **执行命令 / Execute Commands**:
|
|
154
|
+
- 使用 `process_exec_cmd` 执行 Shell 命令
|
|
155
|
+
- Use `process_exec_cmd` to execute shell commands
|
|
156
|
+
- 适合运行系统工具、安装包等操作
|
|
157
|
+
- Suitable for running system tools, installing packages, etc.
|
|
158
|
+
|
|
159
|
+
2. **进程监控 / Process Monitoring**:
|
|
160
|
+
- 使用 `process_list` 查看所有运行中的进程
|
|
161
|
+
- Use `process_list` to view all running processes
|
|
162
|
+
- 使用 `process_kill` 终止不需要的进程
|
|
163
|
+
- Use `process_kill` to terminate unwanted processes
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
def __init__(
|
|
167
|
+
self,
|
|
168
|
+
template_name: str,
|
|
169
|
+
config: Optional[Config],
|
|
170
|
+
sandbox_idle_timeout_seconds: int,
|
|
171
|
+
) -> None:
|
|
172
|
+
super().__init__(
|
|
173
|
+
template_name=template_name,
|
|
174
|
+
template_type=TemplateType.CODE_INTERPRETER,
|
|
175
|
+
sandbox_idle_timeout_seconds=sandbox_idle_timeout_seconds,
|
|
176
|
+
config=config,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# ==================== 健康检查 / Health Check ====================
|
|
180
|
+
|
|
181
|
+
@tool(
|
|
182
|
+
name="health",
|
|
183
|
+
description=(
|
|
184
|
+
"Check the health status of the code interpreter sandbox. Returns"
|
|
185
|
+
" status='ok' if the sandbox is running normally. Recommended to"
|
|
186
|
+
" call before other operations to confirm sandbox readiness."
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
def check_health(self) -> Dict[str, Any]:
|
|
190
|
+
"""检查沙箱健康状态 / Check sandbox health status"""
|
|
191
|
+
|
|
192
|
+
def inner(sb: Sandbox):
|
|
193
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
194
|
+
return sb.check_health()
|
|
195
|
+
|
|
196
|
+
return self._run_in_sandbox(inner)
|
|
197
|
+
|
|
198
|
+
# ==================== 代码执行 / Code Execution ====================
|
|
199
|
+
|
|
200
|
+
@tool(
|
|
201
|
+
name="run_code",
|
|
202
|
+
description=(
|
|
203
|
+
"Execute code in a secure isolated sandbox environment. Supports"
|
|
204
|
+
" Python and JavaScript languages. Can specify context_id to"
|
|
205
|
+
" execute in an existing context, preserving variable state."
|
|
206
|
+
" Returns stdout, stderr, and exit_code."
|
|
207
|
+
),
|
|
208
|
+
)
|
|
209
|
+
def run_code(
|
|
210
|
+
self,
|
|
211
|
+
code: str,
|
|
212
|
+
language: str = "python",
|
|
213
|
+
timeout: int = 60,
|
|
214
|
+
context_id: Optional[str] = None,
|
|
215
|
+
) -> Dict[str, Any]:
|
|
216
|
+
"""执行代码 / Execute code"""
|
|
217
|
+
|
|
218
|
+
def inner(sb: Sandbox):
|
|
219
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
220
|
+
if context_id:
|
|
221
|
+
result = sb.context.execute(
|
|
222
|
+
code=code, context_id=context_id, timeout=timeout
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
with sb.context.create() as ctx:
|
|
226
|
+
try:
|
|
227
|
+
result = ctx.execute(code=code, timeout=timeout)
|
|
228
|
+
finally:
|
|
229
|
+
try:
|
|
230
|
+
ctx.delete()
|
|
231
|
+
except Exception:
|
|
232
|
+
pass
|
|
233
|
+
return {
|
|
234
|
+
"stdout": result.get("stdout", ""),
|
|
235
|
+
"stderr": result.get("stderr", ""),
|
|
236
|
+
"exit_code": result.get("exitCode", 0),
|
|
237
|
+
"result": result,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return self._run_in_sandbox(inner)
|
|
241
|
+
|
|
242
|
+
# 保留旧的 execute_code 作为别名
|
|
243
|
+
@tool(
|
|
244
|
+
name="execute_code",
|
|
245
|
+
description=(
|
|
246
|
+
"Execute code in sandbox (alias for run_code, for backward"
|
|
247
|
+
" compatibility)."
|
|
248
|
+
),
|
|
249
|
+
)
|
|
250
|
+
def execute_code(
|
|
251
|
+
self,
|
|
252
|
+
code: str,
|
|
253
|
+
language: str = "python",
|
|
254
|
+
timeout: int = 60,
|
|
255
|
+
) -> Dict[str, Any]:
|
|
256
|
+
"""执行代码(run_code 的别名)/ Execute code (alias for run_code)"""
|
|
257
|
+
return self.run_code(code=code, language=language, timeout=timeout)
|
|
258
|
+
|
|
259
|
+
# ==================== 上下文管理 / Context Management ====================
|
|
260
|
+
|
|
261
|
+
@tool(
|
|
262
|
+
name="list_contexts",
|
|
263
|
+
description=(
|
|
264
|
+
"List all created execution contexts. Contexts preserve code"
|
|
265
|
+
" execution state like variables and imported modules."
|
|
266
|
+
),
|
|
267
|
+
)
|
|
268
|
+
def list_contexts(self) -> Dict[str, Any]:
|
|
269
|
+
"""列出所有执行上下文 / List all execution contexts"""
|
|
270
|
+
|
|
271
|
+
def inner(sb: Sandbox):
|
|
272
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
273
|
+
contexts = sb.context.list()
|
|
274
|
+
return {"contexts": contexts}
|
|
275
|
+
|
|
276
|
+
return self._run_in_sandbox(inner)
|
|
277
|
+
|
|
278
|
+
@tool(
|
|
279
|
+
name="create_context",
|
|
280
|
+
description=(
|
|
281
|
+
"Create a new execution context for stateful code execution. Code"
|
|
282
|
+
" executed in the same context can share variables and imports."
|
|
283
|
+
" Returns context_id for subsequent run_code calls. Call"
|
|
284
|
+
" delete_context to release resources when done."
|
|
285
|
+
),
|
|
286
|
+
)
|
|
287
|
+
def create_context(
|
|
288
|
+
self,
|
|
289
|
+
language: str = "python",
|
|
290
|
+
cwd: str = "/home/user",
|
|
291
|
+
) -> Dict[str, Any]:
|
|
292
|
+
"""创建新的执行上下文 / Create a new execution context"""
|
|
293
|
+
|
|
294
|
+
def inner(sb: Sandbox):
|
|
295
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
296
|
+
ctx = sb.context.create(language=language, cwd=cwd)
|
|
297
|
+
return {
|
|
298
|
+
"context_id": ctx.context_id,
|
|
299
|
+
"language": ctx._language,
|
|
300
|
+
"cwd": ctx._cwd,
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return self._run_in_sandbox(inner)
|
|
304
|
+
|
|
305
|
+
@tool(
|
|
306
|
+
name="get_context",
|
|
307
|
+
description=(
|
|
308
|
+
"Get details of a specific execution context, including language"
|
|
309
|
+
" and working directory."
|
|
310
|
+
),
|
|
311
|
+
)
|
|
312
|
+
def get_context(self, context_id: str) -> Dict[str, Any]:
|
|
313
|
+
"""获取上下文详情 / Get context details"""
|
|
314
|
+
|
|
315
|
+
def inner(sb: Sandbox):
|
|
316
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
317
|
+
ctx = sb.context.get(context_id=context_id)
|
|
318
|
+
return {
|
|
319
|
+
"context_id": ctx.context_id,
|
|
320
|
+
"language": ctx._language,
|
|
321
|
+
"cwd": ctx._cwd,
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return self._run_in_sandbox(inner)
|
|
325
|
+
|
|
326
|
+
@tool(
|
|
327
|
+
name="delete_context",
|
|
328
|
+
description=(
|
|
329
|
+
"Delete a specific execution context and release related resources."
|
|
330
|
+
" All variables and state in the context will be lost after"
|
|
331
|
+
" deletion."
|
|
332
|
+
),
|
|
333
|
+
)
|
|
334
|
+
def delete_context(self, context_id: str) -> Dict[str, Any]:
|
|
335
|
+
"""删除执行上下文 / Delete execution context"""
|
|
336
|
+
|
|
337
|
+
def inner(sb: Sandbox):
|
|
338
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
339
|
+
result = sb.context.delete(context_id=context_id)
|
|
340
|
+
return {"success": True, "result": result}
|
|
341
|
+
|
|
342
|
+
return self._run_in_sandbox(inner)
|
|
343
|
+
|
|
344
|
+
# ==================== 文件操作 / File Operations ====================
|
|
345
|
+
|
|
346
|
+
@tool(
|
|
347
|
+
name="read_file",
|
|
348
|
+
description=(
|
|
349
|
+
"Read the content of a file at the specified path in the sandbox."
|
|
350
|
+
" Returns the text content. Suitable for reading code files,"
|
|
351
|
+
" configs, logs, etc."
|
|
352
|
+
),
|
|
353
|
+
)
|
|
354
|
+
def read_file(self, path: str) -> Dict[str, Any]:
|
|
355
|
+
"""读取文件内容 / Read file content"""
|
|
356
|
+
|
|
357
|
+
def inner(sb: Sandbox):
|
|
358
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
359
|
+
content = sb.file.read(path=path)
|
|
360
|
+
return {"path": path, "content": content}
|
|
361
|
+
|
|
362
|
+
return self._run_in_sandbox(inner)
|
|
363
|
+
|
|
364
|
+
@tool(
|
|
365
|
+
name="write_file",
|
|
366
|
+
description=(
|
|
367
|
+
"Write content to a file at the specified path in the sandbox."
|
|
368
|
+
" Creates the file automatically if it doesn't exist, including"
|
|
369
|
+
" parent directories. Can specify file permission mode and"
|
|
370
|
+
" encoding."
|
|
371
|
+
),
|
|
372
|
+
)
|
|
373
|
+
def write_file(
|
|
374
|
+
self,
|
|
375
|
+
path: str,
|
|
376
|
+
content: str,
|
|
377
|
+
mode: str = "644",
|
|
378
|
+
encoding: str = "utf-8",
|
|
379
|
+
) -> Dict[str, Any]:
|
|
380
|
+
"""写入文件内容 / Write file content"""
|
|
381
|
+
|
|
382
|
+
def inner(sb: Sandbox):
|
|
383
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
384
|
+
result = sb.file.write(
|
|
385
|
+
path=path, content=content, mode=mode, encoding=encoding
|
|
386
|
+
)
|
|
387
|
+
return {"path": path, "success": True, "result": result}
|
|
388
|
+
|
|
389
|
+
return self._run_in_sandbox(inner)
|
|
390
|
+
|
|
391
|
+
# ==================== 文件系统操作 / File System Operations ====================
|
|
392
|
+
|
|
393
|
+
@tool(
|
|
394
|
+
name="file_system_list",
|
|
395
|
+
description=(
|
|
396
|
+
"List the contents of a directory in the sandbox, including files"
|
|
397
|
+
" and subdirectories. Can specify traversal depth to get nested"
|
|
398
|
+
" directory structure. Returns name, type, size, etc. for each"
|
|
399
|
+
" entry."
|
|
400
|
+
),
|
|
401
|
+
)
|
|
402
|
+
def file_system_list(
|
|
403
|
+
self, path: str = "/", depth: Optional[int] = None
|
|
404
|
+
) -> Dict[str, Any]:
|
|
405
|
+
"""列出目录内容 / List directory contents"""
|
|
406
|
+
|
|
407
|
+
def inner(sb: Sandbox):
|
|
408
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
409
|
+
entries = sb.file_system.list(path=path, depth=depth)
|
|
410
|
+
return {"path": path, "entries": entries}
|
|
411
|
+
|
|
412
|
+
return self._run_in_sandbox(inner)
|
|
413
|
+
|
|
414
|
+
# 保留旧的 list_directory 作为别名
|
|
415
|
+
@tool(
|
|
416
|
+
name="list_directory",
|
|
417
|
+
description="List directory contents (alias for file_system_list).",
|
|
418
|
+
)
|
|
419
|
+
def list_directory(self, path: str = "/") -> Dict[str, Any]:
|
|
420
|
+
"""列出目录内容 / List directory contents"""
|
|
421
|
+
return self.file_system_list(path=path)
|
|
422
|
+
|
|
423
|
+
@tool(
|
|
424
|
+
name="file_system_stat",
|
|
425
|
+
description=(
|
|
426
|
+
"Get detailed status information of a file or directory. Includes"
|
|
427
|
+
" size, permissions, modification time, access time, and other"
|
|
428
|
+
" metadata. Can be used to check file existence, get file size,"
|
|
429
|
+
" etc."
|
|
430
|
+
),
|
|
431
|
+
)
|
|
432
|
+
def file_system_stat(self, path: str) -> Dict[str, Any]:
|
|
433
|
+
"""获取文件/目录状态 / Get file/directory status"""
|
|
434
|
+
|
|
435
|
+
def inner(sb: Sandbox):
|
|
436
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
437
|
+
stat_info = sb.file_system.stat(path=path)
|
|
438
|
+
return {"path": path, "stat": stat_info}
|
|
439
|
+
|
|
440
|
+
return self._run_in_sandbox(inner)
|
|
441
|
+
|
|
442
|
+
@tool(
|
|
443
|
+
name="file_system_mkdir",
|
|
444
|
+
description=(
|
|
445
|
+
"Create a directory in the sandbox. By default, automatically"
|
|
446
|
+
" creates all necessary parent directories (like mkdir -p). Can"
|
|
447
|
+
" specify directory permission mode."
|
|
448
|
+
),
|
|
449
|
+
)
|
|
450
|
+
def file_system_mkdir(
|
|
451
|
+
self,
|
|
452
|
+
path: str,
|
|
453
|
+
parents: bool = True,
|
|
454
|
+
mode: str = "0755",
|
|
455
|
+
) -> Dict[str, Any]:
|
|
456
|
+
"""创建目录 / Create directory"""
|
|
457
|
+
|
|
458
|
+
def inner(sb: Sandbox):
|
|
459
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
460
|
+
result = sb.file_system.mkdir(path=path, parents=parents, mode=mode)
|
|
461
|
+
return {"path": path, "success": True, "result": result}
|
|
462
|
+
|
|
463
|
+
return self._run_in_sandbox(inner)
|
|
464
|
+
|
|
465
|
+
@tool(
|
|
466
|
+
name="file_system_move",
|
|
467
|
+
description=(
|
|
468
|
+
"Move or rename a file/directory. Can rename within the same"
|
|
469
|
+
" directory or move to a different directory."
|
|
470
|
+
),
|
|
471
|
+
)
|
|
472
|
+
def file_system_move(self, source: str, destination: str) -> Dict[str, Any]:
|
|
473
|
+
"""移动/重命名文件或目录 / Move/rename file or directory"""
|
|
474
|
+
|
|
475
|
+
def inner(sb: Sandbox):
|
|
476
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
477
|
+
result = sb.file_system.move(source=source, destination=destination)
|
|
478
|
+
return {
|
|
479
|
+
"source": source,
|
|
480
|
+
"destination": destination,
|
|
481
|
+
"success": True,
|
|
482
|
+
"result": result,
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return self._run_in_sandbox(inner)
|
|
486
|
+
|
|
487
|
+
@tool(
|
|
488
|
+
name="file_system_remove",
|
|
489
|
+
description=(
|
|
490
|
+
"Delete a file or directory. Recursively deletes all contents when"
|
|
491
|
+
" removing a directory. Use with caution."
|
|
492
|
+
),
|
|
493
|
+
)
|
|
494
|
+
def file_system_remove(self, path: str) -> Dict[str, Any]:
|
|
495
|
+
"""删除文件或目录 / Delete file or directory"""
|
|
496
|
+
|
|
497
|
+
def inner(sb: Sandbox):
|
|
498
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
499
|
+
result = sb.file_system.remove(path=path)
|
|
500
|
+
return {"path": path, "success": True, "result": result}
|
|
501
|
+
|
|
502
|
+
return self._run_in_sandbox(inner)
|
|
503
|
+
|
|
504
|
+
# ==================== 进程管理 / Process Management ====================
|
|
505
|
+
|
|
506
|
+
@tool(
|
|
507
|
+
name="process_exec_cmd",
|
|
508
|
+
description=(
|
|
509
|
+
"Execute a shell command in the sandbox. Suitable for running"
|
|
510
|
+
" system tools, installing packages, executing scripts, etc."
|
|
511
|
+
" Returns stdout, stderr, and exit code. Can specify working"
|
|
512
|
+
" directory and timeout."
|
|
513
|
+
),
|
|
514
|
+
)
|
|
515
|
+
def process_exec_cmd(
|
|
516
|
+
self,
|
|
517
|
+
command: str,
|
|
518
|
+
cwd: str = "/home/user",
|
|
519
|
+
timeout: int = 30,
|
|
520
|
+
) -> Dict[str, Any]:
|
|
521
|
+
"""执行命令 / Execute command"""
|
|
522
|
+
|
|
523
|
+
def inner(sb: Sandbox):
|
|
524
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
525
|
+
result = sb.process.cmd(command=command, cwd=cwd, timeout=timeout)
|
|
526
|
+
return {
|
|
527
|
+
"command": command,
|
|
528
|
+
"stdout": result.get("stdout", ""),
|
|
529
|
+
"stderr": result.get("stderr", ""),
|
|
530
|
+
"exit_code": result.get("exitCode", 0),
|
|
531
|
+
"result": result,
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return self._run_in_sandbox(inner)
|
|
535
|
+
|
|
536
|
+
@tool(
|
|
537
|
+
name="process_list",
|
|
538
|
+
description=(
|
|
539
|
+
"List all running processes in the sandbox. Returns PID, name,"
|
|
540
|
+
" status, etc. for each process. Can be used to monitor resource"
|
|
541
|
+
" usage or find processes to terminate."
|
|
542
|
+
),
|
|
543
|
+
)
|
|
544
|
+
def process_list(self) -> Dict[str, Any]:
|
|
545
|
+
"""列出所有进程 / List all processes"""
|
|
546
|
+
|
|
547
|
+
def inner(sb: Sandbox):
|
|
548
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
549
|
+
processes = sb.process.list()
|
|
550
|
+
return {"processes": processes}
|
|
551
|
+
|
|
552
|
+
return self._run_in_sandbox(inner)
|
|
553
|
+
|
|
554
|
+
@tool(
|
|
555
|
+
name="process_stat",
|
|
556
|
+
description=(
|
|
557
|
+
"Get detailed status information of a specific process. "
|
|
558
|
+
"Includes CPU usage, memory consumption, runtime, etc."
|
|
559
|
+
),
|
|
560
|
+
)
|
|
561
|
+
def process_stat(self, pid: str) -> Dict[str, Any]:
|
|
562
|
+
"""获取进程状态 / Get process status"""
|
|
563
|
+
|
|
564
|
+
def inner(sb: Sandbox):
|
|
565
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
566
|
+
process_info = sb.process.get(pid=pid)
|
|
567
|
+
return {"pid": pid, "process": process_info}
|
|
568
|
+
|
|
569
|
+
return self._run_in_sandbox(inner)
|
|
570
|
+
|
|
571
|
+
@tool(
|
|
572
|
+
name="process_kill",
|
|
573
|
+
description=(
|
|
574
|
+
"Terminate a specific process. "
|
|
575
|
+
"Used to stop long-running or unresponsive processes. "
|
|
576
|
+
"Make sure not to terminate critical system processes."
|
|
577
|
+
),
|
|
578
|
+
)
|
|
579
|
+
def process_kill(self, pid: str) -> Dict[str, Any]:
|
|
580
|
+
"""终止进程 / Terminate process"""
|
|
581
|
+
|
|
582
|
+
def inner(sb: Sandbox):
|
|
583
|
+
assert isinstance(sb, CodeInterpreterSandbox)
|
|
584
|
+
result = sb.process.kill(pid=pid)
|
|
585
|
+
return {"pid": pid, "success": True, "result": result}
|
|
586
|
+
|
|
587
|
+
return self._run_in_sandbox(inner)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
class BrowserToolSet(SandboxToolSet):
|
|
591
|
+
"""浏览器沙箱工具集 / Browser Sandbox ToolSet
|
|
592
|
+
|
|
593
|
+
提供浏览器自动化操作功能,兼容官方 MCP 工具接口。
|
|
594
|
+
Provides browser automation capabilities, compatible with official MCP tool interfaces.
|
|
595
|
+
|
|
596
|
+
功能分类 / Feature Categories:
|
|
597
|
+
- 健康检查 / Health Check: health
|
|
598
|
+
- 导航 / Navigation: browser_navigate, browser_navigate_back, browser_go_forward
|
|
599
|
+
- 页面交互 / Page Interaction: browser_click, browser_type, browser_fill,
|
|
600
|
+
browser_hover, browser_drag, browser_dblclick
|
|
601
|
+
- 页面信息 / Page Info: browser_snapshot, browser_take_screenshot, browser_get_title
|
|
602
|
+
- 标签页管理 / Tab Management: browser_tabs_list, browser_tabs_new, browser_tabs_select
|
|
603
|
+
- 高级功能 / Advanced: browser_evaluate, browser_wait_for
|
|
604
|
+
|
|
605
|
+
使用指南 / Usage Guide:
|
|
606
|
+
============================================================
|
|
607
|
+
|
|
608
|
+
## 基本操作流程 / Basic Operation Flow
|
|
609
|
+
|
|
610
|
+
1. **导航到目标页面 / Navigate to Target Page**:
|
|
611
|
+
- 使用 `browser_navigate` 访问目标 URL
|
|
612
|
+
- Use `browser_navigate` to visit the target URL
|
|
613
|
+
- 可以指定等待条件:load, domcontentloaded, networkidle
|
|
614
|
+
- Can specify wait condition: load, domcontentloaded, networkidle
|
|
615
|
+
|
|
616
|
+
2. **获取页面信息 / Get Page Information**:
|
|
617
|
+
- 使用 `browser_snapshot` 获取页面 HTML 结构
|
|
618
|
+
- Use `browser_snapshot` to get page HTML structure
|
|
619
|
+
- 使用 `browser_take_screenshot` 截取页面截图
|
|
620
|
+
- Use `browser_take_screenshot` to capture page screenshot
|
|
621
|
+
|
|
622
|
+
3. **与页面交互 / Interact with Page**:
|
|
623
|
+
- 使用 `browser_click` 点击元素
|
|
624
|
+
- Use `browser_click` to click elements
|
|
625
|
+
- 使用 `browser_fill` 填充表单
|
|
626
|
+
- Use `browser_fill` to fill forms
|
|
627
|
+
- 使用 `browser_type` 逐字符输入(适用于需要触发输入事件的场景)
|
|
628
|
+
- Use `browser_type` for character-by-character input (for triggering input events)
|
|
629
|
+
|
|
630
|
+
## 元素选择器指南 / Selector Guide
|
|
631
|
+
|
|
632
|
+
支持多种选择器类型 / Supports multiple selector types:
|
|
633
|
+
- CSS 选择器 / CSS Selectors: `#id`, `.class`, `tag`, `[attr=value]`
|
|
634
|
+
- 文本选择器 / Text Selectors: `text=Submit`, `text="Exact Match"`
|
|
635
|
+
- XPath: `xpath=//button[@type="submit"]`
|
|
636
|
+
- 组合选择器 / Combined: `div.container >> button.submit`
|
|
637
|
+
|
|
638
|
+
## 常见操作示例 / Common Operation Examples
|
|
639
|
+
|
|
640
|
+
1. **搜索操作 / Search Operation**:
|
|
641
|
+
```
|
|
642
|
+
browser_navigate(url="https://www.bing.com")
|
|
643
|
+
browser_fill(selector='input[name="q"]', value="搜索关键词")
|
|
644
|
+
browser_click(selector='input[type="submit"]')
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
2. **表单填写 / Form Filling**:
|
|
648
|
+
```
|
|
649
|
+
browser_fill(selector='#username', value="user@example.com")
|
|
650
|
+
browser_fill(selector='#password', value="password123")
|
|
651
|
+
browser_click(selector='button[type="submit"]')
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
3. **页面分析 / Page Analysis**:
|
|
655
|
+
```
|
|
656
|
+
browser_snapshot() # 获取 HTML 结构
|
|
657
|
+
browser_take_screenshot(full_page=True) # 截取完整页面
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
## 错误处理建议 / Error Handling Tips
|
|
661
|
+
|
|
662
|
+
- 操作可能因元素未加载而失败,使用 `browser_wait_for` 等待
|
|
663
|
+
- Operations may fail if elements not loaded, use `browser_wait_for` to wait
|
|
664
|
+
- 如果选择器找不到元素,尝试使用更具体或不同的选择器
|
|
665
|
+
- If selector doesn't find element, try more specific or different selectors
|
|
666
|
+
- 使用 `browser_snapshot` 检查页面当前状态
|
|
667
|
+
- Use `browser_snapshot` to check current page state
|
|
668
|
+
|
|
669
|
+
## 多标签页操作 / Multi-Tab Operations
|
|
670
|
+
|
|
671
|
+
- 使用 `browser_tabs_new` 创建新标签页
|
|
672
|
+
- Use `browser_tabs_new` to create new tabs
|
|
673
|
+
- 使用 `browser_tabs_list` 查看所有标签页
|
|
674
|
+
- Use `browser_tabs_list` to view all tabs
|
|
675
|
+
- 使用 `browser_tabs_select` 切换标签页
|
|
676
|
+
- Use `browser_tabs_select` to switch tabs
|
|
677
|
+
"""
|
|
678
|
+
|
|
679
|
+
def __init__(
|
|
680
|
+
self,
|
|
681
|
+
template_name: str,
|
|
682
|
+
config: Optional[Config],
|
|
683
|
+
sandbox_idle_timeout_seconds: int,
|
|
684
|
+
) -> None:
|
|
685
|
+
|
|
686
|
+
super().__init__(
|
|
687
|
+
template_name=template_name,
|
|
688
|
+
template_type=TemplateType.BROWSER,
|
|
689
|
+
sandbox_idle_timeout_seconds=sandbox_idle_timeout_seconds,
|
|
690
|
+
config=config,
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
# ==================== 健康检查 / Health Check ====================
|
|
694
|
+
|
|
695
|
+
@tool(
|
|
696
|
+
name="health",
|
|
697
|
+
description=(
|
|
698
|
+
"Check the health status of the browser sandbox. Returns"
|
|
699
|
+
" status='ok' if the browser is running normally. Recommended to"
|
|
700
|
+
" call before other operations to confirm browser readiness."
|
|
701
|
+
),
|
|
702
|
+
)
|
|
703
|
+
def check_health(self) -> Dict[str, Any]:
|
|
704
|
+
"""检查浏览器沙箱健康状态 / Check browser sandbox health status"""
|
|
705
|
+
|
|
706
|
+
def inner(sb: Sandbox):
|
|
707
|
+
assert isinstance(sb, BrowserSandbox)
|
|
708
|
+
return sb.check_health()
|
|
709
|
+
|
|
710
|
+
return self._run_in_sandbox(inner)
|
|
711
|
+
|
|
712
|
+
# ==================== 导航 / Navigation ====================
|
|
713
|
+
|
|
714
|
+
@tool(
|
|
715
|
+
name="browser_navigate",
|
|
716
|
+
description=(
|
|
717
|
+
"Navigate to the specified URL. This is the first step in browser"
|
|
718
|
+
" automation. Can specify wait condition to ensure page is loaded"
|
|
719
|
+
" before continuing. wait_until options: 'load' (fully loaded),"
|
|
720
|
+
" 'domcontentloaded' (DOM ready), 'networkidle' (network idle),"
|
|
721
|
+
" 'commit' (response received)."
|
|
722
|
+
),
|
|
723
|
+
)
|
|
724
|
+
def browser_navigate(
|
|
725
|
+
self,
|
|
726
|
+
url: str,
|
|
727
|
+
wait_until: str = "load",
|
|
728
|
+
timeout: Optional[float] = None,
|
|
729
|
+
) -> Dict[str, Any]:
|
|
730
|
+
"""导航到 URL / Navigate to URL"""
|
|
731
|
+
|
|
732
|
+
def inner(sb: Sandbox):
|
|
733
|
+
assert isinstance(sb, BrowserSandbox)
|
|
734
|
+
with sb.sync_playwright() as p:
|
|
735
|
+
response = p.goto(url, wait_until=wait_until, timeout=timeout)
|
|
736
|
+
return {
|
|
737
|
+
"url": url,
|
|
738
|
+
"success": True,
|
|
739
|
+
"status": response.status if response else None,
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return self._run_in_sandbox(inner)
|
|
743
|
+
|
|
744
|
+
# 保留旧的 goto 作为别名
|
|
745
|
+
@tool(
|
|
746
|
+
name="goto",
|
|
747
|
+
description=(
|
|
748
|
+
"Navigate to URL (alias for browser_navigate, for backward"
|
|
749
|
+
" compatibility)."
|
|
750
|
+
),
|
|
751
|
+
)
|
|
752
|
+
def goto(self, url: str) -> Dict[str, Any]:
|
|
753
|
+
"""导航到 URL / Navigate to URL"""
|
|
754
|
+
return self.browser_navigate(url=url)
|
|
755
|
+
|
|
756
|
+
@tool(
|
|
757
|
+
name="browser_navigate_back",
|
|
758
|
+
description=(
|
|
759
|
+
"Go back to the previous page, equivalent to clicking the browser's"
|
|
760
|
+
" back button. If there's no history to go back to, the operation"
|
|
761
|
+
" has no effect."
|
|
762
|
+
),
|
|
763
|
+
)
|
|
764
|
+
def browser_navigate_back(
|
|
765
|
+
self,
|
|
766
|
+
wait_until: str = "load",
|
|
767
|
+
timeout: Optional[float] = None,
|
|
768
|
+
) -> Dict[str, Any]:
|
|
769
|
+
"""返回上一页 / Go back to previous page"""
|
|
770
|
+
|
|
771
|
+
def inner(sb: Sandbox):
|
|
772
|
+
assert isinstance(sb, BrowserSandbox)
|
|
773
|
+
with sb.sync_playwright() as p:
|
|
774
|
+
response = p.go_back(wait_until=wait_until, timeout=timeout)
|
|
775
|
+
return {
|
|
776
|
+
"success": True,
|
|
777
|
+
"status": response.status if response else None,
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return self._run_in_sandbox(inner)
|
|
781
|
+
|
|
782
|
+
@tool(
|
|
783
|
+
name="browser_go_forward",
|
|
784
|
+
description=(
|
|
785
|
+
"Go forward to the next page, equivalent to clicking the browser's"
|
|
786
|
+
" forward button. Only effective if a back navigation was"
|
|
787
|
+
" previously performed."
|
|
788
|
+
),
|
|
789
|
+
)
|
|
790
|
+
def browser_go_forward(
|
|
791
|
+
self,
|
|
792
|
+
wait_until: str = "load",
|
|
793
|
+
timeout: Optional[float] = None,
|
|
794
|
+
) -> Dict[str, Any]:
|
|
795
|
+
"""前进到下一页 / Go forward to next page"""
|
|
796
|
+
|
|
797
|
+
def inner(sb: Sandbox):
|
|
798
|
+
assert isinstance(sb, BrowserSandbox)
|
|
799
|
+
with sb.sync_playwright() as p:
|
|
800
|
+
response = p.go_forward(wait_until=wait_until, timeout=timeout)
|
|
801
|
+
return {
|
|
802
|
+
"success": True,
|
|
803
|
+
"status": response.status if response else None,
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return self._run_in_sandbox(inner)
|
|
807
|
+
|
|
808
|
+
# ==================== 页面交互 - 点击/拖拽 / Click/Drag ====================
|
|
809
|
+
|
|
810
|
+
@tool(
|
|
811
|
+
name="browser_click",
|
|
812
|
+
description=(
|
|
813
|
+
"Click an element matching the selector on the page. "
|
|
814
|
+
"Supports CSS selectors, text selectors (text=xxx), XPath, etc. "
|
|
815
|
+
"Can specify mouse button (left/right/middle) and click count. "
|
|
816
|
+
"Automatically scrolls to make the element visible if needed."
|
|
817
|
+
),
|
|
818
|
+
)
|
|
819
|
+
def browser_click(
|
|
820
|
+
self,
|
|
821
|
+
selector: str,
|
|
822
|
+
button: str = "left",
|
|
823
|
+
click_count: int = 1,
|
|
824
|
+
timeout: Optional[float] = None,
|
|
825
|
+
) -> Dict[str, Any]:
|
|
826
|
+
"""点击元素 / Click element"""
|
|
827
|
+
|
|
828
|
+
def inner(sb: Sandbox):
|
|
829
|
+
assert isinstance(sb, BrowserSandbox)
|
|
830
|
+
with sb.sync_playwright() as p:
|
|
831
|
+
p.click(
|
|
832
|
+
selector,
|
|
833
|
+
button=button,
|
|
834
|
+
click_count=click_count,
|
|
835
|
+
timeout=timeout,
|
|
836
|
+
)
|
|
837
|
+
return {"selector": selector, "success": True}
|
|
838
|
+
|
|
839
|
+
return self._run_in_sandbox(inner)
|
|
840
|
+
|
|
841
|
+
# 保留旧的 click 作为别名
|
|
842
|
+
@tool(
|
|
843
|
+
name="click",
|
|
844
|
+
description=(
|
|
845
|
+
"Click element (alias for browser_click, for backward"
|
|
846
|
+
" compatibility)."
|
|
847
|
+
),
|
|
848
|
+
)
|
|
849
|
+
def click(self, selector: str) -> Dict[str, Any]:
|
|
850
|
+
"""点击元素 / Click element"""
|
|
851
|
+
return self.browser_click(selector=selector)
|
|
852
|
+
|
|
853
|
+
@tool(
|
|
854
|
+
name="browser_dblclick",
|
|
855
|
+
description=(
|
|
856
|
+
"Double-click an element matching the selector on the page."
|
|
857
|
+
" Commonly used for opening files, editing text, and other"
|
|
858
|
+
" double-click actions."
|
|
859
|
+
),
|
|
860
|
+
)
|
|
861
|
+
def browser_dblclick(
|
|
862
|
+
self,
|
|
863
|
+
selector: str,
|
|
864
|
+
timeout: Optional[float] = None,
|
|
865
|
+
) -> Dict[str, Any]:
|
|
866
|
+
"""双击元素 / Double-click element"""
|
|
867
|
+
|
|
868
|
+
def inner(sb: Sandbox):
|
|
869
|
+
assert isinstance(sb, BrowserSandbox)
|
|
870
|
+
with sb.sync_playwright() as p:
|
|
871
|
+
p.dblclick(selector, timeout=timeout)
|
|
872
|
+
return {"selector": selector, "success": True}
|
|
873
|
+
|
|
874
|
+
return self._run_in_sandbox(inner)
|
|
875
|
+
|
|
876
|
+
@tool(
|
|
877
|
+
name="browser_drag",
|
|
878
|
+
description=(
|
|
879
|
+
"Drag the source element to the target element position. "
|
|
880
|
+
"Commonly used for drag-and-drop sorting, file upload areas, etc."
|
|
881
|
+
),
|
|
882
|
+
)
|
|
883
|
+
def browser_drag(
|
|
884
|
+
self,
|
|
885
|
+
source_selector: str,
|
|
886
|
+
target_selector: str,
|
|
887
|
+
timeout: Optional[float] = None,
|
|
888
|
+
) -> Dict[str, Any]:
|
|
889
|
+
"""拖拽元素 / Drag element"""
|
|
890
|
+
|
|
891
|
+
def inner(sb: Sandbox):
|
|
892
|
+
assert isinstance(sb, BrowserSandbox)
|
|
893
|
+
with sb.sync_playwright() as p:
|
|
894
|
+
p.drag_and_drop(
|
|
895
|
+
source_selector, target_selector, timeout=timeout
|
|
896
|
+
)
|
|
897
|
+
return {
|
|
898
|
+
"source": source_selector,
|
|
899
|
+
"target": target_selector,
|
|
900
|
+
"success": True,
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
return self._run_in_sandbox(inner)
|
|
904
|
+
|
|
905
|
+
@tool(
|
|
906
|
+
name="browser_hover",
|
|
907
|
+
description=(
|
|
908
|
+
"Hover the mouse over an element matching the selector. Commonly"
|
|
909
|
+
" used to trigger hover menus, tooltips, and other hover effects."
|
|
910
|
+
),
|
|
911
|
+
)
|
|
912
|
+
def browser_hover(
|
|
913
|
+
self,
|
|
914
|
+
selector: str,
|
|
915
|
+
timeout: Optional[float] = None,
|
|
916
|
+
) -> Dict[str, Any]:
|
|
917
|
+
"""鼠标悬停 / Mouse hover"""
|
|
918
|
+
|
|
919
|
+
def inner(sb: Sandbox):
|
|
920
|
+
assert isinstance(sb, BrowserSandbox)
|
|
921
|
+
with sb.sync_playwright() as p:
|
|
922
|
+
p.hover(selector, timeout=timeout)
|
|
923
|
+
return {"selector": selector, "success": True}
|
|
924
|
+
|
|
925
|
+
return self._run_in_sandbox(inner)
|
|
926
|
+
|
|
927
|
+
# ==================== 页面交互 - 输入 / Input ====================
|
|
928
|
+
|
|
929
|
+
@tool(
|
|
930
|
+
name="browser_type",
|
|
931
|
+
description=(
|
|
932
|
+
"Type text character by character in an element matching the"
|
|
933
|
+
" selector. Triggers keydown, keypress, keyup events for each"
|
|
934
|
+
" character. Suitable for scenarios that need input events (like"
|
|
935
|
+
" autocomplete, live search). Use browser_fill if you just need to"
|
|
936
|
+
" fill a value without triggering events."
|
|
937
|
+
),
|
|
938
|
+
)
|
|
939
|
+
def browser_type(
|
|
940
|
+
self,
|
|
941
|
+
selector: str,
|
|
942
|
+
text: str,
|
|
943
|
+
delay: Optional[float] = None,
|
|
944
|
+
timeout: Optional[float] = None,
|
|
945
|
+
) -> Dict[str, Any]:
|
|
946
|
+
"""输入文本 / Type text"""
|
|
947
|
+
|
|
948
|
+
def inner(sb: Sandbox):
|
|
949
|
+
assert isinstance(sb, BrowserSandbox)
|
|
950
|
+
with sb.sync_playwright() as p:
|
|
951
|
+
p.type(selector, text, delay=delay, timeout=timeout)
|
|
952
|
+
return {"selector": selector, "text": text, "success": True}
|
|
953
|
+
|
|
954
|
+
return self._run_in_sandbox(inner)
|
|
955
|
+
|
|
956
|
+
@tool(
|
|
957
|
+
name="browser_fill",
|
|
958
|
+
description=(
|
|
959
|
+
"Fill a form input with a value all at once. Clears existing"
|
|
960
|
+
" content first, then fills the new value. Faster than browser_type"
|
|
961
|
+
" but doesn't trigger character-by-character input events. Suitable"
|
|
962
|
+
" for regular form filling scenarios."
|
|
963
|
+
),
|
|
964
|
+
)
|
|
965
|
+
def browser_fill(
|
|
966
|
+
self,
|
|
967
|
+
selector: str,
|
|
968
|
+
value: str,
|
|
969
|
+
timeout: Optional[float] = None,
|
|
970
|
+
) -> Dict[str, Any]:
|
|
971
|
+
"""填充输入框 / Fill input"""
|
|
972
|
+
|
|
973
|
+
def inner(sb: Sandbox):
|
|
974
|
+
assert isinstance(sb, BrowserSandbox)
|
|
975
|
+
with sb.sync_playwright() as p:
|
|
976
|
+
p.fill(selector, value, timeout=timeout)
|
|
977
|
+
return {"selector": selector, "value": value, "success": True}
|
|
978
|
+
|
|
979
|
+
return self._run_in_sandbox(inner)
|
|
980
|
+
|
|
981
|
+
# 保留旧的 fill 作为别名
|
|
982
|
+
@tool(
|
|
983
|
+
name="fill",
|
|
984
|
+
description=(
|
|
985
|
+
"Fill input (alias for browser_fill, for backward compatibility)."
|
|
986
|
+
),
|
|
987
|
+
)
|
|
988
|
+
def fill(self, selector: str, value: str) -> Dict[str, Any]:
|
|
989
|
+
"""填充输入框 / Fill input"""
|
|
990
|
+
return self.browser_fill(selector=selector, value=value)
|
|
991
|
+
|
|
992
|
+
# ==================== 页面信息 / Page Information ====================
|
|
993
|
+
|
|
994
|
+
@tool(
|
|
995
|
+
name="browser_snapshot",
|
|
996
|
+
description=(
|
|
997
|
+
"Get the HTML snapshot and title of the current page. This is the"
|
|
998
|
+
" preferred method for analyzing page structure, useful for"
|
|
999
|
+
" understanding layout and finding elements. Returns complete HTML"
|
|
1000
|
+
" content for extracting information or determining next steps."
|
|
1001
|
+
),
|
|
1002
|
+
)
|
|
1003
|
+
def browser_snapshot(self) -> Dict[str, Any]:
|
|
1004
|
+
"""获取页面 HTML 快照 / Get page HTML snapshot"""
|
|
1005
|
+
|
|
1006
|
+
def inner(sb: Sandbox):
|
|
1007
|
+
assert isinstance(sb, BrowserSandbox)
|
|
1008
|
+
with sb.sync_playwright() as p:
|
|
1009
|
+
html = p.html_content()
|
|
1010
|
+
title = p.title()
|
|
1011
|
+
return {"html": html, "title": title}
|
|
1012
|
+
|
|
1013
|
+
return self._run_in_sandbox(inner)
|
|
1014
|
+
|
|
1015
|
+
# 保留旧的 html_content 作为别名
|
|
1016
|
+
@tool(
|
|
1017
|
+
name="html_content",
|
|
1018
|
+
description=(
|
|
1019
|
+
"Get page HTML content (alias for browser_snapshot, for backward"
|
|
1020
|
+
" compatibility)."
|
|
1021
|
+
),
|
|
1022
|
+
)
|
|
1023
|
+
def html_content(self) -> Dict[str, Any]:
|
|
1024
|
+
"""获取页面 HTML 内容 / Get page HTML content"""
|
|
1025
|
+
return self.browser_snapshot()
|
|
1026
|
+
|
|
1027
|
+
@tool(
|
|
1028
|
+
name="browser_take_screenshot",
|
|
1029
|
+
description=(
|
|
1030
|
+
"Capture a screenshot of the current page, returns base64 encoded"
|
|
1031
|
+
" image data. Can capture viewport only or full page"
|
|
1032
|
+
" (full_page=True). Supports PNG and JPEG formats. Suitable for"
|
|
1033
|
+
" visual verification or saving page state."
|
|
1034
|
+
),
|
|
1035
|
+
)
|
|
1036
|
+
def browser_take_screenshot(
|
|
1037
|
+
self,
|
|
1038
|
+
full_page: bool = False,
|
|
1039
|
+
type: str = "png",
|
|
1040
|
+
) -> Dict[str, Any]:
|
|
1041
|
+
"""截取页面截图 / Take page screenshot"""
|
|
1042
|
+
|
|
1043
|
+
def inner(sb: Sandbox):
|
|
1044
|
+
assert isinstance(sb, BrowserSandbox)
|
|
1045
|
+
with sb.sync_playwright() as p:
|
|
1046
|
+
screenshot_bytes = p.screenshot(full_page=full_page, type=type)
|
|
1047
|
+
screenshot_base64 = base64.b64encode(screenshot_bytes).decode(
|
|
1048
|
+
"utf-8"
|
|
1049
|
+
)
|
|
1050
|
+
return {
|
|
1051
|
+
"screenshot": screenshot_base64,
|
|
1052
|
+
"format": type,
|
|
1053
|
+
"full_page": full_page,
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
return self._run_in_sandbox(inner)
|
|
1057
|
+
|
|
1058
|
+
@tool(
|
|
1059
|
+
name="browser_get_title",
|
|
1060
|
+
description=(
|
|
1061
|
+
"Get the title of the current page (content of title tag). "
|
|
1062
|
+
"Can be used to verify navigation to the correct page."
|
|
1063
|
+
),
|
|
1064
|
+
)
|
|
1065
|
+
def browser_get_title(self) -> Dict[str, Any]:
|
|
1066
|
+
"""获取页面标题 / Get page title"""
|
|
1067
|
+
|
|
1068
|
+
def inner(sb: Sandbox):
|
|
1069
|
+
assert isinstance(sb, BrowserSandbox)
|
|
1070
|
+
with sb.sync_playwright() as p:
|
|
1071
|
+
title = p.title()
|
|
1072
|
+
return {"title": title}
|
|
1073
|
+
|
|
1074
|
+
return self._run_in_sandbox(inner)
|
|
1075
|
+
|
|
1076
|
+
# ==================== 标签页管理 / Tab Management ====================
|
|
1077
|
+
|
|
1078
|
+
@tool(
|
|
1079
|
+
name="browser_tabs_list",
|
|
1080
|
+
description=(
|
|
1081
|
+
"List all open browser tabs. Returns index, URL, and title for each"
|
|
1082
|
+
" tab. Useful for knowing which pages are open and determining"
|
|
1083
|
+
" which tab to switch to."
|
|
1084
|
+
),
|
|
1085
|
+
)
|
|
1086
|
+
def browser_tabs_list(self) -> Dict[str, Any]:
|
|
1087
|
+
"""列出所有标签页 / List all tabs"""
|
|
1088
|
+
|
|
1089
|
+
def inner(sb: Sandbox):
|
|
1090
|
+
assert isinstance(sb, BrowserSandbox)
|
|
1091
|
+
with sb.sync_playwright() as p:
|
|
1092
|
+
pages = p.list_pages()
|
|
1093
|
+
tabs = []
|
|
1094
|
+
for i, page in enumerate(pages):
|
|
1095
|
+
tabs.append({
|
|
1096
|
+
"index": i,
|
|
1097
|
+
"url": page.url,
|
|
1098
|
+
"title": page.title(),
|
|
1099
|
+
})
|
|
1100
|
+
return {"tabs": tabs, "count": len(tabs)}
|
|
1101
|
+
|
|
1102
|
+
return self._run_in_sandbox(inner)
|
|
1103
|
+
|
|
1104
|
+
@tool(
|
|
1105
|
+
name="browser_tabs_new",
|
|
1106
|
+
description=(
|
|
1107
|
+
"Create a new browser tab. Can optionally specify an initial URL,"
|
|
1108
|
+
" otherwise opens a blank page. The new tab automatically becomes"
|
|
1109
|
+
" the active tab after creation."
|
|
1110
|
+
),
|
|
1111
|
+
)
|
|
1112
|
+
def browser_tabs_new(self, url: Optional[str] = None) -> Dict[str, Any]:
|
|
1113
|
+
"""创建新标签页 / Create new tab"""
|
|
1114
|
+
|
|
1115
|
+
def inner(sb: Sandbox):
|
|
1116
|
+
assert isinstance(sb, BrowserSandbox)
|
|
1117
|
+
with sb.sync_playwright() as p:
|
|
1118
|
+
page = p.new_page()
|
|
1119
|
+
if url:
|
|
1120
|
+
page.goto(url)
|
|
1121
|
+
return {
|
|
1122
|
+
"success": True,
|
|
1123
|
+
"url": page.url,
|
|
1124
|
+
"title": page.title(),
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
return self._run_in_sandbox(inner)
|
|
1128
|
+
|
|
1129
|
+
@tool(
|
|
1130
|
+
name="browser_tabs_select",
|
|
1131
|
+
description=(
|
|
1132
|
+
"Switch to the tab at the specified index. Index starts from 0, use"
|
|
1133
|
+
" browser_tabs_list to get all tab indices. After switching, all"
|
|
1134
|
+
" subsequent operations will be performed on the selected tab."
|
|
1135
|
+
),
|
|
1136
|
+
)
|
|
1137
|
+
def browser_tabs_select(self, index: int) -> Dict[str, Any]:
|
|
1138
|
+
"""切换标签页 / Switch tab"""
|
|
1139
|
+
|
|
1140
|
+
def inner(sb: Sandbox):
|
|
1141
|
+
assert isinstance(sb, BrowserSandbox)
|
|
1142
|
+
with sb.sync_playwright() as p:
|
|
1143
|
+
page = p.select_tab(index)
|
|
1144
|
+
return {
|
|
1145
|
+
"success": True,
|
|
1146
|
+
"index": index,
|
|
1147
|
+
"url": page.url,
|
|
1148
|
+
"title": page.title(),
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
return self._run_in_sandbox(inner)
|
|
1152
|
+
|
|
1153
|
+
# ==================== 高级功能 / Advanced Features ====================
|
|
1154
|
+
|
|
1155
|
+
@tool(
|
|
1156
|
+
name="browser_evaluate",
|
|
1157
|
+
description=(
|
|
1158
|
+
"Execute JavaScript code in the page context and return the result."
|
|
1159
|
+
" Can access page DOM, window object, etc. Suitable for getting"
|
|
1160
|
+
" page data, performing complex operations, or calling page"
|
|
1161
|
+
" functions. Return value is automatically serialized to"
|
|
1162
|
+
" JSON-compatible format."
|
|
1163
|
+
),
|
|
1164
|
+
)
|
|
1165
|
+
def browser_evaluate(
|
|
1166
|
+
self,
|
|
1167
|
+
expression: str,
|
|
1168
|
+
arg: Optional[Any] = None,
|
|
1169
|
+
) -> Dict[str, Any]:
|
|
1170
|
+
"""执行 JavaScript / Execute JavaScript"""
|
|
1171
|
+
|
|
1172
|
+
def inner(sb: Sandbox):
|
|
1173
|
+
assert isinstance(sb, BrowserSandbox)
|
|
1174
|
+
with sb.sync_playwright() as p:
|
|
1175
|
+
result = p.evaluate(expression, arg=arg)
|
|
1176
|
+
return {"result": result}
|
|
1177
|
+
|
|
1178
|
+
return self._run_in_sandbox(inner)
|
|
1179
|
+
|
|
1180
|
+
# 保留旧的 evaluate 作为别名
|
|
1181
|
+
@tool(
|
|
1182
|
+
name="evaluate",
|
|
1183
|
+
description=(
|
|
1184
|
+
"执行 JavaScript(browser_evaluate 的别名,保持向后兼容)。 /"
|
|
1185
|
+
" Execute JavaScript (alias for browser_evaluate, for backward"
|
|
1186
|
+
" compatibility)."
|
|
1187
|
+
),
|
|
1188
|
+
)
|
|
1189
|
+
def evaluate(self, expression: str) -> Dict[str, Any]:
|
|
1190
|
+
"""执行 JavaScript / Execute JavaScript"""
|
|
1191
|
+
return self.browser_evaluate(expression=expression)
|
|
1192
|
+
|
|
1193
|
+
@tool(
|
|
1194
|
+
name="browser_wait_for",
|
|
1195
|
+
description=(
|
|
1196
|
+
"Wait for the specified time (in milliseconds). Used to wait for"
|
|
1197
|
+
" page animations, AJAX requests, and other async operations. Note:"
|
|
1198
|
+
" Prefer more precise wait conditions (like waiting for element),"
|
|
1199
|
+
" fixed time wait should be a last resort."
|
|
1200
|
+
),
|
|
1201
|
+
)
|
|
1202
|
+
def browser_wait_for(self, timeout: float) -> Dict[str, Any]:
|
|
1203
|
+
"""等待指定时间 / Wait for specified time"""
|
|
1204
|
+
|
|
1205
|
+
def inner(sb: Sandbox):
|
|
1206
|
+
assert isinstance(sb, BrowserSandbox)
|
|
1207
|
+
with sb.sync_playwright() as p:
|
|
1208
|
+
p.wait(timeout)
|
|
1209
|
+
return {"success": True, "waited_ms": timeout}
|
|
1210
|
+
|
|
1211
|
+
return self._run_in_sandbox(inner)
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
def sandbox_toolset(
|
|
1215
|
+
template_name: str,
|
|
1216
|
+
*,
|
|
1217
|
+
template_type: TemplateType = TemplateType.CODE_INTERPRETER,
|
|
1218
|
+
config: Optional[Config] = None,
|
|
1219
|
+
sandbox_idle_timeout_seconds: int = 5 * 60,
|
|
1220
|
+
) -> CommonToolSet:
|
|
1221
|
+
"""将沙箱模板封装为 LangChain ``StructuredTool`` 列表。"""
|
|
1222
|
+
|
|
1223
|
+
if template_type != TemplateType.CODE_INTERPRETER:
|
|
1224
|
+
return BrowserToolSet(
|
|
1225
|
+
template_name=template_name,
|
|
1226
|
+
config=config,
|
|
1227
|
+
sandbox_idle_timeout_seconds=sandbox_idle_timeout_seconds,
|
|
1228
|
+
)
|
|
1229
|
+
else:
|
|
1230
|
+
return CodeInterpreterToolSet(
|
|
1231
|
+
template_name=template_name,
|
|
1232
|
+
config=config,
|
|
1233
|
+
sandbox_idle_timeout_seconds=sandbox_idle_timeout_seconds,
|
|
1234
|
+
)
|