autoglm-gui 1.4.0__py3-none-any.whl → 1.4.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 (57) hide show
  1. AutoGLM_GUI/__main__.py +0 -4
  2. AutoGLM_GUI/adb_plus/qr_pair.py +8 -8
  3. AutoGLM_GUI/agents/__init__.py +20 -0
  4. AutoGLM_GUI/agents/factory.py +160 -0
  5. AutoGLM_GUI/agents/mai_adapter.py +627 -0
  6. AutoGLM_GUI/agents/protocols.py +23 -0
  7. AutoGLM_GUI/api/__init__.py +48 -7
  8. AutoGLM_GUI/api/agents.py +61 -17
  9. AutoGLM_GUI/api/devices.py +12 -18
  10. AutoGLM_GUI/api/dual_model.py +15 -9
  11. AutoGLM_GUI/api/health.py +13 -0
  12. AutoGLM_GUI/api/layered_agent.py +239 -166
  13. AutoGLM_GUI/api/mcp.py +11 -10
  14. AutoGLM_GUI/api/version.py +23 -10
  15. AutoGLM_GUI/api/workflows.py +2 -1
  16. AutoGLM_GUI/config_manager.py +55 -1
  17. AutoGLM_GUI/device_adapter.py +263 -0
  18. AutoGLM_GUI/device_protocol.py +266 -0
  19. AutoGLM_GUI/devices/__init__.py +49 -0
  20. AutoGLM_GUI/devices/adb_device.py +205 -0
  21. AutoGLM_GUI/devices/mock_device.py +183 -0
  22. AutoGLM_GUI/devices/remote_device.py +172 -0
  23. AutoGLM_GUI/dual_model/decision_model.py +4 -4
  24. AutoGLM_GUI/exceptions.py +3 -3
  25. AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +2 -2
  26. AutoGLM_GUI/metrics.py +13 -20
  27. AutoGLM_GUI/phone_agent_manager.py +219 -134
  28. AutoGLM_GUI/phone_agent_patches.py +2 -1
  29. AutoGLM_GUI/platform_utils.py +5 -2
  30. AutoGLM_GUI/schemas.py +47 -0
  31. AutoGLM_GUI/scrcpy_stream.py +17 -13
  32. AutoGLM_GUI/server.py +3 -1
  33. AutoGLM_GUI/socketio_server.py +16 -4
  34. AutoGLM_GUI/state.py +10 -30
  35. AutoGLM_GUI/static/assets/{about-DeclntHg.js → about-_XNhzQZX.js} +1 -1
  36. AutoGLM_GUI/static/assets/chat-DwJpiAWf.js +126 -0
  37. AutoGLM_GUI/static/assets/{dialog-BfdcBs1x.js → dialog-B3uW4T8V.js} +3 -3
  38. AutoGLM_GUI/static/assets/index-Cpv2gSF1.css +1 -0
  39. AutoGLM_GUI/static/assets/{index-zQ4KKDHt.js → index-Cy8TmmHV.js} +1 -1
  40. AutoGLM_GUI/static/assets/{index-DHF1NZh0.js → index-UYYauTly.js} +6 -6
  41. AutoGLM_GUI/static/assets/{workflows-xiplap-r.js → workflows-Du_de-dt.js} +1 -1
  42. AutoGLM_GUI/static/index.html +2 -2
  43. AutoGLM_GUI/types.py +125 -0
  44. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/METADATA +83 -4
  45. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/RECORD +54 -37
  46. mai_agent/base.py +137 -0
  47. mai_agent/mai_grounding_agent.py +263 -0
  48. mai_agent/mai_naivigation_agent.py +526 -0
  49. mai_agent/prompt.py +148 -0
  50. mai_agent/unified_memory.py +67 -0
  51. mai_agent/utils.py +73 -0
  52. AutoGLM_GUI/config.py +0 -23
  53. AutoGLM_GUI/static/assets/chat-Iut2yhSw.js +0 -125
  54. AutoGLM_GUI/static/assets/index-5hCCwHA7.css +0 -1
  55. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/WHEEL +0 -0
  56. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/entry_points.txt +0 -0
  57. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,49 @@
1
+ """Device implementations for the DeviceProtocol interface.
2
+
3
+ This package provides concrete implementations of DeviceProtocol:
4
+ - ADBDevice: Local ADB subprocess calls
5
+ - MockDevice: State machine driven mock for testing
6
+ - RemoteDevice: HTTP client for remote device agents
7
+
8
+ Example:
9
+ >>> from AutoGLM_GUI.devices import ADBDevice, RemoteDevice, get_device_manager
10
+ >>>
11
+ >>> # Local ADB device
12
+ >>> device = ADBDevice("emulator-5554")
13
+ >>> device.tap(100, 200)
14
+ >>>
15
+ >>> # Remote device via HTTP
16
+ >>> remote = RemoteDevice("phone_001", "http://device-agent:8001")
17
+ >>> remote.tap(100, 200)
18
+ """
19
+
20
+ from AutoGLM_GUI.devices.adb_device import ADBDevice, ADBDeviceManager
21
+ from AutoGLM_GUI.devices.mock_device import MockDevice
22
+ from AutoGLM_GUI.devices.remote_device import RemoteDevice, RemoteDeviceManager
23
+
24
+ _device_manager: "ADBDeviceManager | None" = None
25
+
26
+
27
+ def get_device_manager() -> ADBDeviceManager:
28
+ """Get the global device manager instance."""
29
+ global _device_manager
30
+ if _device_manager is None:
31
+ _device_manager = ADBDeviceManager()
32
+ return _device_manager
33
+
34
+
35
+ def set_device_manager(manager: "ADBDeviceManager") -> None:
36
+ """Set the global device manager instance (useful for testing)."""
37
+ global _device_manager
38
+ _device_manager = manager
39
+
40
+
41
+ __all__ = [
42
+ "ADBDevice",
43
+ "ADBDeviceManager",
44
+ "MockDevice",
45
+ "RemoteDevice",
46
+ "RemoteDeviceManager",
47
+ "get_device_manager",
48
+ "set_device_manager",
49
+ ]
@@ -0,0 +1,205 @@
1
+ """ADB Device implementation of DeviceProtocol.
2
+
3
+ This module wraps the existing phone_agent.adb module to provide
4
+ a DeviceProtocol-compliant implementation.
5
+ """
6
+
7
+ from phone_agent import adb
8
+ from phone_agent.adb import ADBConnection
9
+
10
+ from AutoGLM_GUI.device_protocol import (
11
+ DeviceInfo,
12
+ DeviceManagerProtocol,
13
+ DeviceProtocol,
14
+ Screenshot,
15
+ )
16
+
17
+
18
+ class ADBDevice:
19
+ """
20
+ ADB device implementation using local subprocess calls.
21
+
22
+ Wraps the existing phone_agent.adb module to provide a clean
23
+ DeviceProtocol interface.
24
+
25
+ Example:
26
+ >>> device = ADBDevice("emulator-5554")
27
+ >>> screenshot = device.get_screenshot()
28
+ >>> device.tap(100, 200)
29
+ >>> device.swipe(100, 200, 300, 400)
30
+ """
31
+
32
+ def __init__(self, device_id: str):
33
+ """
34
+ Initialize ADB device.
35
+
36
+ Args:
37
+ device_id: ADB device ID (e.g., "emulator-5554", "192.168.1.100:5555").
38
+ """
39
+ self._device_id = device_id
40
+
41
+ @property
42
+ def device_id(self) -> str:
43
+ """Unique device identifier."""
44
+ return self._device_id
45
+
46
+ # === Screenshot ===
47
+ def get_screenshot(self, timeout: int = 10) -> Screenshot:
48
+ """Capture current screen."""
49
+ result = adb.get_screenshot(self._device_id, timeout)
50
+ return Screenshot(
51
+ base64_data=result.base64_data,
52
+ width=result.width,
53
+ height=result.height,
54
+ is_sensitive=result.is_sensitive,
55
+ )
56
+
57
+ # === Input Operations ===
58
+ def tap(self, x: int, y: int, delay: float | None = None) -> None:
59
+ """Tap at specified coordinates."""
60
+ adb.tap(x, y, self._device_id, delay)
61
+
62
+ def double_tap(self, x: int, y: int, delay: float | None = None) -> None:
63
+ """Double tap at specified coordinates."""
64
+ adb.double_tap(x, y, self._device_id, delay)
65
+
66
+ def long_press(
67
+ self, x: int, y: int, duration_ms: int = 3000, delay: float | None = None
68
+ ) -> None:
69
+ """Long press at specified coordinates."""
70
+ adb.long_press(x, y, duration_ms, self._device_id, delay)
71
+
72
+ def swipe(
73
+ self,
74
+ start_x: int,
75
+ start_y: int,
76
+ end_x: int,
77
+ end_y: int,
78
+ duration_ms: int | None = None,
79
+ delay: float | None = None,
80
+ ) -> None:
81
+ """Swipe from start to end coordinates."""
82
+ adb.swipe(start_x, start_y, end_x, end_y, duration_ms, self._device_id, delay)
83
+
84
+ def type_text(self, text: str) -> None:
85
+ """Type text into the currently focused input field."""
86
+ adb.type_text(text, self._device_id)
87
+
88
+ def clear_text(self) -> None:
89
+ """Clear text in the currently focused input field."""
90
+ adb.clear_text(self._device_id)
91
+
92
+ # === Navigation ===
93
+ def back(self, delay: float | None = None) -> None:
94
+ """Press the back button."""
95
+ adb.back(self._device_id, delay)
96
+
97
+ def home(self, delay: float | None = None) -> None:
98
+ """Press the home button."""
99
+ adb.home(self._device_id, delay)
100
+
101
+ def launch_app(self, app_name: str, delay: float | None = None) -> bool:
102
+ """Launch an app by name."""
103
+ return adb.launch_app(app_name, self._device_id, delay)
104
+
105
+ # === State Query ===
106
+ def get_current_app(self) -> str:
107
+ """Get the currently focused app name."""
108
+ return adb.get_current_app(self._device_id)
109
+
110
+ # === Keyboard Management ===
111
+ def detect_and_set_adb_keyboard(self) -> str:
112
+ """Detect current keyboard and switch to ADB Keyboard if needed."""
113
+ return adb.detect_and_set_adb_keyboard(self._device_id)
114
+
115
+ def restore_keyboard(self, ime: str) -> None:
116
+ """Restore the original keyboard IME."""
117
+ adb.restore_keyboard(ime, self._device_id)
118
+
119
+
120
+ # Verify ADBDevice implements DeviceProtocol
121
+ assert isinstance(ADBDevice("test"), DeviceProtocol)
122
+
123
+
124
+ class ADBDeviceManager:
125
+ """
126
+ ADB device manager implementation.
127
+
128
+ Manages multiple ADB devices and provides DeviceProtocol instances.
129
+
130
+ Example:
131
+ >>> manager = ADBDeviceManager()
132
+ >>> devices = manager.list_devices()
133
+ >>> device = manager.get_device("emulator-5554")
134
+ >>> device.tap(100, 200)
135
+ """
136
+
137
+ def __init__(self, adb_path: str = "adb"):
138
+ """
139
+ Initialize the device manager.
140
+
141
+ Args:
142
+ adb_path: Path to ADB executable.
143
+ """
144
+ self._connection = ADBConnection(adb_path)
145
+ self._devices: dict[str, ADBDevice] = {}
146
+
147
+ def list_devices(self) -> list[DeviceInfo]:
148
+ """List all available devices."""
149
+ adb_devices = self._connection.list_devices()
150
+ result = []
151
+
152
+ for dev in adb_devices:
153
+ result.append(
154
+ DeviceInfo(
155
+ device_id=dev.device_id,
156
+ status="online" if dev.status == "device" else dev.status,
157
+ model=dev.model,
158
+ platform="android",
159
+ connection_type=dev.connection_type.value,
160
+ )
161
+ )
162
+
163
+ return result
164
+
165
+ def get_device(self, device_id: str) -> ADBDevice:
166
+ """
167
+ Get a device instance by ID.
168
+
169
+ Args:
170
+ device_id: The device ID.
171
+
172
+ Returns:
173
+ ADBDevice instance.
174
+
175
+ Raises:
176
+ KeyError: If device not found or offline.
177
+ """
178
+ # Check if device exists and is online
179
+ devices = self.list_devices()
180
+ device_info = next((d for d in devices if d.device_id == device_id), None)
181
+
182
+ if device_info is None:
183
+ raise KeyError(f"Device '{device_id}' not found")
184
+ if device_info.status != "online":
185
+ raise KeyError(f"Device '{device_id}' is {device_info.status}")
186
+
187
+ # Cache and return device instance
188
+ if device_id not in self._devices:
189
+ self._devices[device_id] = ADBDevice(device_id)
190
+
191
+ return self._devices[device_id]
192
+
193
+ def connect(self, address: str, timeout: int = 10) -> tuple[bool, str]:
194
+ """Connect to a remote device."""
195
+ return self._connection.connect(address, timeout)
196
+
197
+ def disconnect(self, device_id: str) -> tuple[bool, str]:
198
+ """Disconnect from a device."""
199
+ # Remove from cache
200
+ self._devices.pop(device_id, None)
201
+ return self._connection.disconnect(device_id)
202
+
203
+
204
+ # Verify ADBDeviceManager implements DeviceManagerProtocol
205
+ assert isinstance(ADBDeviceManager(), DeviceManagerProtocol)
@@ -0,0 +1,183 @@
1
+ """Mock Device implementation for testing.
2
+
3
+ This module provides a MockDevice that routes all operations through
4
+ a state machine, enabling controlled testing without real devices.
5
+ """
6
+
7
+ from typing import TYPE_CHECKING
8
+
9
+ from AutoGLM_GUI.device_protocol import (
10
+ DeviceInfo,
11
+ Screenshot,
12
+ )
13
+
14
+ if TYPE_CHECKING:
15
+ from tests.integration.state_machine import StateMachine
16
+
17
+
18
+ class MockDevice:
19
+ """
20
+ Mock device implementation driven by a state machine.
21
+
22
+ All operations are routed through the state machine, which controls
23
+ screenshots and validates actions (tap coordinates, etc.).
24
+
25
+ Example:
26
+ >>> from tests.integration.state_machine import load_test_case
27
+ >>> state_machine, instruction, max_steps = load_test_case("scenario.yaml")
28
+ >>>
29
+ >>> device = MockDevice("mock_001", state_machine)
30
+ >>> screenshot = device.get_screenshot() # Returns current state's screenshot
31
+ >>> device.tap(100, 200) # State machine validates and transitions
32
+ """
33
+
34
+ def __init__(self, device_id: str, state_machine: "StateMachine"):
35
+ """
36
+ Initialize mock device.
37
+
38
+ Args:
39
+ device_id: Mock device ID.
40
+ state_machine: State machine that controls test flow.
41
+ """
42
+ self._device_id = device_id
43
+ self._state_machine = state_machine
44
+
45
+ @property
46
+ def device_id(self) -> str:
47
+ """Unique device identifier."""
48
+ return self._device_id
49
+
50
+ @property
51
+ def state_machine(self) -> "StateMachine":
52
+ """Get the underlying state machine."""
53
+ return self._state_machine
54
+
55
+ # === Screenshot ===
56
+ def get_screenshot(self, timeout: int = 10) -> Screenshot:
57
+ """Get screenshot from current state."""
58
+ result = self._state_machine.get_current_screenshot()
59
+ return Screenshot(
60
+ base64_data=result.base64_data,
61
+ width=result.width,
62
+ height=result.height,
63
+ )
64
+
65
+ # === Input Operations ===
66
+ def tap(self, x: int, y: int, delay: float | None = None) -> None:
67
+ """Handle tap action through state machine."""
68
+ self._state_machine.handle_tap(x, y)
69
+
70
+ def double_tap(self, x: int, y: int, delay: float | None = None) -> None:
71
+ """Handle double tap (treated as single tap)."""
72
+ self._state_machine.handle_tap(x, y)
73
+
74
+ def long_press(
75
+ self, x: int, y: int, duration_ms: int = 3000, delay: float | None = None
76
+ ) -> None:
77
+ """Handle long press (treated as tap for testing)."""
78
+ self._state_machine.handle_tap(x, y)
79
+
80
+ def swipe(
81
+ self,
82
+ start_x: int,
83
+ start_y: int,
84
+ end_x: int,
85
+ end_y: int,
86
+ duration_ms: int | None = None,
87
+ delay: float | None = None,
88
+ ) -> None:
89
+ """Handle swipe action."""
90
+ self._state_machine.handle_swipe(start_x, start_y, end_x, end_y)
91
+
92
+ def type_text(self, text: str) -> None:
93
+ """Handle text input (no-op in testing)."""
94
+ pass
95
+
96
+ def clear_text(self) -> None:
97
+ """Handle text clear (no-op in testing)."""
98
+ pass
99
+
100
+ # === Navigation ===
101
+ def back(self, delay: float | None = None) -> None:
102
+ """Handle back button (no-op in testing)."""
103
+ pass
104
+
105
+ def home(self, delay: float | None = None) -> None:
106
+ """Handle home button (no-op in testing)."""
107
+ pass
108
+
109
+ def launch_app(self, app_name: str, delay: float | None = None) -> bool:
110
+ """Handle app launch (always succeeds in testing)."""
111
+ return True
112
+
113
+ # === State Query ===
114
+ def get_current_app(self) -> str:
115
+ """Get current app name from the current state."""
116
+ return self._state_machine.current_state.current_app
117
+
118
+ # === Keyboard Management ===
119
+ def detect_and_set_adb_keyboard(self) -> str:
120
+ """Mock keyboard detection."""
121
+ return "com.mock.keyboard"
122
+
123
+ def restore_keyboard(self, ime: str) -> None:
124
+ """Mock keyboard restore."""
125
+ pass
126
+
127
+
128
+ class MockDeviceManager:
129
+ """
130
+ Mock device manager for testing.
131
+
132
+ Provides a single mock device backed by a state machine.
133
+
134
+ Example:
135
+ >>> state_machine, _, _ = load_test_case("scenario.yaml")
136
+ >>> manager = MockDeviceManager(state_machine)
137
+ >>> device = manager.get_device("mock_001")
138
+ """
139
+
140
+ def __init__(
141
+ self,
142
+ state_machine: "StateMachine",
143
+ device_id: str = "mock_device_001",
144
+ ):
145
+ """
146
+ Initialize mock device manager.
147
+
148
+ Args:
149
+ state_machine: State machine for the mock device.
150
+ device_id: ID for the mock device.
151
+ """
152
+ self._device_id = device_id
153
+ self._device = MockDevice(device_id, state_machine)
154
+
155
+ def list_devices(self) -> list[DeviceInfo]:
156
+ """List the mock device."""
157
+ return [
158
+ DeviceInfo(
159
+ device_id=self._device_id,
160
+ status="online",
161
+ model="MockPhone",
162
+ platform="android",
163
+ connection_type="mock",
164
+ )
165
+ ]
166
+
167
+ def get_device(self, device_id: str) -> MockDevice:
168
+ """Get the mock device."""
169
+ if device_id != self._device_id:
170
+ raise KeyError(f"Device '{device_id}' not found")
171
+ return self._device
172
+
173
+ def connect(self, address: str, timeout: int = 10) -> tuple[bool, str]:
174
+ """Mock connect (always succeeds)."""
175
+ return True, f"Connected to {address}"
176
+
177
+ def disconnect(self, device_id: str) -> tuple[bool, str]:
178
+ """Mock disconnect (always succeeds)."""
179
+ return True, f"Disconnected from {device_id}"
180
+
181
+
182
+ # Verify MockDeviceManager implements DeviceManagerProtocol
183
+ # Same issue as above - can't verify at import time
@@ -0,0 +1,172 @@
1
+ """Remote Device implementation using HTTP.
2
+
3
+ This module provides a RemoteDevice that connects to a Device Agent
4
+ via HTTP, allowing remote control of devices.
5
+ """
6
+
7
+ import httpx
8
+
9
+ from AutoGLM_GUI.device_protocol import DeviceInfo, Screenshot
10
+
11
+
12
+ class RemoteDevice:
13
+ """
14
+ Remote device implementation using HTTP.
15
+
16
+ Connects to a Device Agent server that handles actual device operations.
17
+ The server decides the implementation (ADB, Accessibility, Mock, etc.).
18
+
19
+ Example:
20
+ >>> device = RemoteDevice("phone_001", "http://localhost:8001")
21
+ >>> screenshot = device.get_screenshot()
22
+ >>> device.tap(100, 200)
23
+ """
24
+
25
+ def __init__(self, device_id: str, base_url: str, timeout: float = 30.0):
26
+ self._device_id = device_id
27
+ self._base_url = base_url.rstrip("/")
28
+ self._client = httpx.Client(timeout=timeout)
29
+
30
+ @property
31
+ def device_id(self) -> str:
32
+ return self._device_id
33
+
34
+ def _post(self, endpoint: str, json: dict | None = None) -> dict:
35
+ """POST request helper."""
36
+ url = f"{self._base_url}/device/{self._device_id}{endpoint}"
37
+ resp = self._client.post(url, json=json or {})
38
+ resp.raise_for_status()
39
+ return resp.json()
40
+
41
+ def _get(self, endpoint: str) -> dict:
42
+ """GET request helper."""
43
+ url = f"{self._base_url}/device/{self._device_id}{endpoint}"
44
+ resp = self._client.get(url)
45
+ resp.raise_for_status()
46
+ return resp.json()
47
+
48
+ def get_screenshot(self, timeout: int = 10) -> Screenshot:
49
+ data = self._post("/screenshot", {"timeout": timeout})
50
+ return Screenshot(
51
+ base64_data=data["base64_data"],
52
+ width=data["width"],
53
+ height=data["height"],
54
+ is_sensitive=data.get("is_sensitive", False),
55
+ )
56
+
57
+ def tap(self, x: int, y: int, delay: float | None = None) -> None:
58
+ self._post("/tap", {"x": x, "y": y, "delay": delay})
59
+
60
+ def double_tap(self, x: int, y: int, delay: float | None = None) -> None:
61
+ self._post("/double_tap", {"x": x, "y": y, "delay": delay})
62
+
63
+ def long_press(
64
+ self, x: int, y: int, duration_ms: int = 3000, delay: float | None = None
65
+ ) -> None:
66
+ self._post(
67
+ "/long_press", {"x": x, "y": y, "duration_ms": duration_ms, "delay": delay}
68
+ )
69
+
70
+ def swipe(
71
+ self,
72
+ start_x: int,
73
+ start_y: int,
74
+ end_x: int,
75
+ end_y: int,
76
+ duration_ms: int | None = None,
77
+ delay: float | None = None,
78
+ ) -> None:
79
+ self._post(
80
+ "/swipe",
81
+ {
82
+ "start_x": start_x,
83
+ "start_y": start_y,
84
+ "end_x": end_x,
85
+ "end_y": end_y,
86
+ "duration_ms": duration_ms,
87
+ "delay": delay,
88
+ },
89
+ )
90
+
91
+ def type_text(self, text: str) -> None:
92
+ self._post("/type_text", {"text": text})
93
+
94
+ def clear_text(self) -> None:
95
+ self._post("/clear_text")
96
+
97
+ def back(self, delay: float | None = None) -> None:
98
+ self._post("/back", {"delay": delay})
99
+
100
+ def home(self, delay: float | None = None) -> None:
101
+ self._post("/home", {"delay": delay})
102
+
103
+ def launch_app(self, app_name: str, delay: float | None = None) -> bool:
104
+ data = self._post("/launch_app", {"app_name": app_name, "delay": delay})
105
+ return data.get("success", True)
106
+
107
+ def get_current_app(self) -> str:
108
+ data = self._get("/current_app")
109
+ return data["app_name"]
110
+
111
+ def detect_and_set_adb_keyboard(self) -> str:
112
+ data = self._post("/detect_keyboard")
113
+ return data.get("original_ime", "")
114
+
115
+ def restore_keyboard(self, ime: str) -> None:
116
+ self._post("/restore_keyboard", {"ime": ime})
117
+
118
+ def close(self) -> None:
119
+ """Close the HTTP client."""
120
+ self._client.close()
121
+
122
+ def __enter__(self):
123
+ return self
124
+
125
+ def __exit__(self, exc_type, exc_val, exc_tb):
126
+ self.close()
127
+
128
+
129
+ class RemoteDeviceManager:
130
+ """
131
+ Remote device manager using HTTP.
132
+
133
+ Manages connections to a Device Agent server.
134
+ """
135
+
136
+ def __init__(self, base_url: str, timeout: float = 30.0):
137
+ self._base_url = base_url.rstrip("/")
138
+ self._timeout = timeout
139
+ self._client = httpx.Client(timeout=timeout)
140
+ self._devices: dict[str, RemoteDevice] = {}
141
+
142
+ def list_devices(self) -> list[DeviceInfo]:
143
+ resp = self._client.get(f"{self._base_url}/devices")
144
+ resp.raise_for_status()
145
+ return [DeviceInfo(**d) for d in resp.json()]
146
+
147
+ def get_device(self, device_id: str) -> RemoteDevice:
148
+ if device_id not in self._devices:
149
+ self._devices[device_id] = RemoteDevice(
150
+ device_id, self._base_url, self._timeout
151
+ )
152
+ return self._devices[device_id]
153
+
154
+ def connect(self, address: str, timeout: int = 10) -> tuple[bool, str]:
155
+ resp = self._client.post(
156
+ f"{self._base_url}/connect", json={"address": address, "timeout": timeout}
157
+ )
158
+ data = resp.json()
159
+ return data.get("success", False), data.get("message", "")
160
+
161
+ def disconnect(self, device_id: str) -> tuple[bool, str]:
162
+ self._devices.pop(device_id, None)
163
+ resp = self._client.post(
164
+ f"{self._base_url}/disconnect", json={"device_id": device_id}
165
+ )
166
+ data = resp.json()
167
+ return data.get("success", True), data.get("message", "Disconnected")
168
+
169
+ def close(self) -> None:
170
+ for device in self._devices.values():
171
+ device.close()
172
+ self._client.close()
@@ -49,8 +49,8 @@ class ActionStep:
49
49
  need_generate: bool = False
50
50
  direction: Optional[str] = None
51
51
 
52
- def to_dict(self) -> dict:
53
- result = {"action": self.action, "target": self.target}
52
+ def to_dict(self) -> dict[str, str | bool]:
53
+ result: dict[str, str | bool] = {"action": self.action, "target": self.target}
54
54
  if self.content:
55
55
  result["content"] = self.content
56
56
  if self.need_generate:
@@ -127,7 +127,7 @@ class DecisionModel:
127
127
  self.client = OpenAI(
128
128
  base_url=config.base_url,
129
129
  api_key=config.api_key,
130
- )
130
+ ) # type: ignore[call-arg]
131
131
  self.model_name = config.model_name
132
132
  self.conversation_history: list[dict] = []
133
133
  self.current_task: str = ""
@@ -159,7 +159,7 @@ class DecisionModel:
159
159
  try:
160
160
  response = self.client.chat.completions.create(
161
161
  model=self.model_name,
162
- messages=messages,
162
+ messages=messages, # type: ignore[arg-type]
163
163
  max_tokens=self.config.max_tokens,
164
164
  temperature=self.config.temperature,
165
165
  stream=True,
AutoGLM_GUI/exceptions.py CHANGED
@@ -79,9 +79,9 @@ class AgentInitializationError(Exception):
79
79
  How to fix:
80
80
  1. Check configuration:
81
81
  >>> from AutoGLM_GUI.config_manager import config_manager
82
- >>> config = config_manager.get_effective_config()
83
- >>> print(f"base_url: {config.base_url}")
84
- >>> print(f"model_name: {config.model_name}")
82
+ >>> effective_config = config_manager.get_effective_config()
83
+ >>> print(f"base_url: {effective_config.base_url}")
84
+ >>> print(f"model_name: {effective_config.model_name}")
85
85
 
86
86
  2. Set configuration:
87
87
  >>> via API: POST /api/config {"base_url": "...", "model_name": "...", "api_key": "..."}
@@ -8,8 +8,8 @@ from phone_agent.actions.handler import ActionHandler
8
8
  from phone_agent.model import ModelConfig
9
9
 
10
10
  from AutoGLM_GUI.logger import logger
11
- from AutoGLM_GUI.mai_ui.mai_navigation_agent import MAIUINaivigationAgent
12
- from AutoGLM_GUI.mai_ui_adapter.action_adapter import MAIUIActionAdapter
11
+ from AutoGLM_GUI.mai_ui.mai_navigation_agent import MAIUINaivigationAgent # type: ignore[import-not-found]
12
+ from AutoGLM_GUI.mai_ui_adapter.action_adapter import MAIUIActionAdapter # type: ignore[import-not-found]
13
13
 
14
14
 
15
15
  @dataclass