autoglm-gui 1.3.1__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 (61) 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 +50 -7
  8. AutoGLM_GUI/api/agents.py +61 -19
  9. AutoGLM_GUI/api/devices.py +12 -18
  10. AutoGLM_GUI/api/dual_model.py +24 -17
  11. AutoGLM_GUI/api/health.py +13 -0
  12. AutoGLM_GUI/api/layered_agent.py +659 -0
  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 +56 -24
  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/dual_model/protocols.py +3 -3
  25. AutoGLM_GUI/exceptions.py +3 -3
  26. AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +291 -0
  27. AutoGLM_GUI/metrics.py +13 -20
  28. AutoGLM_GUI/phone_agent_manager.py +219 -134
  29. AutoGLM_GUI/phone_agent_patches.py +2 -1
  30. AutoGLM_GUI/platform_utils.py +5 -2
  31. AutoGLM_GUI/prompts.py +6 -1
  32. AutoGLM_GUI/schemas.py +45 -14
  33. AutoGLM_GUI/scrcpy_stream.py +17 -13
  34. AutoGLM_GUI/server.py +3 -1
  35. AutoGLM_GUI/socketio_server.py +16 -4
  36. AutoGLM_GUI/state.py +10 -30
  37. AutoGLM_GUI/static/assets/{about-Cj6QXqMf.js → about-_XNhzQZX.js} +1 -1
  38. AutoGLM_GUI/static/assets/chat-DwJpiAWf.js +126 -0
  39. AutoGLM_GUI/static/assets/{dialog-CxJlnjzH.js → dialog-B3uW4T8V.js} +3 -3
  40. AutoGLM_GUI/static/assets/index-Cpv2gSF1.css +1 -0
  41. AutoGLM_GUI/static/assets/{index-C_B-Arvf.js → index-Cy8TmmHV.js} +1 -1
  42. AutoGLM_GUI/static/assets/{index-CxJQuE4y.js → index-UYYauTly.js} +6 -6
  43. AutoGLM_GUI/static/assets/{workflows-BTiGCNI0.js → workflows-Du_de-dt.js} +1 -1
  44. AutoGLM_GUI/static/index.html +2 -2
  45. AutoGLM_GUI/types.py +125 -0
  46. {autoglm_gui-1.3.1.dist-info → autoglm_gui-1.4.1.dist-info}/METADATA +147 -65
  47. {autoglm_gui-1.3.1.dist-info → autoglm_gui-1.4.1.dist-info}/RECORD +58 -39
  48. mai_agent/base.py +137 -0
  49. mai_agent/mai_grounding_agent.py +263 -0
  50. mai_agent/mai_naivigation_agent.py +526 -0
  51. mai_agent/prompt.py +148 -0
  52. mai_agent/unified_memory.py +67 -0
  53. mai_agent/utils.py +73 -0
  54. phone_agent/config/prompts.py +6 -1
  55. phone_agent/config/prompts_zh.py +6 -1
  56. AutoGLM_GUI/config.py +0 -23
  57. AutoGLM_GUI/static/assets/chat-BJeomZgh.js +0 -124
  58. AutoGLM_GUI/static/assets/index-Z0uYCPOO.css +0 -1
  59. {autoglm_gui-1.3.1.dist-info → autoglm_gui-1.4.1.dist-info}/WHEEL +0 -0
  60. {autoglm_gui-1.3.1.dist-info → autoglm_gui-1.4.1.dist-info}/entry_points.txt +0 -0
  61. {autoglm_gui-1.3.1.dist-info → autoglm_gui-1.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,7 @@
1
1
  """FastAPI application factory and route registration."""
2
2
 
3
3
  import asyncio
4
+ import os
4
5
  import sys
5
6
  from contextlib import asynccontextmanager
6
7
  from importlib.resources import files
@@ -12,6 +13,7 @@ from fastapi.responses import FileResponse
12
13
  from fastapi.staticfiles import StaticFiles
13
14
 
14
15
  from AutoGLM_GUI.adb_plus.qr_pair import qr_pairing_manager
16
+ from AutoGLM_GUI.logger import logger
15
17
  from AutoGLM_GUI.version import APP_VERSION
16
18
 
17
19
  from . import (
@@ -19,6 +21,8 @@ from . import (
19
21
  control,
20
22
  devices,
21
23
  dual_model,
24
+ health,
25
+ layered_agent,
22
26
  mcp,
23
27
  media,
24
28
  metrics,
@@ -27,11 +31,30 @@ from . import (
27
31
  )
28
32
 
29
33
 
34
+ # TODO:应该要支持运行时动态切换设备
35
+ def _maybe_inject_remote_device() -> None:
36
+ if remote_base_url := os.getenv("REMOTE_DEVICE_BASE_URL"):
37
+ from AutoGLM_GUI.device_adapter import inject_device_protocol
38
+ from AutoGLM_GUI.devices.remote_device import RemoteDevice
39
+
40
+ def get_remote_device(device_id: str | None):
41
+ return RemoteDevice(device_id or "mock_device_001", remote_base_url)
42
+
43
+ inject_device_protocol(get_remote_device)
44
+ logger.info(f"Remote device mode enabled: connecting to {remote_base_url}")
45
+
46
+
47
+ def _get_cors_origins() -> list[str]:
48
+ cors_origins_str = os.getenv("AUTOGLM_CORS_ORIGINS", "http://localhost:3000")
49
+ if cors_origins_str == "*":
50
+ return ["*"]
51
+ return [origin.strip() for origin in cors_origins_str.split(",") if origin.strip()]
52
+
53
+
30
54
  def _get_static_dir() -> Path | None:
31
- """Locate packaged static assets."""
32
- # Priority 1: PyInstaller bundled path (for packaged executable)
33
- if getattr(sys, "_MEIPASS", None):
34
- bundled_static = Path(sys._MEIPASS) / "AutoGLM_GUI" / "static"
55
+ meipass = getattr(sys, "_MEIPASS", None)
56
+ if meipass:
57
+ bundled_static = Path(meipass) / "AutoGLM_GUI" / "static"
35
58
  if bundled_static.exists():
36
59
  return bundled_static
37
60
 
@@ -54,6 +77,9 @@ def _get_static_dir() -> Path | None:
54
77
  def create_app() -> FastAPI:
55
78
  """Build the FastAPI app with routers and static assets."""
56
79
 
80
+ # Inject RemoteDevice if REMOTE_DEVICE_BASE_URL is set
81
+ _maybe_inject_remote_device()
82
+
57
83
  # Create MCP ASGI app
58
84
  mcp_app = mcp.get_mcp_asgi_app()
59
85
 
@@ -82,13 +108,15 @@ def create_app() -> FastAPI:
82
108
 
83
109
  app.add_middleware(
84
110
  CORSMiddleware,
85
- allow_origins=["http://localhost:3000"],
111
+ allow_origins=_get_cors_origins(),
86
112
  allow_credentials=True,
87
113
  allow_methods=["*"],
88
114
  allow_headers=["*"],
89
115
  )
90
116
 
91
117
  app.include_router(agents.router)
118
+ app.include_router(health.router)
119
+ app.include_router(layered_agent.router)
92
120
  app.include_router(devices.router)
93
121
  app.include_router(control.router)
94
122
  app.include_router(media.router)
@@ -104,14 +132,29 @@ def create_app() -> FastAPI:
104
132
  if static_dir is not None and static_dir.exists():
105
133
  assets_dir = static_dir / "assets"
106
134
  if assets_dir.exists():
135
+ # Vite builds assets with content hashes, so we can cache them long-term
107
136
  app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
108
137
 
109
138
  # Define SPA serving function
110
139
  async def serve_spa(full_path: str) -> FileResponse:
111
140
  file_path = static_dir / full_path
112
141
  if file_path.is_file():
113
- return FileResponse(file_path)
114
- return FileResponse(static_dir / "index.html")
142
+ return FileResponse(
143
+ file_path,
144
+ headers={
145
+ "Cache-Control": "no-cache, no-store, must-revalidate",
146
+ "Pragma": "no-cache",
147
+ "Expires": "0",
148
+ },
149
+ )
150
+ return FileResponse(
151
+ static_dir / "index.html",
152
+ headers={
153
+ "Cache-Control": "no-cache, no-store, must-revalidate",
154
+ "Pragma": "no-cache",
155
+ "Expires": "0",
156
+ },
157
+ )
115
158
 
116
159
  # Add catch-all route for SPA (handles all non-API routes)
117
160
  app.add_api_route(
AutoGLM_GUI/api/agents.py CHANGED
@@ -3,13 +3,12 @@
3
3
  import json
4
4
  import queue
5
5
  import threading
6
- from typing import Any
7
6
 
8
7
  from fastapi import APIRouter, HTTPException
9
8
  from fastapi.responses import StreamingResponse
9
+ from phone_agent.agent import StepResult
10
10
  from pydantic import ValidationError
11
11
 
12
- from AutoGLM_GUI.config import config
13
12
  from AutoGLM_GUI.logger import logger
14
13
  from AutoGLM_GUI.phone_agent_patches import apply_patches
15
14
  from AutoGLM_GUI.schemas import (
@@ -91,9 +90,12 @@ def _initialize_agent_with_config(
91
90
  logger.info(f"Agent initialized successfully for device {device_id}")
92
91
 
93
92
 
93
+ SSEPayload = dict[str, str | int | bool | None | dict]
94
+
95
+
94
96
  def _create_sse_event(
95
- event_type: str, data: dict[str, Any], role: str = "assistant"
96
- ) -> dict[str, Any]:
97
+ event_type: str, data: SSEPayload, role: str = "assistant"
98
+ ) -> SSEPayload:
97
99
  """Create an SSE event with standardized fields including role."""
98
100
  event_data = {"type": event_type, "role": role, **data}
99
101
  return event_data
@@ -116,11 +118,17 @@ def init_agent(request: InitRequest) -> dict:
116
118
  # 热重载配置文件(支持运行时手动修改)
117
119
  config_manager.load_file_config()
118
120
  config_manager.sync_to_env()
119
- config.refresh_from_env()
120
121
 
121
- base_url = req_model_config.base_url or config.base_url
122
- api_key = req_model_config.api_key or config.api_key
123
- model_name = req_model_config.model_name or config.model_name
122
+ # 获取有效配置(已合并 CLI > ENV > FILE > DEFAULT)
123
+ effective_config = config_manager.get_effective_config()
124
+
125
+ # 优先级:请求参数 > 有效配置
126
+ base_url = req_model_config.base_url or effective_config.base_url
127
+ api_key = req_model_config.api_key or effective_config.api_key
128
+ model_name = req_model_config.model_name or effective_config.model_name
129
+
130
+ # 获取配置的默认最大步数
131
+ max_steps = effective_config.default_max_steps
124
132
 
125
133
  if not base_url:
126
134
  raise HTTPException(
@@ -139,7 +147,7 @@ def init_agent(request: InitRequest) -> dict:
139
147
  )
140
148
 
141
149
  agent_config = AgentConfig(
142
- max_steps=req_agent_config.max_steps,
150
+ max_steps=max_steps,
143
151
  device_id=device_id,
144
152
  lang=req_agent_config.lang,
145
153
  system_prompt=req_agent_config.system_prompt,
@@ -148,7 +156,35 @@ def init_agent(request: InitRequest) -> dict:
148
156
 
149
157
  # Initialize agent (includes ADB Keyboard setup)
150
158
  try:
151
- _initialize_agent_with_config(device_id, model_config, agent_config)
159
+ # Setup ADB Keyboard (common for all agents)
160
+ _setup_adb_keyboard(device_id)
161
+
162
+ # Use agent factory to create agent
163
+ from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
164
+
165
+ manager = PhoneAgentManager.get_instance()
166
+
167
+ # Initialize agent using factory pattern
168
+ from typing import cast
169
+
170
+ from AutoGLM_GUI.types import AgentSpecificConfig
171
+
172
+ agent_config_params = cast(
173
+ AgentSpecificConfig, request.agent_config_params or {}
174
+ )
175
+ manager.initialize_agent_with_factory(
176
+ device_id=device_id,
177
+ agent_type=request.agent_type,
178
+ model_config=model_config,
179
+ agent_config=agent_config,
180
+ agent_specific_config=agent_config_params,
181
+ takeover_callback=non_blocking_takeover,
182
+ force=request.force,
183
+ )
184
+
185
+ logger.info(
186
+ f"Agent of type '{request.agent_type}' initialized for device {device_id}"
187
+ )
152
188
  except Exception as e:
153
189
  logger.error(f"Failed to initialize agent: {e}")
154
190
  raise HTTPException(status_code=500, detail=str(e))
@@ -157,6 +193,7 @@ def init_agent(request: InitRequest) -> dict:
157
193
  "success": True,
158
194
  "device_id": device_id,
159
195
  "message": f"Agent initialized for device {device_id}",
196
+ "agent_type": request.agent_type,
160
197
  }
161
198
 
162
199
 
@@ -207,12 +244,12 @@ def chat_stream(request: ChatRequest):
207
244
  )
208
245
 
209
246
  def event_generator():
210
- """SSE 事件生成器."""
211
247
  threads: list[threading.Thread] = []
248
+ stop_event: threading.Event | None = None
212
249
 
213
250
  try:
214
251
  # 创建事件队列用于 agent → SSE 通信
215
- event_queue: queue.Queue[tuple[str, Any]] = queue.Queue()
252
+ event_queue: queue.Queue[tuple[str, SSEPayload | None]] = queue.Queue()
216
253
 
217
254
  # 思考块回调
218
255
  def on_thinking_chunk(chunk: str):
@@ -231,8 +268,8 @@ def chat_stream(request: ChatRequest):
231
268
  return
232
269
 
233
270
  # 在线程中运行 agent 步骤
234
- step_result: list[Any] = [None]
235
- error_result: list[Any] = [None]
271
+ step_result: list[StepResult | None] = [None]
272
+ error_result: list[Exception | None] = [None]
236
273
 
237
274
  def run_step(is_first: bool = True, task: str | None = None):
238
275
  try:
@@ -277,6 +314,9 @@ def chat_stream(request: ChatRequest):
277
314
  raise error_result[0]
278
315
 
279
316
  result = step_result[0]
317
+ if result is None:
318
+ raise RuntimeError("step_result is None after step_done")
319
+
280
320
  event_data = _create_sse_event(
281
321
  "step",
282
322
  {
@@ -349,8 +389,7 @@ def chat_stream(request: ChatRequest):
349
389
  yield "event: error\n"
350
390
  yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
351
391
  finally:
352
- # 通知线程停止
353
- if "stop_event" in locals():
392
+ if stop_event is not None:
354
393
  stop_event.set()
355
394
 
356
395
  # 等待线程完成(带超时)
@@ -460,7 +499,9 @@ def get_config_endpoint() -> ConfigResponse:
460
499
  decision_api_key=effective_config.decision_api_key
461
500
  if effective_config.decision_api_key
462
501
  else "",
463
- thinking_mode=effective_config.thinking_mode,
502
+ agent_type=effective_config.agent_type,
503
+ agent_config_params=effective_config.agent_config_params,
504
+ default_max_steps=effective_config.default_max_steps,
464
505
  conflicts=[
465
506
  {
466
507
  "field": c.field,
@@ -497,7 +538,9 @@ def save_config_endpoint(request: ConfigSaveRequest) -> dict:
497
538
  decision_base_url=request.decision_base_url,
498
539
  decision_model_name=request.decision_model_name,
499
540
  decision_api_key=request.decision_api_key,
500
- thinking_mode=request.thinking_mode,
541
+ agent_type=request.agent_type,
542
+ agent_config_params=request.agent_config_params,
543
+ default_max_steps=request.default_max_steps,
501
544
  merge_mode=True,
502
545
  )
503
546
 
@@ -506,7 +549,6 @@ def save_config_endpoint(request: ConfigSaveRequest) -> dict:
506
549
 
507
550
  # 同步到环境变量
508
551
  config_manager.sync_to_env()
509
- config.refresh_from_env()
510
552
 
511
553
  # 检测冲突并返回警告
512
554
  conflicts = config_manager.detect_conflicts()
@@ -15,6 +15,7 @@ from AutoGLM_GUI.logger import logger
15
15
 
16
16
  from AutoGLM_GUI.schemas import (
17
17
  DeviceListResponse,
18
+ DeviceResponse,
18
19
  WiFiConnectRequest,
19
20
  WiFiConnectResponse,
20
21
  WiFiDisconnectRequest,
@@ -32,29 +33,16 @@ from AutoGLM_GUI.schemas import (
32
33
 
33
34
 
34
35
  def _build_device_response_with_agent(
35
- device: ManagedDevice, agent_manager: PhoneAgentManager
36
- ) -> dict:
37
- """组合设备信息和 Agent 状态(API 层职责)。
38
-
39
- Args:
40
- device: ManagedDevice 实例
41
- agent_manager: PhoneAgentManager 实例
42
-
43
- Returns:
44
- dict: 完整的设备响应,匹配 DeviceResponse schema
45
- """
46
- # 获取纯设备信息
36
+ device: "ManagedDevice", agent_manager: "PhoneAgentManager"
37
+ ) -> DeviceResponse:
47
38
  response = device.to_dict()
48
-
49
- # 通过 serial 查找 Agent(支持连接切换)
50
39
  agent_device_id = agent_manager.find_agent_by_serial(device.serial)
51
40
 
52
41
  if agent_device_id:
53
42
  metadata = agent_manager.get_metadata(agent_device_id)
54
-
55
43
  if metadata:
56
44
  response["agent"] = {
57
- "state": metadata.state.value,
45
+ "state": metadata.state, # AgentState is str, Enum, already a string
58
46
  "created_at": metadata.created_at,
59
47
  "last_used": metadata.last_used,
60
48
  "error_message": metadata.error_message,
@@ -65,7 +53,7 @@ def _build_device_response_with_agent(
65
53
  else:
66
54
  response["agent"] = None
67
55
 
68
- return response
56
+ return DeviceResponse.model_validate(response)
69
57
 
70
58
 
71
59
  router = APIRouter()
@@ -97,9 +85,15 @@ def list_devices() -> DeviceListResponse:
97
85
 
98
86
  @router.post("/api/devices/connect_wifi", response_model=WiFiConnectResponse)
99
87
  def connect_wifi(request: WiFiConnectRequest) -> WiFiConnectResponse:
100
- """从 USB 启用 TCP/IP 并连接到 WiFi。"""
101
88
  from AutoGLM_GUI.device_manager import DeviceManager
102
89
 
90
+ if not request.device_id:
91
+ return WiFiConnectResponse(
92
+ success=False,
93
+ message="device_id is required",
94
+ error="device_not_found",
95
+ )
96
+
103
97
  device_manager = DeviceManager.get_instance()
104
98
  success, message, wifi_id = device_manager.connect_wifi(
105
99
  device_id=request.device_id,
@@ -1,7 +1,7 @@
1
1
  """双模型协作API端点"""
2
2
 
3
3
  import threading
4
- from typing import Any, Optional
4
+ from typing import Optional
5
5
 
6
6
  from fastapi import APIRouter, HTTPException
7
7
  from fastapi.responses import StreamingResponse
@@ -30,19 +30,17 @@ class DualModelInitRequest(BaseModel):
30
30
  device_id: str
31
31
 
32
32
  # 决策大模型配置
33
- decision_base_url: str = "https://api-inference.modelscope.cn/v1"
33
+ decision_base_url: str
34
34
  decision_api_key: str
35
- decision_model_name: str = "ZhipuAI/GLM-4.7"
35
+ decision_model_name: str
36
36
 
37
37
  # 视觉小模型配置(复用现有配置)
38
38
  vision_base_url: Optional[str] = None
39
39
  vision_api_key: Optional[str] = None
40
40
  vision_model_name: Optional[str] = None
41
41
 
42
- # 思考模式: fast 或 deep
43
- thinking_mode: str = "deep"
44
-
45
42
  max_steps: int = 50
43
+ thinking_mode: str = "deep" # fast, deep, turbo
46
44
 
47
45
 
48
46
  class DualModelChatRequest(BaseModel):
@@ -69,13 +67,16 @@ class DualModelStatusResponse(BaseModel):
69
67
  @router.post("/init")
70
68
  def init_dual_model(request: DualModelInitRequest) -> dict:
71
69
  """初始化双模型Agent"""
72
- from AutoGLM_GUI.config import config
70
+ from AutoGLM_GUI.config_manager import config_manager
73
71
  from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
74
72
 
75
73
  device_id = request.device_id
76
- thinking_mode = (
77
- ThinkingMode.FAST if request.thinking_mode == "fast" else ThinkingMode.DEEP
78
- )
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)
79
80
  logger.info(f"初始化双模型Agent: {device_id}, 模式: {thinking_mode.value}")
80
81
 
81
82
  # 检查设备是否已有单模型Agent初始化
@@ -85,10 +86,16 @@ def init_dual_model(request: DualModelInitRequest) -> dict:
85
86
  status_code=400, detail="设备尚未初始化单模型Agent,请先调用 /api/init"
86
87
  )
87
88
 
88
- # 获取视觉模型配置
89
- vision_base_url = request.vision_base_url or config.base_url
90
- vision_api_key = request.vision_api_key or config.api_key
91
- vision_model_name = request.vision_model_name or config.model_name
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
92
99
 
93
100
  if not vision_base_url:
94
101
  raise HTTPException(status_code=400, detail="视觉模型base_url未配置")
@@ -113,7 +120,7 @@ def init_dual_model(request: DualModelInitRequest) -> dict:
113
120
  decision_config=decision_config,
114
121
  vision_config=vision_config,
115
122
  device_id=device_id,
116
- max_steps=request.max_steps,
123
+ max_steps=max_steps,
117
124
  thinking_mode=thinking_mode,
118
125
  )
119
126
 
@@ -163,8 +170,8 @@ def dual_model_chat_stream(request: DualModelChatRequest):
163
170
  logger.info(f"开始双模型任务: {request.message[:50]}...")
164
171
 
165
172
  # 在后台线程运行Agent
166
- result_holder: list[Any] = [None]
167
- error_holder: list[Any] = [None]
173
+ result_holder: list[dict | None] = [None]
174
+ error_holder: list[Exception | None] = [None]
168
175
 
169
176
  def run_agent():
170
177
  try:
@@ -0,0 +1,13 @@
1
+ from fastapi import APIRouter
2
+
3
+ from AutoGLM_GUI.version import APP_VERSION
4
+
5
+ router = APIRouter(prefix="/api", tags=["health"])
6
+
7
+
8
+ @router.get("/health")
9
+ async def health_check() -> dict:
10
+ return {
11
+ "status": "healthy",
12
+ "version": APP_VERSION,
13
+ }