autoglm-gui 1.4.1__py3-none-any.whl → 1.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. AutoGLM_GUI/__init__.py +11 -0
  2. AutoGLM_GUI/__main__.py +26 -4
  3. AutoGLM_GUI/actions/__init__.py +6 -0
  4. phone_agent/actions/handler_ios.py → AutoGLM_GUI/actions/handler.py +30 -112
  5. AutoGLM_GUI/actions/types.py +15 -0
  6. {phone_agent → AutoGLM_GUI}/adb/__init__.py +25 -23
  7. {phone_agent → AutoGLM_GUI}/adb/connection.py +5 -40
  8. {phone_agent → AutoGLM_GUI}/adb/device.py +12 -94
  9. {phone_agent → AutoGLM_GUI}/adb/input.py +6 -47
  10. AutoGLM_GUI/adb/screenshot.py +11 -0
  11. {phone_agent/config → AutoGLM_GUI/adb}/timing.py +1 -1
  12. AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
  13. AutoGLM_GUI/adb_plus/screenshot.py +22 -1
  14. AutoGLM_GUI/adb_plus/serial.py +38 -20
  15. AutoGLM_GUI/adb_plus/touch.py +4 -9
  16. AutoGLM_GUI/agents/__init__.py +43 -12
  17. AutoGLM_GUI/agents/events.py +19 -0
  18. AutoGLM_GUI/agents/factory.py +31 -38
  19. AutoGLM_GUI/agents/glm/__init__.py +7 -0
  20. AutoGLM_GUI/agents/glm/agent.py +297 -0
  21. AutoGLM_GUI/agents/glm/message_builder.py +81 -0
  22. AutoGLM_GUI/agents/glm/parser.py +110 -0
  23. {phone_agent/config → AutoGLM_GUI/agents/glm}/prompts_en.py +7 -9
  24. {phone_agent/config → AutoGLM_GUI/agents/glm}/prompts_zh.py +18 -25
  25. AutoGLM_GUI/agents/mai/__init__.py +28 -0
  26. AutoGLM_GUI/agents/mai/agent.py +408 -0
  27. AutoGLM_GUI/agents/mai/parser.py +254 -0
  28. AutoGLM_GUI/agents/mai/prompts.py +103 -0
  29. AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
  30. AutoGLM_GUI/agents/protocols.py +12 -8
  31. AutoGLM_GUI/agents/stream_runner.py +193 -0
  32. AutoGLM_GUI/api/__init__.py +40 -21
  33. AutoGLM_GUI/api/agents.py +181 -239
  34. AutoGLM_GUI/api/control.py +9 -6
  35. AutoGLM_GUI/api/devices.py +102 -12
  36. AutoGLM_GUI/api/history.py +104 -0
  37. AutoGLM_GUI/api/layered_agent.py +67 -15
  38. AutoGLM_GUI/api/media.py +64 -1
  39. AutoGLM_GUI/api/scheduled_tasks.py +98 -0
  40. AutoGLM_GUI/config.py +81 -0
  41. AutoGLM_GUI/config_manager.py +68 -51
  42. AutoGLM_GUI/device_manager.py +248 -29
  43. AutoGLM_GUI/device_protocol.py +1 -1
  44. AutoGLM_GUI/devices/adb_device.py +5 -10
  45. AutoGLM_GUI/devices/mock_device.py +4 -2
  46. AutoGLM_GUI/devices/remote_device.py +8 -3
  47. AutoGLM_GUI/history_manager.py +164 -0
  48. AutoGLM_GUI/model/__init__.py +5 -0
  49. AutoGLM_GUI/model/message_builder.py +69 -0
  50. AutoGLM_GUI/model/types.py +24 -0
  51. AutoGLM_GUI/models/__init__.py +10 -0
  52. AutoGLM_GUI/models/history.py +140 -0
  53. AutoGLM_GUI/models/scheduled_task.py +71 -0
  54. AutoGLM_GUI/parsers/__init__.py +22 -0
  55. AutoGLM_GUI/parsers/base.py +50 -0
  56. AutoGLM_GUI/parsers/phone_parser.py +58 -0
  57. AutoGLM_GUI/phone_agent_manager.py +62 -396
  58. AutoGLM_GUI/platform_utils.py +26 -0
  59. AutoGLM_GUI/prompt_config.py +15 -0
  60. AutoGLM_GUI/prompts/__init__.py +32 -0
  61. AutoGLM_GUI/scheduler_manager.py +350 -0
  62. AutoGLM_GUI/schemas.py +246 -72
  63. AutoGLM_GUI/scrcpy_stream.py +142 -24
  64. AutoGLM_GUI/socketio_server.py +100 -27
  65. AutoGLM_GUI/static/assets/{about-_XNhzQZX.js → about-CfwX1Cmc.js} +1 -1
  66. AutoGLM_GUI/static/assets/alert-dialog-CtGlN2IJ.js +1 -0
  67. AutoGLM_GUI/static/assets/chat-BYa-foUI.js +129 -0
  68. AutoGLM_GUI/static/assets/circle-alert-t08bEMPO.js +1 -0
  69. AutoGLM_GUI/static/assets/dialog-FNwZJFwk.js +45 -0
  70. AutoGLM_GUI/static/assets/eye-D0UPWCWC.js +1 -0
  71. AutoGLM_GUI/static/assets/history-CRo95B7i.js +1 -0
  72. AutoGLM_GUI/static/assets/{index-Cy8TmmHV.js → index-BaLMSqd3.js} +1 -1
  73. AutoGLM_GUI/static/assets/index-CTHbFvKl.js +11 -0
  74. AutoGLM_GUI/static/assets/index-CV7jGxGm.css +1 -0
  75. AutoGLM_GUI/static/assets/label-DJFevVmr.js +1 -0
  76. AutoGLM_GUI/static/assets/logs-RW09DyYY.js +1 -0
  77. AutoGLM_GUI/static/assets/popover--JTJrE5v.js +1 -0
  78. AutoGLM_GUI/static/assets/scheduled-tasks-DTRKsQXF.js +1 -0
  79. AutoGLM_GUI/static/assets/square-pen-CPK_K680.js +1 -0
  80. AutoGLM_GUI/static/assets/textarea-PRmVnWq5.js +1 -0
  81. AutoGLM_GUI/static/assets/workflows-CdcsAoaT.js +1 -0
  82. AutoGLM_GUI/static/index.html +2 -2
  83. AutoGLM_GUI/types.py +17 -0
  84. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/METADATA +179 -130
  85. autoglm_gui-1.5.1.dist-info/RECORD +118 -0
  86. AutoGLM_GUI/agents/mai_adapter.py +0 -627
  87. AutoGLM_GUI/api/dual_model.py +0 -317
  88. AutoGLM_GUI/device_adapter.py +0 -263
  89. AutoGLM_GUI/dual_model/__init__.py +0 -53
  90. AutoGLM_GUI/dual_model/decision_model.py +0 -664
  91. AutoGLM_GUI/dual_model/dual_agent.py +0 -917
  92. AutoGLM_GUI/dual_model/protocols.py +0 -354
  93. AutoGLM_GUI/dual_model/vision_model.py +0 -442
  94. AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
  95. AutoGLM_GUI/phone_agent_patches.py +0 -147
  96. AutoGLM_GUI/static/assets/chat-DwJpiAWf.js +0 -126
  97. AutoGLM_GUI/static/assets/dialog-B3uW4T8V.js +0 -45
  98. AutoGLM_GUI/static/assets/index-Cpv2gSF1.css +0 -1
  99. AutoGLM_GUI/static/assets/index-UYYauTly.js +0 -12
  100. AutoGLM_GUI/static/assets/workflows-Du_de-dt.js +0 -1
  101. autoglm_gui-1.4.1.dist-info/RECORD +0 -117
  102. mai_agent/base.py +0 -137
  103. mai_agent/mai_grounding_agent.py +0 -263
  104. mai_agent/mai_naivigation_agent.py +0 -526
  105. mai_agent/prompt.py +0 -148
  106. mai_agent/unified_memory.py +0 -67
  107. mai_agent/utils.py +0 -73
  108. phone_agent/__init__.py +0 -12
  109. phone_agent/actions/__init__.py +0 -5
  110. phone_agent/actions/handler.py +0 -400
  111. phone_agent/adb/screenshot.py +0 -108
  112. phone_agent/agent.py +0 -253
  113. phone_agent/agent_ios.py +0 -277
  114. phone_agent/config/__init__.py +0 -53
  115. phone_agent/config/apps_harmonyos.py +0 -256
  116. phone_agent/config/apps_ios.py +0 -339
  117. phone_agent/config/prompts.py +0 -80
  118. phone_agent/device_factory.py +0 -166
  119. phone_agent/hdc/__init__.py +0 -53
  120. phone_agent/hdc/connection.py +0 -384
  121. phone_agent/hdc/device.py +0 -269
  122. phone_agent/hdc/input.py +0 -145
  123. phone_agent/hdc/screenshot.py +0 -127
  124. phone_agent/model/__init__.py +0 -5
  125. phone_agent/model/client.py +0 -290
  126. phone_agent/xctest/__init__.py +0 -47
  127. phone_agent/xctest/connection.py +0 -379
  128. phone_agent/xctest/device.py +0 -472
  129. phone_agent/xctest/input.py +0 -311
  130. phone_agent/xctest/screenshot.py +0 -226
  131. {phone_agent/config → AutoGLM_GUI/adb}/apps.py +0 -0
  132. {phone_agent/config → AutoGLM_GUI}/i18n.py +0 -0
  133. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/WHEEL +0 -0
  134. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/entry_points.txt +0 -0
  135. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,317 +0,0 @@
1
- """双模型协作API端点"""
2
-
3
- import threading
4
- from typing import Optional
5
-
6
- from fastapi import APIRouter, HTTPException
7
- from fastapi.responses import StreamingResponse
8
- from pydantic import BaseModel
9
-
10
- from AutoGLM_GUI.logger import logger
11
- from AutoGLM_GUI.dual_model import (
12
- DecisionModelConfig,
13
- DualModelAgent,
14
- DualModelEvent,
15
- DualModelEventType,
16
- )
17
- from AutoGLM_GUI.dual_model.protocols import ThinkingMode
18
- from phone_agent.model import ModelConfig
19
-
20
- router = APIRouter(prefix="/api/dual", tags=["dual-model"])
21
-
22
- # 活跃的双模型会话 (device_id -> (agent, stop_event))
23
- _active_dual_sessions: dict[str, tuple[DualModelAgent, threading.Event]] = {}
24
- _active_dual_sessions_lock = threading.Lock()
25
-
26
-
27
- class DualModelInitRequest(BaseModel):
28
- """双模型初始化请求"""
29
-
30
- device_id: str
31
-
32
- # 决策大模型配置
33
- decision_base_url: str
34
- decision_api_key: str
35
- decision_model_name: str
36
-
37
- # 视觉小模型配置(复用现有配置)
38
- vision_base_url: Optional[str] = None
39
- vision_api_key: Optional[str] = None
40
- vision_model_name: Optional[str] = None
41
-
42
- max_steps: int = 50
43
- thinking_mode: str = "deep" # fast, deep, turbo
44
-
45
-
46
- class DualModelChatRequest(BaseModel):
47
- """双模型聊天请求"""
48
-
49
- device_id: str
50
- message: str
51
-
52
-
53
- class DualModelAbortRequest(BaseModel):
54
- """中止请求"""
55
-
56
- device_id: str
57
-
58
-
59
- class DualModelStatusResponse(BaseModel):
60
- """状态响应"""
61
-
62
- active: bool
63
- device_id: Optional[str] = None
64
- state: Optional[dict] = None
65
-
66
-
67
- @router.post("/init")
68
- def init_dual_model(request: DualModelInitRequest) -> dict:
69
- """初始化双模型Agent"""
70
- from AutoGLM_GUI.config_manager import config_manager
71
- from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
72
-
73
- device_id = request.device_id
74
- thinking_mode_map = {
75
- "fast": ThinkingMode.FAST,
76
- "deep": ThinkingMode.DEEP,
77
- "turbo": ThinkingMode.TURBO,
78
- }
79
- thinking_mode = thinking_mode_map.get(request.thinking_mode, ThinkingMode.DEEP)
80
- logger.info(f"初始化双模型Agent: {device_id}, 模式: {thinking_mode.value}")
81
-
82
- # 检查设备是否已有单模型Agent初始化
83
- manager = PhoneAgentManager.get_instance()
84
- if not manager.is_initialized(device_id):
85
- raise HTTPException(
86
- status_code=400, detail="设备尚未初始化单模型Agent,请先调用 /api/init"
87
- )
88
-
89
- # 获取有效配置
90
- effective_config = config_manager.get_effective_config()
91
-
92
- # 获取配置的默认最大步数
93
- max_steps = effective_config.default_max_steps
94
-
95
- # 获取视觉模型配置(优先级:请求参数 > 有效配置)
96
- vision_base_url = request.vision_base_url or effective_config.base_url
97
- vision_api_key = request.vision_api_key or effective_config.api_key
98
- vision_model_name = request.vision_model_name or effective_config.model_name
99
-
100
- if not vision_base_url:
101
- raise HTTPException(status_code=400, detail="视觉模型base_url未配置")
102
-
103
- # 创建配置
104
- decision_config = DecisionModelConfig(
105
- base_url=request.decision_base_url,
106
- api_key=request.decision_api_key,
107
- model_name=request.decision_model_name,
108
- thinking_mode=thinking_mode,
109
- )
110
-
111
- vision_config = ModelConfig(
112
- base_url=vision_base_url,
113
- api_key=vision_api_key,
114
- model_name=vision_model_name,
115
- )
116
-
117
- # 创建双模型Agent
118
- try:
119
- agent = DualModelAgent(
120
- decision_config=decision_config,
121
- vision_config=vision_config,
122
- device_id=device_id,
123
- max_steps=max_steps,
124
- thinking_mode=thinking_mode,
125
- )
126
-
127
- # 存储到活跃会话
128
- with _active_dual_sessions_lock:
129
- # 清理旧会话
130
- if device_id in _active_dual_sessions:
131
- old_agent, old_event = _active_dual_sessions[device_id]
132
- old_event.set()
133
-
134
- _active_dual_sessions[device_id] = (agent, threading.Event())
135
-
136
- logger.info(f"双模型Agent初始化成功: {device_id}")
137
-
138
- return {
139
- "success": True,
140
- "device_id": device_id,
141
- "message": "双模型Agent初始化成功",
142
- "decision_model": request.decision_model_name,
143
- "vision_model": vision_model_name,
144
- "thinking_mode": thinking_mode.value,
145
- }
146
-
147
- except Exception as e:
148
- logger.error(f"双模型Agent初始化失败: {e}")
149
- raise HTTPException(status_code=500, detail=str(e))
150
-
151
-
152
- @router.post("/chat/stream")
153
- def dual_model_chat_stream(request: DualModelChatRequest):
154
- """双模型聊天(SSE流式)"""
155
- device_id = request.device_id
156
-
157
- with _active_dual_sessions_lock:
158
- if device_id not in _active_dual_sessions:
159
- raise HTTPException(
160
- status_code=400, detail="双模型Agent未初始化,请先调用 /api/dual/init"
161
- )
162
- agent, stop_event = _active_dual_sessions[device_id]
163
-
164
- # 重置停止事件
165
- stop_event.clear()
166
-
167
- def event_generator():
168
- """SSE事件生成器"""
169
- try:
170
- logger.info(f"开始双模型任务: {request.message[:50]}...")
171
-
172
- # 在后台线程运行Agent
173
- result_holder: list[dict | None] = [None]
174
- error_holder: list[Exception | None] = [None]
175
-
176
- def run_agent():
177
- try:
178
- result = agent.run(request.message)
179
- result_holder[0] = result
180
- except Exception as e:
181
- error_holder[0] = e
182
-
183
- thread = threading.Thread(target=run_agent, daemon=True)
184
- thread.start()
185
-
186
- # 持续发送事件
187
- while thread.is_alive() or not agent.event_queue.empty():
188
- if stop_event.is_set():
189
- agent.abort()
190
- yield "event: aborted\n"
191
- yield 'data: {"type": "aborted", "message": "任务被用户中断"}\n\n'
192
- break
193
-
194
- # 获取事件
195
- try:
196
- events = agent.get_events(timeout=0.1)
197
- for event in events:
198
- yield event.to_sse()
199
-
200
- # 如果是完成或错误事件,结束循环
201
- if event.type in [
202
- DualModelEventType.TASK_COMPLETE,
203
- DualModelEventType.ERROR,
204
- ]:
205
- return
206
- except Exception:
207
- continue
208
-
209
- # 等待线程完成
210
- thread.join(timeout=5)
211
-
212
- # 检查错误
213
- if error_holder[0]:
214
- error_event = DualModelEvent(
215
- type=DualModelEventType.ERROR,
216
- data={"message": str(error_holder[0])},
217
- )
218
- yield error_event.to_sse()
219
-
220
- # 如果没有发送完成事件,发送一个
221
- if result_holder[0] and not stop_event.is_set():
222
- result = result_holder[0]
223
- if isinstance(result, dict):
224
- done_event = DualModelEvent(
225
- type=DualModelEventType.TASK_COMPLETE,
226
- data={
227
- "success": result.get("success", False),
228
- "message": result.get("message", ""),
229
- "steps": result.get("steps", 0),
230
- },
231
- )
232
- yield done_event.to_sse()
233
-
234
- except Exception as e:
235
- logger.exception(f"双模型任务异常: {e}")
236
- error_event = DualModelEvent(
237
- type=DualModelEventType.ERROR,
238
- data={"message": str(e)},
239
- )
240
- yield error_event.to_sse()
241
-
242
- return StreamingResponse(
243
- event_generator(),
244
- media_type="text/event-stream",
245
- headers={
246
- "Cache-Control": "no-cache",
247
- "Connection": "keep-alive",
248
- "X-Accel-Buffering": "no",
249
- },
250
- )
251
-
252
-
253
- @router.post("/chat/abort")
254
- def abort_dual_model_chat(request: DualModelAbortRequest) -> dict:
255
- """中止双模型聊天"""
256
- device_id = request.device_id
257
-
258
- with _active_dual_sessions_lock:
259
- if device_id in _active_dual_sessions:
260
- agent, stop_event = _active_dual_sessions[device_id]
261
- stop_event.set()
262
- agent.abort()
263
- logger.info(f"双模型任务已中止: {device_id}")
264
- return {"success": True, "message": "已发送中止信号"}
265
- else:
266
- return {"success": False, "message": "未找到活跃的双模型会话"}
267
-
268
-
269
- @router.get("/status")
270
- def get_dual_model_status(device_id: Optional[str] = None) -> DualModelStatusResponse:
271
- """获取双模型状态"""
272
- with _active_dual_sessions_lock:
273
- if device_id:
274
- if device_id in _active_dual_sessions:
275
- agent, _ = _active_dual_sessions[device_id]
276
- return DualModelStatusResponse(
277
- active=True,
278
- device_id=device_id,
279
- state=agent.get_state(),
280
- )
281
- else:
282
- return DualModelStatusResponse(active=False, device_id=device_id)
283
- else:
284
- # 返回所有活跃会话
285
- return DualModelStatusResponse(
286
- active=len(_active_dual_sessions) > 0,
287
- state={"active_devices": list(_active_dual_sessions.keys())},
288
- )
289
-
290
-
291
- @router.post("/reset")
292
- def reset_dual_model(request: DualModelAbortRequest) -> dict:
293
- """重置双模型Agent"""
294
- device_id = request.device_id
295
-
296
- with _active_dual_sessions_lock:
297
- if device_id in _active_dual_sessions:
298
- agent, stop_event = _active_dual_sessions[device_id]
299
- stop_event.set()
300
- agent.reset()
301
- logger.info(f"双模型Agent已重置: {device_id}")
302
- return {"success": True, "message": "双模型Agent已重置"}
303
- else:
304
- return {"success": False, "message": "未找到双模型会话"}
305
-
306
-
307
- @router.delete("/session/{device_id}")
308
- def delete_dual_model_session(device_id: str) -> dict:
309
- """删除双模型会话"""
310
- with _active_dual_sessions_lock:
311
- if device_id in _active_dual_sessions:
312
- agent, stop_event = _active_dual_sessions.pop(device_id)
313
- stop_event.set()
314
- logger.info(f"双模型会话已删除: {device_id}")
315
- return {"success": True, "message": "双模型会话已删除"}
316
- else:
317
- return {"success": False, "message": "未找到双模型会话"}
@@ -1,263 +0,0 @@
1
- """Device Protocol Adapter for phone_agent integration.
2
-
3
- This module provides an adapter that bridges DeviceProtocol implementations
4
- to the interface expected by phone_agent's DeviceFactory.
5
-
6
- The adapter allows injecting any DeviceProtocol implementation (ADB, Mock, Remote)
7
- into phone_agent without modifying the third-party code.
8
-
9
- Example:
10
- >>> from AutoGLM_GUI.device_adapter import inject_device_protocol
11
- >>> from AutoGLM_GUI.devices import MockDevice, ADBDevice
12
- >>>
13
- >>> # For testing: inject mock device
14
- >>> mock = MockDevice("mock_001", state_machine)
15
- >>> inject_device_protocol(lambda _: mock)
16
- >>>
17
- >>> # For production: inject ADB device
18
- >>> devices = {"phone_1": ADBDevice("emulator-5554")}
19
- >>> inject_device_protocol(lambda device_id: devices[device_id])
20
- """
21
-
22
- from typing import Callable
23
-
24
- import phone_agent.device_factory as device_factory_module
25
- from AutoGLM_GUI.device_protocol import DeviceProtocol, Screenshot
26
-
27
-
28
- class DeviceProtocolAdapter:
29
- """
30
- Adapter that bridges DeviceProtocol to phone_agent's DeviceFactory interface.
31
-
32
- This adapter wraps a DeviceProtocol getter function and exposes the same
33
- interface as phone_agent's DeviceFactory, allowing seamless injection.
34
-
35
- The adapter handles:
36
- - Routing device operations to the correct DeviceProtocol instance
37
- - Converting between DeviceProtocol and DeviceFactory method signatures
38
- - Managing device_id parameters (phone_agent passes device_id to each method)
39
- """
40
-
41
- def __init__(
42
- self,
43
- get_device: Callable[[str | None], DeviceProtocol],
44
- default_device_id: str | None = None,
45
- ):
46
- """
47
- Initialize the adapter.
48
-
49
- Args:
50
- get_device: Function that returns a DeviceProtocol given a device_id.
51
- If device_id is None, should return a default device.
52
- default_device_id: Default device ID to use when None is passed.
53
- """
54
- self._get_device = get_device
55
- self._default_device_id = default_device_id
56
- # For compatibility with code that checks device_type
57
- self.device_type = "protocol_adapter"
58
-
59
- def _device(self, device_id: str | None) -> DeviceProtocol:
60
- """Get device for the given ID."""
61
- effective_id = device_id or self._default_device_id
62
- return self._get_device(effective_id)
63
-
64
- # === Screenshot ===
65
- def get_screenshot(
66
- self, device_id: str | None = None, timeout: int = 10
67
- ) -> Screenshot:
68
- """Get screenshot from device."""
69
- return self._device(device_id).get_screenshot(timeout)
70
-
71
- # === Input Operations ===
72
- def tap(
73
- self, x: int, y: int, device_id: str | None = None, delay: float | None = None
74
- ) -> None:
75
- """Tap at coordinates."""
76
- self._device(device_id).tap(x, y, delay)
77
-
78
- def double_tap(
79
- self, x: int, y: int, device_id: str | None = None, delay: float | None = None
80
- ) -> None:
81
- """Double tap at coordinates."""
82
- self._device(device_id).double_tap(x, y, delay)
83
-
84
- def long_press(
85
- self,
86
- x: int,
87
- y: int,
88
- duration_ms: int = 3000,
89
- device_id: str | None = None,
90
- delay: float | None = None,
91
- ) -> None:
92
- """Long press at coordinates."""
93
- self._device(device_id).long_press(x, y, duration_ms, delay)
94
-
95
- def swipe(
96
- self,
97
- start_x: int,
98
- start_y: int,
99
- end_x: int,
100
- end_y: int,
101
- duration_ms: int | None = None,
102
- device_id: str | None = None,
103
- delay: float | None = None,
104
- ) -> None:
105
- """Swipe from start to end."""
106
- self._device(device_id).swipe(
107
- start_x, start_y, end_x, end_y, duration_ms, delay
108
- )
109
-
110
- def type_text(self, text: str, device_id: str | None = None) -> None:
111
- """Type text."""
112
- self._device(device_id).type_text(text)
113
-
114
- def clear_text(self, device_id: str | None = None) -> None:
115
- """Clear text."""
116
- self._device(device_id).clear_text()
117
-
118
- # === Navigation ===
119
- def back(self, device_id: str | None = None, delay: float | None = None) -> None:
120
- """Press back button."""
121
- self._device(device_id).back(delay)
122
-
123
- def home(self, device_id: str | None = None, delay: float | None = None) -> None:
124
- """Press home button."""
125
- self._device(device_id).home(delay)
126
-
127
- def launch_app(
128
- self, app_name: str, device_id: str | None = None, delay: float | None = None
129
- ) -> bool:
130
- """Launch an app."""
131
- return self._device(device_id).launch_app(app_name, delay)
132
-
133
- # === State Query ===
134
- def get_current_app(self, device_id: str | None = None) -> str:
135
- """Get current app name."""
136
- return self._device(device_id).get_current_app()
137
-
138
- # === Keyboard Management ===
139
- def detect_and_set_adb_keyboard(self, device_id: str | None = None) -> str:
140
- """Detect and set keyboard."""
141
- return self._device(device_id).detect_and_set_adb_keyboard()
142
-
143
- def restore_keyboard(self, ime: str, device_id: str | None = None) -> None:
144
- """Restore keyboard."""
145
- self._device(device_id).restore_keyboard(ime)
146
-
147
- # === Device Management ===
148
- def list_devices(self) -> list[str]:
149
- """
150
- List connected devices.
151
-
152
- Note: This is a simplified implementation. For full device listing,
153
- use ADBDeviceManager.list_devices() directly.
154
- """
155
- # This is called by some parts of phone_agent
156
- # Return the default device if available
157
- if self._default_device_id:
158
- return [self._default_device_id]
159
- return []
160
-
161
- def get_connection_class(self):
162
- """Not applicable for protocol adapter."""
163
- raise NotImplementedError(
164
- "Protocol adapter does not support get_connection_class. "
165
- "Use ADBDeviceManager for connection management."
166
- )
167
-
168
-
169
- # Store original factory for restoration
170
- _original_factory = None
171
-
172
-
173
- def inject_device_protocol(
174
- get_device: Callable[[str | None], DeviceProtocol],
175
- default_device_id: str | None = None,
176
- ) -> DeviceProtocolAdapter:
177
- """
178
- Inject a DeviceProtocol implementation into phone_agent.
179
-
180
- This replaces phone_agent's global _device_factory with an adapter
181
- that routes all device operations through the provided DeviceProtocol.
182
-
183
- Args:
184
- get_device: Function that returns a DeviceProtocol given a device_id.
185
- default_device_id: Default device ID when None is passed.
186
-
187
- Returns:
188
- The adapter instance (for inspection or further configuration).
189
-
190
- Example:
191
- >>> # Single mock device
192
- >>> mock = MockDevice("mock_001", state_machine)
193
- >>> inject_device_protocol(lambda _: mock)
194
- >>>
195
- >>> # Multiple devices
196
- >>> devices = {
197
- ... "phone_1": ADBDevice("emulator-5554"),
198
- ... "phone_2": RemoteDevice("phone_2", "http://remote:8080"),
199
- ... }
200
- >>> inject_device_protocol(lambda did: devices.get(did, devices["phone_1"]))
201
- """
202
- # TODO: 不应该依赖这种全部变量
203
- global _original_factory
204
-
205
- # Save original factory if not already saved
206
- if _original_factory is None:
207
- _original_factory = device_factory_module._device_factory
208
-
209
- # Create and inject adapter
210
- adapter = DeviceProtocolAdapter(get_device, default_device_id)
211
- device_factory_module._device_factory = adapter
212
-
213
- return adapter
214
-
215
-
216
- def restore_device_factory() -> None:
217
- """
218
- Restore the original device factory.
219
-
220
- Call this after testing to restore normal operation.
221
- """
222
- global _original_factory
223
-
224
- if _original_factory is not None:
225
- device_factory_module._device_factory = _original_factory
226
- _original_factory = None
227
-
228
-
229
- class DeviceProtocolContext:
230
- """
231
- Context manager for temporarily injecting a DeviceProtocol.
232
-
233
- Example:
234
- >>> with DeviceProtocolContext(lambda _: mock_device):
235
- ... agent.run("test instruction")
236
- >>> # Original factory is automatically restored
237
- """
238
-
239
- def __init__(
240
- self,
241
- get_device: Callable[[str | None], DeviceProtocol],
242
- default_device_id: str | None = None,
243
- ):
244
- """
245
- Initialize context.
246
-
247
- Args:
248
- get_device: Function that returns a DeviceProtocol given a device_id.
249
- default_device_id: Default device ID when None is passed.
250
- """
251
- self._get_device = get_device
252
- self._default_device_id = default_device_id
253
- self._original_factory = None
254
-
255
- def __enter__(self) -> DeviceProtocolAdapter:
256
- """Enter context and inject adapter."""
257
- self._original_factory = device_factory_module._device_factory
258
- return inject_device_protocol(self._get_device, self._default_device_id)
259
-
260
- def __exit__(self, exc_type, exc_val, exc_tb) -> None:
261
- """Exit context and restore original factory."""
262
- device_factory_module._device_factory = self._original_factory
263
- return None
@@ -1,53 +0,0 @@
1
- """
2
- 双模型协作模块
3
-
4
- 大模型(GLM-4.7): 负责任务分析、决策制定、内容生成
5
- 小模型(autoglm-phone): 负责屏幕识别、动作执行
6
- """
7
-
8
- from .decision_model import (
9
- DecisionModel,
10
- Decision,
11
- TaskPlan,
12
- ActionSequence,
13
- ActionStep,
14
- )
15
- from .vision_model import VisionModel, ScreenDescription, ExecutionResult
16
- from .dual_agent import DualModelAgent, DualModelCallbacks
17
- from .protocols import (
18
- DualModelConfig,
19
- DecisionModelConfig,
20
- DualModelState,
21
- DualModelEvent,
22
- DualModelEventType,
23
- ModelRole,
24
- ModelStage,
25
- ThinkingMode,
26
- DECISION_SYSTEM_PROMPT,
27
- DECISION_SYSTEM_PROMPT_TURBO,
28
- VISION_DESCRIBE_PROMPT,
29
- )
30
-
31
- __all__ = [
32
- "DecisionModel",
33
- "Decision",
34
- "TaskPlan",
35
- "ActionSequence",
36
- "ActionStep",
37
- "VisionModel",
38
- "ScreenDescription",
39
- "ExecutionResult",
40
- "DualModelAgent",
41
- "DualModelCallbacks",
42
- "DualModelConfig",
43
- "DecisionModelConfig",
44
- "DualModelState",
45
- "DualModelEvent",
46
- "DualModelEventType",
47
- "ModelRole",
48
- "ModelStage",
49
- "ThinkingMode",
50
- "DECISION_SYSTEM_PROMPT",
51
- "DECISION_SYSTEM_PROMPT_TURBO",
52
- "VISION_DESCRIBE_PROMPT",
53
- ]