a2c-smcp 0.1.1rc0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- a2c_smcp-0.1.1rc0/LICENSE +21 -0
- a2c_smcp-0.1.1rc0/PKG-INFO +43 -0
- a2c_smcp-0.1.1rc0/README.md +2 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/__init__.py +7 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/agent/__init__.py +56 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/agent/auth.py +140 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/agent/base.py +288 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/agent/client.py +342 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/agent/sync_client.py +297 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/agent/types.py +134 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/__init__.py +10 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/base.py +153 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/cli/__init__.py +6 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/cli/interactive_impl.py +490 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/cli/main.py +289 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/cli/utils.py +177 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/computer.py +738 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/desktop/__init__.py +6 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/desktop/organize.py +142 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/inputs/__init__.py +6 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/inputs/base.py +135 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/inputs/cli_io.py +182 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/inputs/render.py +96 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/inputs/resolver.py +88 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/mcp_clients/__init__.py +5 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/mcp_clients/base_client.py +461 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/mcp_clients/base_client.pyi +91 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/mcp_clients/http_client.py +49 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/mcp_clients/manager.py +552 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/mcp_clients/model.py +192 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/mcp_clients/sse_client.py +44 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/mcp_clients/stdio_client.py +42 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/mcp_clients/utils.py +28 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/socketio/__init__.py +5 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/socketio/client.py +227 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/types.py +34 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/utils/__init__.py +6 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/computer/utils/console.py +23 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/server/__init__.py +50 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/server/auth.py +134 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/server/base.py +129 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/server/namespace.py +410 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/server/sync_auth.py +90 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/server/sync_base.py +91 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/server/sync_namespace.py +304 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/server/types.py +48 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/server/utils.py +139 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/smcp.py +299 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/types.py +29 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/utils/__init__.py +18 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/utils/async_property/__init__.py +9 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/utils/async_property/base.py +51 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/utils/async_property/proxy.py +463 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/utils/logger.py +67 -0
- a2c_smcp-0.1.1rc0/a2c_smcp/utils/window_uri.py +232 -0
- a2c_smcp-0.1.1rc0/pyproject.toml +86 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 A2C-SMCP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: a2c-smcp
|
|
3
|
+
Version: 0.1.1rc0
|
|
4
|
+
Summary: The official Python Computer Client SDK for A2C-SMCP
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: JQQ
|
|
8
|
+
Author-email: jqq1716@gmail.com
|
|
9
|
+
Requires-Python: >=3.11,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Provides-Extra: cli
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Provides-Extra: test
|
|
19
|
+
Requires-Dist: aiohttp (>=3.12.15,<4.0.0) ; extra == "test"
|
|
20
|
+
Requires-Dist: inline-snapshot (>=0.27.2,<0.28.0) ; extra == "test"
|
|
21
|
+
Requires-Dist: mcp (==1.15.0)
|
|
22
|
+
Requires-Dist: mypy (>=1.17.1,<2.0.0) ; extra == "dev"
|
|
23
|
+
Requires-Dist: poethepoet (>=0.37.0,<0.38.0) ; extra == "dev"
|
|
24
|
+
Requires-Dist: polyfactory (>=2.22.2,<3.0.0) ; extra == "test"
|
|
25
|
+
Requires-Dist: prompt-toolkit (>=3.0.52,<4.0.0) ; extra == "cli"
|
|
26
|
+
Requires-Dist: pydantic (>=2.11.7,<3.0.0)
|
|
27
|
+
Requires-Dist: pytest (>=8.4.1,<9.0.0) ; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-asyncio (>=1.1.0,<2.0.0) ; extra == "test"
|
|
29
|
+
Requires-Dist: pytest-cov (>=6.2.1,<7.0.0) ; extra == "test"
|
|
30
|
+
Requires-Dist: pytest-dotenv (>=0.5.2,<0.6.0) ; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-mock (>=3.14.1,<4.0.0) ; extra == "test"
|
|
32
|
+
Requires-Dist: python-socketio (>=5.13.0,<6.0.0)
|
|
33
|
+
Requires-Dist: rich (>=14.1.0,<15.0.0) ; extra == "cli"
|
|
34
|
+
Requires-Dist: ruff (>=0.12.9,<0.13.0) ; extra == "dev"
|
|
35
|
+
Requires-Dist: transitions (>=0.9.3,<0.10.0)
|
|
36
|
+
Requires-Dist: typer (>=0.17.4,<1.0.0) ; extra == "cli"
|
|
37
|
+
Requires-Dist: tzlocal (>=5.0.0,<6.0.0)
|
|
38
|
+
Requires-Dist: vrl-python (>=0.1.0,<0.2.0)
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# python-sdk
|
|
42
|
+
The official Python Client SDK for A2C-SMCP
|
|
43
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# filename: __init__.py
|
|
3
|
+
# @Time : 2025/9/30 13:56
|
|
4
|
+
# @Author : JQQ
|
|
5
|
+
# @Email : jqq1716@gmail.com
|
|
6
|
+
# @Software: PyCharm
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
A2C-SMCP Agent模块
|
|
10
|
+
A2C-SMCP Agent module
|
|
11
|
+
|
|
12
|
+
提供Agent端的SMCP协议客户端实现,包括同步和异步两种模式
|
|
13
|
+
Provides Agent-side SMCP protocol client implementation, including both synchronous and asynchronous modes
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from a2c_smcp.agent.auth import AgentAuthProvider, DefaultAgentAuthProvider
|
|
17
|
+
from a2c_smcp.agent.base import BaseAgentClient
|
|
18
|
+
from a2c_smcp.agent.client import AsyncSMCPAgentClient
|
|
19
|
+
from a2c_smcp.agent.sync_client import SMCPAgentClient
|
|
20
|
+
from a2c_smcp.agent.types import (
|
|
21
|
+
AgentConfig,
|
|
22
|
+
AgentEventHandler,
|
|
23
|
+
AgentID,
|
|
24
|
+
AgentIDGetter,
|
|
25
|
+
AsyncAgentEventHandler,
|
|
26
|
+
AsyncAgentIDGetter,
|
|
27
|
+
AsyncToolCallCallback,
|
|
28
|
+
ComputerID,
|
|
29
|
+
RequestID,
|
|
30
|
+
ToolCallCallback,
|
|
31
|
+
ToolCallContext,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
# 认证相关 / Authentication related
|
|
36
|
+
"AgentAuthProvider",
|
|
37
|
+
"DefaultAgentAuthProvider",
|
|
38
|
+
|
|
39
|
+
# 客户端实现 / Client implementations
|
|
40
|
+
"BaseAgentClient",
|
|
41
|
+
"SMCPAgentClient",
|
|
42
|
+
"AsyncSMCPAgentClient",
|
|
43
|
+
|
|
44
|
+
# 类型定义 / Type definitions
|
|
45
|
+
"AgentConfig",
|
|
46
|
+
"AgentEventHandler",
|
|
47
|
+
"AsyncAgentEventHandler",
|
|
48
|
+
"AgentID",
|
|
49
|
+
"ComputerID",
|
|
50
|
+
"RequestID",
|
|
51
|
+
"ToolCallContext",
|
|
52
|
+
"ToolCallCallback",
|
|
53
|
+
"AsyncToolCallCallback",
|
|
54
|
+
"AgentIDGetter",
|
|
55
|
+
"AsyncAgentIDGetter",
|
|
56
|
+
]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
* 文件名: auth
|
|
3
|
+
* 作者: JQQ
|
|
4
|
+
* 创建日期: 2025/9/30
|
|
5
|
+
* 最后修改日期: 2025/9/30
|
|
6
|
+
* 版权: 2023 JQQ. All rights reserved.
|
|
7
|
+
* 依赖: None
|
|
8
|
+
* 描述: Agent端认证接口抽象定义 / Agent-side authentication interface abstract definition
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from a2c_smcp.agent.types import AgentConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgentAuthProvider(ABC):
|
|
18
|
+
"""
|
|
19
|
+
Agent认证提供者抽象基类,用于处理Agent连接的认证逻辑
|
|
20
|
+
Abstract base class for Agent authentication providers, handles Agent connection authentication logic
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def get_agent_id(self) -> str:
|
|
25
|
+
"""
|
|
26
|
+
获取agent_id,由用户实现具体逻辑
|
|
27
|
+
Get agent_id, implemented by user with specific logic
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
str: agent_id
|
|
31
|
+
"""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def get_connection_auth(self) -> dict[str, Any] | None:
|
|
36
|
+
"""
|
|
37
|
+
获取连接认证信息,用于Socket.IO连接时的auth参数
|
|
38
|
+
Get connection authentication info, used for Socket.IO connection auth parameter
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
dict[str, Any] | None: 认证信息字典 / Authentication info dictionary
|
|
42
|
+
"""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def get_connection_headers(self) -> dict[str, str]:
|
|
47
|
+
"""
|
|
48
|
+
获取连接请求头,用于Socket.IO连接时的headers参数
|
|
49
|
+
Get connection headers, used for Socket.IO connection headers parameter
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
dict[str, str]: 请求头字典 / Headers dictionary
|
|
53
|
+
"""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def get_agent_config(self) -> AgentConfig:
|
|
58
|
+
"""
|
|
59
|
+
获取Agent配置信息
|
|
60
|
+
Get Agent configuration information
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
AgentConfig: Agent配置 / Agent configuration
|
|
64
|
+
"""
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class DefaultAgentAuthProvider(AgentAuthProvider):
|
|
69
|
+
"""
|
|
70
|
+
默认Agent认证提供者,提供基础的认证逻辑实现
|
|
71
|
+
Default Agent authentication provider, provides basic authentication logic implementation
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
agent_id: str,
|
|
77
|
+
office_id: str,
|
|
78
|
+
api_key: str | None = None,
|
|
79
|
+
api_key_header: str = "x-api-key",
|
|
80
|
+
extra_headers: dict[str, str] | None = None,
|
|
81
|
+
auth_data: dict[str, Any] | None = None,
|
|
82
|
+
) -> None:
|
|
83
|
+
"""
|
|
84
|
+
初始化默认Agent认证提供者
|
|
85
|
+
Initialize default Agent authentication provider
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
agent_id (str): Agent唯一标识 / Agent unique identifier
|
|
89
|
+
office_id (str): 办公室ID / Office ID
|
|
90
|
+
api_key (str | None): API密钥 / API key
|
|
91
|
+
api_key_header (str): API密钥请求头名称 / API key header name
|
|
92
|
+
extra_headers (dict[str, str] | None): 额外请求头 / Extra headers
|
|
93
|
+
auth_data (dict[str, Any] | None): 额外认证数据 / Extra auth data
|
|
94
|
+
"""
|
|
95
|
+
self._agent_id = agent_id
|
|
96
|
+
self._office_id = office_id
|
|
97
|
+
self._api_key = api_key
|
|
98
|
+
self._api_key_header = api_key_header
|
|
99
|
+
self._extra_headers = extra_headers or {}
|
|
100
|
+
self._auth_data = auth_data or {}
|
|
101
|
+
|
|
102
|
+
def get_agent_id(self) -> str:
|
|
103
|
+
"""
|
|
104
|
+
获取Agent ID
|
|
105
|
+
Get Agent ID
|
|
106
|
+
"""
|
|
107
|
+
return self._agent_id
|
|
108
|
+
|
|
109
|
+
def get_connection_auth(self) -> dict[str, Any] | None:
|
|
110
|
+
"""
|
|
111
|
+
获取连接认证信息
|
|
112
|
+
Get connection authentication info
|
|
113
|
+
"""
|
|
114
|
+
if not self._auth_data:
|
|
115
|
+
return None
|
|
116
|
+
return self._auth_data.copy()
|
|
117
|
+
|
|
118
|
+
def get_connection_headers(self) -> dict[str, str]:
|
|
119
|
+
"""
|
|
120
|
+
获取连接请求头
|
|
121
|
+
Get connection headers
|
|
122
|
+
"""
|
|
123
|
+
headers = self._extra_headers.copy()
|
|
124
|
+
|
|
125
|
+
# 添加API密钥到请求头
|
|
126
|
+
# Add API key to headers
|
|
127
|
+
if self._api_key:
|
|
128
|
+
headers[self._api_key_header] = self._api_key
|
|
129
|
+
|
|
130
|
+
return headers
|
|
131
|
+
|
|
132
|
+
def get_agent_config(self) -> AgentConfig:
|
|
133
|
+
"""
|
|
134
|
+
获取Agent配置信息
|
|
135
|
+
Get Agent configuration information
|
|
136
|
+
"""
|
|
137
|
+
return AgentConfig(
|
|
138
|
+
agent_id=self._agent_id,
|
|
139
|
+
office_id=self._office_id,
|
|
140
|
+
)
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""
|
|
2
|
+
* 文件名: base
|
|
3
|
+
* 作者: JQQ
|
|
4
|
+
* 创建日期: 2025/9/30
|
|
5
|
+
* 最后修改日期: 2025/9/30
|
|
6
|
+
* 版权: 2023 JQQ. All rights reserved.
|
|
7
|
+
* 依赖: socketio, loguru
|
|
8
|
+
* 描述: Agent基础客户端抽象类 / Agent base client abstract class
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import uuid
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from mcp.types import CallToolResult, TextContent
|
|
16
|
+
|
|
17
|
+
from a2c_smcp.agent.auth import AgentAuthProvider
|
|
18
|
+
from a2c_smcp.agent.types import AgentEventHandler
|
|
19
|
+
from a2c_smcp.smcp import (
|
|
20
|
+
EnterOfficeNotification,
|
|
21
|
+
GetDeskTopReq,
|
|
22
|
+
GetDeskTopRet,
|
|
23
|
+
GetToolsReq,
|
|
24
|
+
GetToolsRet,
|
|
25
|
+
LeaveOfficeNotification,
|
|
26
|
+
ToolCallReq,
|
|
27
|
+
UpdateMCPConfigNotification,
|
|
28
|
+
)
|
|
29
|
+
from a2c_smcp.utils.logger import logger
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BaseAgentClient(ABC):
|
|
33
|
+
"""
|
|
34
|
+
Agent基础客户端抽象类,提供通用的SMCP协议处理逻辑
|
|
35
|
+
Agent base client abstract class, provides common SMCP protocol handling logic
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
auth_provider: AgentAuthProvider,
|
|
41
|
+
event_handler: AgentEventHandler | None = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""
|
|
44
|
+
初始化基础Agent客户端
|
|
45
|
+
Initialize base Agent client
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
auth_provider (AgentAuthProvider): 认证提供者 / Authentication provider
|
|
49
|
+
event_handler (AgentEventHandler | None): 事件处理器 / Event handler
|
|
50
|
+
"""
|
|
51
|
+
self.auth_provider = auth_provider
|
|
52
|
+
self.event_handler = event_handler
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def emit(self, event: str, data: Any = None, namespace: str | None = None, callback: Any = None) -> Any:
|
|
56
|
+
"""
|
|
57
|
+
发送事件的抽象方法
|
|
58
|
+
Abstract method for sending events
|
|
59
|
+
"""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def call(self, event: str, data: Any = None, namespace: str | None = None, timeout: int = 60) -> Any:
|
|
64
|
+
"""
|
|
65
|
+
调用事件并等待响应的抽象方法
|
|
66
|
+
Abstract method for calling events and waiting for response
|
|
67
|
+
"""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
def validate_emit_event(self, event: str) -> None:
|
|
71
|
+
"""
|
|
72
|
+
验证发送事件的合法性
|
|
73
|
+
Validate the legality of emitted events
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
event (str): 事件名称 / Event name
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
ValueError: 当事件不合法时 / When event is invalid
|
|
80
|
+
"""
|
|
81
|
+
if event.startswith("notify:"):
|
|
82
|
+
raise ValueError("AgentClient不允许使用notify:*事件 / AgentClient is not allowed to use notify:* events")
|
|
83
|
+
if event.startswith("agent:"):
|
|
84
|
+
raise ValueError("AgentClient不允许发起agent:*事件 / AgentClient is not allowed to initiate agent:* events")
|
|
85
|
+
|
|
86
|
+
def create_tool_call_request(self, computer: str, tool_name: str, params: dict, timeout: int) -> ToolCallReq:
|
|
87
|
+
"""
|
|
88
|
+
创建工具调用请求对象
|
|
89
|
+
Create tool call request object
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
computer (str): 目标计算机ID / Target computer ID
|
|
93
|
+
tool_name (str): 工具名称 / Tool name
|
|
94
|
+
params (dict): 调用参数 / Call parameters
|
|
95
|
+
timeout (int): 超时时间 / Timeout duration
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
ToolCallReq: 工具调用请求 / Tool call request
|
|
99
|
+
"""
|
|
100
|
+
agent_config = self.auth_provider.get_agent_config()
|
|
101
|
+
return ToolCallReq(
|
|
102
|
+
computer=computer,
|
|
103
|
+
tool_name=tool_name,
|
|
104
|
+
params=params,
|
|
105
|
+
robot_id=agent_config["agent_id"],
|
|
106
|
+
req_id=uuid.uuid4().hex,
|
|
107
|
+
timeout=timeout,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def create_get_tools_request(self, computer: str) -> GetToolsReq:
|
|
111
|
+
"""
|
|
112
|
+
创建获取工具请求对象
|
|
113
|
+
Create get tools request object
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
computer (str): 目标计算机ID / Target computer ID
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
GetToolsReq: 获取工具请求 / Get tools request
|
|
120
|
+
"""
|
|
121
|
+
agent_config = self.auth_provider.get_agent_config()
|
|
122
|
+
return GetToolsReq(
|
|
123
|
+
computer=computer,
|
|
124
|
+
robot_id=agent_config["agent_id"],
|
|
125
|
+
req_id=uuid.uuid4().hex,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def create_get_desktop_request(self, computer: str, *, size: int | None = None, window: str | None = None) -> GetDeskTopReq:
|
|
129
|
+
"""
|
|
130
|
+
创建获取桌面请求对象
|
|
131
|
+
Create get desktop request object
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
computer (str): 目标计算机ID / Target computer ID
|
|
135
|
+
size (int | None): 桌面窗口数量上限 / Max number of windows
|
|
136
|
+
window (str | None): 指定窗口URI / Specific window URI
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
GetDeskTopReq: 获取桌面请求 / Get desktop request
|
|
140
|
+
"""
|
|
141
|
+
agent_config = self.auth_provider.get_agent_config()
|
|
142
|
+
req: GetDeskTopReq = {
|
|
143
|
+
"computer": computer,
|
|
144
|
+
"robot_id": agent_config["agent_id"],
|
|
145
|
+
"req_id": uuid.uuid4().hex,
|
|
146
|
+
}
|
|
147
|
+
if size is not None:
|
|
148
|
+
req["desktop_size"] = size
|
|
149
|
+
if window is not None:
|
|
150
|
+
req["window"] = window
|
|
151
|
+
return req
|
|
152
|
+
|
|
153
|
+
def process_desktop_response(self, response: GetDeskTopRet, computer: str) -> None:
|
|
154
|
+
"""
|
|
155
|
+
处理桌面响应(默认仅记录日志;留作后续扩展回调)。
|
|
156
|
+
Process desktop response (log only by default; placeholder for future callbacks).
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
desktops = response.get("desktops", []) if isinstance(response, dict) else []
|
|
160
|
+
logger.info(f"Received desktop from computer {computer}, windows={len(desktops)}")
|
|
161
|
+
except Exception as e:
|
|
162
|
+
logger.error(f"Error processing desktop response: {e}")
|
|
163
|
+
|
|
164
|
+
def handle_tool_call_timeout(self, req_id: str) -> CallToolResult:
|
|
165
|
+
"""
|
|
166
|
+
处理工具调用超时情况
|
|
167
|
+
Handle tool call timeout situation
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
req_id (str): 请求ID / Request ID
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
CallToolResult: 超时错误结果 / Timeout error result
|
|
174
|
+
"""
|
|
175
|
+
return CallToolResult(
|
|
176
|
+
content=[TextContent(text=f"工具调用超时 / Tool call timeout, req_id={req_id}", type="text")],
|
|
177
|
+
isError=True,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def validate_office_data(self, data: EnterOfficeNotification | LeaveOfficeNotification) -> str:
|
|
181
|
+
"""
|
|
182
|
+
验证办公室数据并返回计算机ID
|
|
183
|
+
Validate office data and return computer ID
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
data: 办公室通知数据 / Office notification data
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
str: 计算机ID / Computer ID
|
|
190
|
+
|
|
191
|
+
Raises:
|
|
192
|
+
AssertionError: 当数据无效时 / When data is invalid
|
|
193
|
+
"""
|
|
194
|
+
agent_config = self.auth_provider.get_agent_config()
|
|
195
|
+
assert data["office_id"] == agent_config["office_id"], "无效的办公室ID / Invalid office ID"
|
|
196
|
+
assert data.get("computer"), "无效的计算机ID / Invalid computer ID"
|
|
197
|
+
return data["computer"]
|
|
198
|
+
|
|
199
|
+
def handle_computer_enter_office(self, data: EnterOfficeNotification) -> None:
|
|
200
|
+
"""
|
|
201
|
+
处理Computer加入办公室事件
|
|
202
|
+
Handle Computer enter office event
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
data: 加入办公室通知数据 / Enter office notification data
|
|
206
|
+
"""
|
|
207
|
+
try:
|
|
208
|
+
computer = self.validate_office_data(data)
|
|
209
|
+
logger.info(f"Computer {computer} entered office {data['office_id']}")
|
|
210
|
+
|
|
211
|
+
# 调用事件处理器
|
|
212
|
+
# Call event handler
|
|
213
|
+
if self.event_handler:
|
|
214
|
+
self.event_handler.on_computer_enter_office(data)
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
logger.error(f"Error handling computer enter office: {e}")
|
|
218
|
+
|
|
219
|
+
def handle_computer_leave_office(self, data: LeaveOfficeNotification) -> None:
|
|
220
|
+
"""
|
|
221
|
+
处理Computer离开办公室事件
|
|
222
|
+
Handle Computer leave office event
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
data: 离开办公室通知数据 / Leave office notification data
|
|
226
|
+
"""
|
|
227
|
+
try:
|
|
228
|
+
computer = self.validate_office_data(data)
|
|
229
|
+
logger.info(f"Computer {computer} left office {data['office_id']}")
|
|
230
|
+
|
|
231
|
+
# 调用事件处理器
|
|
232
|
+
# Call event handler
|
|
233
|
+
if self.event_handler:
|
|
234
|
+
self.event_handler.on_computer_leave_office(data)
|
|
235
|
+
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error(f"Error handling computer leave office: {e}")
|
|
238
|
+
|
|
239
|
+
def handle_computer_update_config(self, data: UpdateMCPConfigNotification) -> None:
|
|
240
|
+
"""
|
|
241
|
+
处理Computer更新配置事件
|
|
242
|
+
Handle Computer update config event
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
data: 配置更新通知数据 / Config update notification data
|
|
246
|
+
"""
|
|
247
|
+
try:
|
|
248
|
+
computer = data["computer"]
|
|
249
|
+
logger.info(f"Computer {computer} updated config")
|
|
250
|
+
|
|
251
|
+
# 调用事件处理器
|
|
252
|
+
# Call event handler
|
|
253
|
+
if self.event_handler:
|
|
254
|
+
self.event_handler.on_computer_update_config(data)
|
|
255
|
+
|
|
256
|
+
except Exception as e:
|
|
257
|
+
logger.error(f"Error handling computer update config: {e}")
|
|
258
|
+
|
|
259
|
+
def process_tools_response(self, response: GetToolsRet, computer: str) -> None:
|
|
260
|
+
"""
|
|
261
|
+
处理工具响应
|
|
262
|
+
Process tools response
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
response: 工具响应数据 / Tools response data
|
|
266
|
+
computer: 计算机ID / Computer ID
|
|
267
|
+
"""
|
|
268
|
+
try:
|
|
269
|
+
if tools := response.get("tools"):
|
|
270
|
+
logger.info(f"Received {len(tools)} tools from computer {computer}")
|
|
271
|
+
|
|
272
|
+
# 调用事件处理器
|
|
273
|
+
# Call event handler
|
|
274
|
+
if self.event_handler:
|
|
275
|
+
self.event_handler.on_tools_received(computer, tools)
|
|
276
|
+
|
|
277
|
+
except Exception as e:
|
|
278
|
+
logger.error(f"Error processing tools response: {e}")
|
|
279
|
+
|
|
280
|
+
@abstractmethod
|
|
281
|
+
def register_event_handlers(self) -> None:
|
|
282
|
+
"""
|
|
283
|
+
注册事件处理器,子类需要实现具体的注册逻辑
|
|
284
|
+
Register event handlers, subclasses need to implement specific registration logic
|
|
285
|
+
"""
|
|
286
|
+
# 这个方法需要在子类中实现具体的事件注册逻辑
|
|
287
|
+
# This method needs to implement specific event registration logic in subclasses
|
|
288
|
+
pass
|