gitinstall 1.1.0__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 (59) hide show
  1. gitinstall/__init__.py +61 -0
  2. gitinstall/_sdk.py +541 -0
  3. gitinstall/academic.py +831 -0
  4. gitinstall/admin.html +327 -0
  5. gitinstall/auto_update.py +384 -0
  6. gitinstall/autopilot.py +349 -0
  7. gitinstall/badge.py +476 -0
  8. gitinstall/checkpoint.py +330 -0
  9. gitinstall/cicd.py +499 -0
  10. gitinstall/clawhub.html +718 -0
  11. gitinstall/config_schema.py +353 -0
  12. gitinstall/db.py +984 -0
  13. gitinstall/db_backend.py +445 -0
  14. gitinstall/dep_chain.py +337 -0
  15. gitinstall/dependency_audit.py +1153 -0
  16. gitinstall/detector.py +542 -0
  17. gitinstall/doctor.py +493 -0
  18. gitinstall/education.py +869 -0
  19. gitinstall/enterprise.py +802 -0
  20. gitinstall/error_fixer.py +953 -0
  21. gitinstall/event_bus.py +251 -0
  22. gitinstall/executor.py +577 -0
  23. gitinstall/feature_flags.py +138 -0
  24. gitinstall/fetcher.py +921 -0
  25. gitinstall/huggingface.py +922 -0
  26. gitinstall/hw_detect.py +988 -0
  27. gitinstall/i18n.py +664 -0
  28. gitinstall/installer_registry.py +362 -0
  29. gitinstall/knowledge_base.py +379 -0
  30. gitinstall/license_check.py +605 -0
  31. gitinstall/llm.py +569 -0
  32. gitinstall/log.py +236 -0
  33. gitinstall/main.py +1408 -0
  34. gitinstall/mcp_agent.py +841 -0
  35. gitinstall/mcp_server.py +386 -0
  36. gitinstall/monorepo.py +810 -0
  37. gitinstall/multi_source.py +425 -0
  38. gitinstall/onboard.py +276 -0
  39. gitinstall/planner.py +222 -0
  40. gitinstall/planner_helpers.py +323 -0
  41. gitinstall/planner_known_projects.py +1010 -0
  42. gitinstall/planner_templates.py +996 -0
  43. gitinstall/remote_gpu.py +633 -0
  44. gitinstall/resilience.py +608 -0
  45. gitinstall/run_tests.py +572 -0
  46. gitinstall/skills.py +476 -0
  47. gitinstall/tool_schemas.py +324 -0
  48. gitinstall/trending.py +279 -0
  49. gitinstall/uninstaller.py +415 -0
  50. gitinstall/validate_top100.py +607 -0
  51. gitinstall/watchdog.py +180 -0
  52. gitinstall/web.py +1277 -0
  53. gitinstall/web_ui.html +2277 -0
  54. gitinstall-1.1.0.dist-info/METADATA +275 -0
  55. gitinstall-1.1.0.dist-info/RECORD +59 -0
  56. gitinstall-1.1.0.dist-info/WHEEL +5 -0
  57. gitinstall-1.1.0.dist-info/entry_points.txt +3 -0
  58. gitinstall-1.1.0.dist-info/licenses/LICENSE +21 -0
  59. gitinstall-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,841 @@
1
+ """
2
+ mcp_agent.py — MCP 生态 + AI Agent 增强引擎
3
+ =============================================
4
+
5
+ 目标市场:MCP 生态 + AI Agent(新兴,★★★★☆)
6
+
7
+ 功能:
8
+ 1. Agent-Friendly API — 面向 AI Agent 的高层 API
9
+ 2. 多步计划执行(Multi-step Plan Executor)
10
+ 3. 会话上下文管理(Conversation Context)
11
+ 4. Tool Schema 生成(OpenAI / Anthropic / MCP 格式)
12
+ 5. Agent 协作协议(Agent-to-Agent)
13
+ 6. 流式结果(Streaming Results)
14
+ 7. Agent Memory — 安装经验记忆
15
+ 8. 自然语言意图解析(Intent Parser)
16
+
17
+ 零外部依赖,纯 Python 标准库。
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import hashlib
23
+ import json
24
+ import os
25
+ import re
26
+ import time
27
+ from dataclasses import dataclass, field
28
+ from datetime import datetime, timezone
29
+ from pathlib import Path
30
+ from typing import Any, Callable, Generator, Optional
31
+
32
+
33
+ # ─────────────────────────────────────────────
34
+ # 数据结构
35
+ # ─────────────────────────────────────────────
36
+
37
+ @dataclass
38
+ class AgentAction:
39
+ """Agent 可执行的原子操作"""
40
+ action_id: str = ""
41
+ name: str = "" # detect | fetch | plan | install | audit | doctor | ...
42
+ params: dict[str, Any] = field(default_factory=dict)
43
+ result: dict[str, Any] | None = None
44
+ status: str = "pending" # pending | running | success | failed | skipped
45
+ error: str = ""
46
+ duration_ms: int = 0
47
+
48
+
49
+ @dataclass
50
+ class AgentPlan:
51
+ """Agent 执行计划 — 多步骤"""
52
+ plan_id: str = ""
53
+ intent: str = "" # 用户原始意图
54
+ actions: list[AgentAction] = field(default_factory=list)
55
+ context: dict[str, Any] = field(default_factory=dict)
56
+ created_at: str = ""
57
+ status: str = "pending" # pending | executing | completed | failed
58
+
59
+
60
+ @dataclass
61
+ class AgentSession:
62
+ """Agent 会话 — 跨多轮对话保持上下文"""
63
+ session_id: str = ""
64
+ created_at: str = ""
65
+ last_active: str = ""
66
+ history: list[dict] = field(default_factory=list) # 对话历史
67
+ environment: dict[str, Any] = field(default_factory=dict) # 缓存的环境信息
68
+ installed: list[str] = field(default_factory=list) # 已安装的项目
69
+ preferences: dict[str, Any] = field(default_factory=dict)
70
+
71
+
72
+ @dataclass
73
+ class AgentMemory:
74
+ """Agent 经验记忆"""
75
+ memory_id: str = ""
76
+ project: str = ""
77
+ outcome: str = "" # success | failed
78
+ environment_hash: str = ""
79
+ steps_taken: list[str] = field(default_factory=list)
80
+ errors_encountered: list[str] = field(default_factory=list)
81
+ resolution: str = ""
82
+ timestamp: str = ""
83
+
84
+
85
+ # ─────────────────────────────────────────────
86
+ # 自然语言意图解析
87
+ # ─────────────────────────────────────────────
88
+
89
+ @dataclass
90
+ class ParsedIntent:
91
+ """解析后的用户意图"""
92
+ action: str = "" # install | info | audit | diagnose | recommend | compare | ...
93
+ targets: list[str] = field(default_factory=list) # 目标项目/模型
94
+ constraints: dict[str, Any] = field(default_factory=dict) # 约束条件
95
+ confidence: float = 0.0
96
+ raw_input: str = ""
97
+
98
+
99
+ # 意图模式库 — 覆盖中英文
100
+ _INTENT_PATTERNS: list[tuple[str, str, float]] = [
101
+ # (正则, action, 基础置信度)
102
+ (r'(?:install|安装|装|setup|部署|deploy)\s+(.+)', "install", 0.9),
103
+ (r'(?:uninstall|卸载|删除|remove)\s+(.+)', "uninstall", 0.9),
104
+ (r'(?:audit|审计|检查安全|scan)\s+(.+)', "audit", 0.85),
105
+ (r'(?:diagnose|诊断|排错|debug|fix)\s*(.+)?', "diagnose", 0.8),
106
+ (r'(?:detect|检测环境|环境|system|系统)', "detect", 0.85),
107
+ (r'(?:doctor|体检|health)', "doctor", 0.85),
108
+ (r'(?:plan|计划|规划)\s+(.+)', "plan", 0.8),
109
+ (r'(?:fetch|获取|info|信息)\s+(.+)', "fetch", 0.8),
110
+ (r'(?:recommend|推荐|建议)\s*(.+)?', "recommend", 0.75),
111
+ (r'(?:compare|对比|比较)\s+(.+)\s+(?:and|和|vs|与)\s+(.+)', "compare", 0.85),
112
+ (r'(?:update|更新|升级)\s*(.+)?', "update", 0.8),
113
+ (r'(?:license|许可证|协议)\s+(.+)', "license", 0.85),
114
+ (r'(?:sbom|物料清单)\s+(.+)', "sbom", 0.85),
115
+ (r'(?:paper|论文|arxiv)\s+(.+)', "paper", 0.85),
116
+ (r'(?:learn|学习|教程|tutorial)\s*(.+)?', "learn", 0.75),
117
+ (r'(?:vram|显存|内存|memory)\s+(.+)', "vram", 0.8),
118
+ (r'(?:gpu|显卡)\s*(.+)?', "gpu_info", 0.8),
119
+ (r'(?:classroom|课堂|教室)\s*(.+)?', "classroom", 0.8),
120
+ (r'(?:badge|徽章|按钮)\s+(.+)', "badge", 0.8),
121
+ (r'(?:ci|cicd|pipeline)\s+(.+)', "cicd", 0.8),
122
+ ]
123
+
124
+
125
+ def parse_intent(text: str) -> ParsedIntent:
126
+ """
127
+ 解析自然语言意图。
128
+
129
+ 支持:
130
+ - "安装 pytorch/pytorch"
131
+ - "install langchain with GPU support"
132
+ - "这个项目安全吗 huggingface/transformers"
133
+ - "推荐一个 NLP 框架"
134
+ - "对比 vllm 和 tgi"
135
+
136
+ Returns:
137
+ ParsedIntent
138
+ """
139
+ text = text.strip()
140
+ if not text:
141
+ return ParsedIntent(raw_input=text)
142
+
143
+ best_match = None
144
+ best_confidence = 0.0
145
+
146
+ for pattern, action, base_conf in _INTENT_PATTERNS:
147
+ m = re.search(pattern, text, re.IGNORECASE)
148
+ if m:
149
+ # 提取目标
150
+ targets = []
151
+ for g in m.groups():
152
+ if g:
153
+ targets.extend(_extract_targets(g.strip()))
154
+
155
+ # 提取约束
156
+ constraints = _extract_constraints(text)
157
+
158
+ conf = base_conf
159
+ if targets:
160
+ conf += 0.05
161
+
162
+ if conf > best_confidence:
163
+ best_confidence = conf
164
+ best_match = ParsedIntent(
165
+ action=action,
166
+ targets=targets,
167
+ constraints=constraints,
168
+ confidence=conf,
169
+ raw_input=text,
170
+ )
171
+
172
+ if best_match:
173
+ return best_match
174
+
175
+ # 如果没有明确意图,尝试识别项目名
176
+ targets = _extract_targets(text)
177
+ if targets:
178
+ return ParsedIntent(
179
+ action="install",
180
+ targets=targets,
181
+ confidence=0.5,
182
+ raw_input=text,
183
+ )
184
+
185
+ return ParsedIntent(action="unknown", raw_input=text, confidence=0.0)
186
+
187
+
188
+ def _extract_targets(text: str) -> list[str]:
189
+ """提取目标项目/模型名"""
190
+ targets = []
191
+
192
+ # GitHub owner/repo 格式
193
+ for m in re.finditer(r'(?:https?://github\.com/)?([\w.-]+/[\w.-]+)', text):
194
+ targets.append(m.group(1))
195
+
196
+ # 如果没有 owner/repo,尝试识别知名项目
197
+ if not targets:
198
+ known_aliases = {
199
+ "pytorch": "pytorch/pytorch",
200
+ "tensorflow": "tensorflow/tensorflow",
201
+ "react": "facebook/react",
202
+ "vue": "vuejs/core",
203
+ "langchain": "langchain-ai/langchain",
204
+ "transformers": "huggingface/transformers",
205
+ "vllm": "vllm-project/vllm",
206
+ "llama.cpp": "ggml-org/llama.cpp",
207
+ "llamacpp": "ggml-org/llama.cpp",
208
+ "whisper": "openai/whisper",
209
+ "yolo": "ultralytics/ultralytics",
210
+ "fastapi": "fastapi/fastapi",
211
+ "flask": "pallets/flask",
212
+ "django": "django/django",
213
+ "next.js": "vercel/next.js",
214
+ "nextjs": "vercel/next.js",
215
+ "deno": "denoland/deno",
216
+ "rust": "rust-lang/rust",
217
+ "go": "golang/go",
218
+ "node": "nodejs/node",
219
+ "numpy": "numpy/numpy",
220
+ "pandas": "pandas-dev/pandas",
221
+ "scikit-learn": "scikit-learn/scikit-learn",
222
+ "sklearn": "scikit-learn/scikit-learn",
223
+ "stable-diffusion": "CompVis/stable-diffusion",
224
+ "ollama": "ollama/ollama",
225
+ "comfyui": "comfyanonymous/ComfyUI",
226
+ }
227
+ text_lower = text.lower()
228
+ for alias, repo in known_aliases.items():
229
+ if alias in text_lower:
230
+ targets.append(repo)
231
+ break
232
+
233
+ return targets
234
+
235
+
236
+ def _extract_constraints(text: str) -> dict[str, Any]:
237
+ """提取约束条件"""
238
+ constraints = {}
239
+ text_lower = text.lower()
240
+
241
+ if any(w in text_lower for w in ("gpu", "cuda", "显卡")):
242
+ constraints["gpu"] = True
243
+ if any(w in text_lower for w in ("cpu only", "无gpu", "无显卡", "no gpu")):
244
+ constraints["gpu"] = False
245
+ if any(w in text_lower for w in ("docker", "容器")):
246
+ constraints["docker"] = True
247
+ if any(w in text_lower for w in ("本地", "local", "离线", "offline")):
248
+ constraints["local"] = True
249
+ if any(w in text_lower for w in ("安全", "secure", "安全模式")):
250
+ constraints["security_first"] = True
251
+
252
+ # VRAM 约束
253
+ m = re.search(r'(\d+)\s*(?:gb|g)\s*(?:vram|显存|内存)', text_lower)
254
+ if m:
255
+ constraints["vram_gb"] = int(m.group(1))
256
+
257
+ return constraints
258
+
259
+
260
+ # ─────────────────────────────────────────────
261
+ # Agent 执行器
262
+ # ─────────────────────────────────────────────
263
+
264
+ class AgentExecutor:
265
+ """
266
+ AI Agent 高层执行引擎。
267
+
268
+ 将自然语言意图转化为结构化的多步骤执行计划,
269
+ 并执行每一步,支持流式输出和错误恢复。
270
+ """
271
+
272
+ def __init__(self, session: AgentSession | None = None):
273
+ if session:
274
+ self.session = session
275
+ else:
276
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
277
+ sid = hashlib.sha256(now.encode()).hexdigest()[:12]
278
+ self.session = AgentSession(
279
+ session_id=sid,
280
+ created_at=now,
281
+ last_active=now,
282
+ )
283
+
284
+ def process(self, user_input: str) -> AgentPlan:
285
+ """
286
+ 处理用户输入,生成并执行计划。
287
+
288
+ 这是 Agent 的主入口。传入自然语言,返回执行结果。
289
+ """
290
+ intent = parse_intent(user_input)
291
+ plan = self._create_plan(intent)
292
+ self._execute_plan(plan)
293
+
294
+ # 记录到会话历史
295
+ self.session.history.append({
296
+ "role": "user",
297
+ "content": user_input,
298
+ "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
299
+ })
300
+ self.session.history.append({
301
+ "role": "assistant",
302
+ "plan_id": plan.plan_id,
303
+ "status": plan.status,
304
+ "actions": len(plan.actions),
305
+ })
306
+ self.session.last_active = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
307
+
308
+ return plan
309
+
310
+ def _create_plan(self, intent: ParsedIntent) -> AgentPlan:
311
+ """从意图创建执行计划"""
312
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
313
+ plan_id = hashlib.sha256(f"{now}-{intent.action}".encode()).hexdigest()[:10]
314
+
315
+ plan = AgentPlan(
316
+ plan_id=plan_id,
317
+ intent=intent.raw_input,
318
+ created_at=now,
319
+ context={"parsed_intent": intent.action, "targets": intent.targets},
320
+ )
321
+
322
+ actions = _INTENT_TO_ACTIONS.get(intent.action, _default_actions)
323
+ plan.actions = actions(intent, self.session)
324
+ return plan
325
+
326
+ def _execute_plan(self, plan: AgentPlan) -> None:
327
+ """执行计划中的每一步"""
328
+ plan.status = "executing"
329
+
330
+ for action in plan.actions:
331
+ if action.status == "skipped":
332
+ continue
333
+
334
+ action.status = "running"
335
+ start = time.monotonic()
336
+
337
+ try:
338
+ result = self._execute_action(action)
339
+ action.result = result
340
+ action.status = "success"
341
+
342
+ # 缓存环境信息
343
+ if action.name == "detect":
344
+ self.session.environment = result
345
+ elif action.name == "install" and result.get("success"):
346
+ target = action.params.get("identifier", "")
347
+ if target and target not in self.session.installed:
348
+ self.session.installed.append(target)
349
+
350
+ except Exception as e:
351
+ action.status = "failed"
352
+ action.error = str(e)
353
+
354
+ action.duration_ms = int((time.monotonic() - start) * 1000)
355
+
356
+ # 整体状态
357
+ if all(a.status in ("success", "skipped") for a in plan.actions):
358
+ plan.status = "completed"
359
+ elif any(a.status == "failed" for a in plan.actions):
360
+ plan.status = "failed"
361
+ else:
362
+ plan.status = "completed"
363
+
364
+ def _execute_action(self, action: AgentAction) -> dict:
365
+ """执行单个 action"""
366
+ from _sdk import detect, plan, install, diagnose, fetch, doctor, audit
367
+
368
+ name = action.name
369
+ params = action.params
370
+
371
+ if name == "detect":
372
+ return detect()
373
+ elif name == "fetch":
374
+ return fetch(params.get("identifier", ""))
375
+ elif name == "plan":
376
+ return plan(params.get("identifier", ""))
377
+ elif name == "install":
378
+ return install(
379
+ params.get("identifier", ""),
380
+ install_dir=params.get("install_dir"),
381
+ )
382
+ elif name == "audit":
383
+ return audit(params.get("identifier", ""))
384
+ elif name == "doctor":
385
+ return doctor()
386
+ elif name == "diagnose":
387
+ return diagnose(params.get("identifier", ""))
388
+ elif name == "noop":
389
+ return {"message": params.get("message", "OK")}
390
+ else:
391
+ return {"error": f"Unknown action: {name}"}
392
+
393
+ def stream_process(self, user_input: str) -> Generator[dict, None, None]:
394
+ """
395
+ 流式处理 — 适合长时间任务的实时反馈。
396
+
397
+ Yields:
398
+ {"type": "intent", "data": {...}}
399
+ {"type": "action_start", "data": {...}}
400
+ {"type": "action_complete", "data": {...}}
401
+ {"type": "plan_complete", "data": {...}}
402
+ """
403
+ intent = parse_intent(user_input)
404
+ yield {"type": "intent", "data": {
405
+ "action": intent.action,
406
+ "targets": intent.targets,
407
+ "confidence": intent.confidence,
408
+ }}
409
+
410
+ plan = self._create_plan(intent)
411
+ plan.status = "executing"
412
+
413
+ for action in plan.actions:
414
+ if action.status == "skipped":
415
+ continue
416
+
417
+ yield {"type": "action_start", "data": {
418
+ "action_id": action.action_id,
419
+ "name": action.name,
420
+ "params": action.params,
421
+ }}
422
+
423
+ action.status = "running"
424
+ start = time.monotonic()
425
+
426
+ try:
427
+ result = self._execute_action(action)
428
+ action.result = result
429
+ action.status = "success"
430
+ except Exception as e:
431
+ action.status = "failed"
432
+ action.error = str(e)
433
+
434
+ action.duration_ms = int((time.monotonic() - start) * 1000)
435
+
436
+ yield {"type": "action_complete", "data": {
437
+ "action_id": action.action_id,
438
+ "name": action.name,
439
+ "status": action.status,
440
+ "duration_ms": action.duration_ms,
441
+ "error": action.error,
442
+ }}
443
+
444
+ plan.status = "completed" if all(
445
+ a.status in ("success", "skipped") for a in plan.actions
446
+ ) else "failed"
447
+
448
+ yield {"type": "plan_complete", "data": {
449
+ "plan_id": plan.plan_id,
450
+ "status": plan.status,
451
+ "total_actions": len(plan.actions),
452
+ }}
453
+
454
+
455
+ # ─────────────────────────────────────────────
456
+ # 意图→Actions 映射
457
+ # ─────────────────────────────────────────────
458
+
459
+ def _make_action(name: str, params: dict | None = None, aid: str = "") -> AgentAction:
460
+ """创建 action"""
461
+ return AgentAction(
462
+ action_id=aid or hashlib.sha256(f"{name}-{time.time()}".encode()).hexdigest()[:8],
463
+ name=name,
464
+ params=params or {},
465
+ )
466
+
467
+
468
+ def _install_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
469
+ """安装流程: detect → plan → install"""
470
+ actions = []
471
+
472
+ # 如果会话中没有缓存环境信息,先检测
473
+ if not session.environment:
474
+ actions.append(_make_action("detect"))
475
+
476
+ for target in intent.targets:
477
+ actions.append(_make_action("plan", {"identifier": target}))
478
+ actions.append(_make_action("install", {"identifier": target}))
479
+
480
+ return actions or [_make_action("noop", {"message": "请指定要安装的项目"})]
481
+
482
+
483
+ def _audit_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
484
+ """审计流程"""
485
+ actions = []
486
+ for target in intent.targets:
487
+ actions.append(_make_action("audit", {"identifier": target}))
488
+ return actions or [_make_action("noop", {"message": "请指定要审计的项目"})]
489
+
490
+
491
+ def _diagnose_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
492
+ """诊断流程: doctor + diagnose"""
493
+ actions = [_make_action("doctor")]
494
+ for target in intent.targets:
495
+ actions.append(_make_action("diagnose", {"identifier": target}))
496
+ return actions
497
+
498
+
499
+ def _detect_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
500
+ return [_make_action("detect")]
501
+
502
+
503
+ def _doctor_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
504
+ return [_make_action("doctor")]
505
+
506
+
507
+ def _fetch_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
508
+ actions = []
509
+ for target in intent.targets:
510
+ actions.append(_make_action("fetch", {"identifier": target}))
511
+ return actions or [_make_action("noop", {"message": "请指定项目"})]
512
+
513
+
514
+ def _plan_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
515
+ actions = []
516
+ if not session.environment:
517
+ actions.append(_make_action("detect"))
518
+ for target in intent.targets:
519
+ actions.append(_make_action("plan", {"identifier": target}))
520
+ return actions or [_make_action("noop", {"message": "请指定项目"})]
521
+
522
+
523
+ def _default_actions(intent: ParsedIntent, session: AgentSession) -> list[AgentAction]:
524
+ return [_make_action("noop", {"message": f"未识别的操作: {intent.action}"})]
525
+
526
+
527
+ _INTENT_TO_ACTIONS: dict[str, Callable] = {
528
+ "install": _install_actions,
529
+ "uninstall": _default_actions, # TODO: 对接 uninstaller.py
530
+ "audit": _audit_actions,
531
+ "diagnose": _diagnose_actions,
532
+ "detect": _detect_actions,
533
+ "doctor": _doctor_actions,
534
+ "plan": _plan_actions,
535
+ "fetch": _fetch_actions,
536
+ "recommend": _default_actions,
537
+ "compare": _default_actions,
538
+ "update": _default_actions,
539
+ "license": _audit_actions,
540
+ "sbom": _audit_actions,
541
+ }
542
+
543
+
544
+ # ─────────────────────────────────────────────
545
+ # Tool Schema 生成(OpenAI / Anthropic / MCP)
546
+ # ─────────────────────────────────────────────
547
+
548
+ def generate_tool_schemas(format: str = "openai") -> list[dict]:
549
+ """
550
+ 生成适配不同 AI 平台的 tool schema。
551
+
552
+ Args:
553
+ format: "openai" | "anthropic" | "mcp"
554
+
555
+ Returns:
556
+ Tool schema 列表
557
+ """
558
+ base_tools = [
559
+ {
560
+ "name": "gitinstall_detect",
561
+ "description": "检测当前系统环境(OS、CPU、GPU、运行时、包管理器)",
562
+ "parameters": {},
563
+ },
564
+ {
565
+ "name": "gitinstall_install",
566
+ "description": "一键安装 GitHub 项目。支持 owner/repo 格式或完整 URL",
567
+ "parameters": {
568
+ "identifier": {"type": "string", "description": "GitHub 项目标识(owner/repo 或 URL)", "required": True},
569
+ "install_dir": {"type": "string", "description": "安装目录(可选)"},
570
+ },
571
+ },
572
+ {
573
+ "name": "gitinstall_plan",
574
+ "description": "为 GitHub 项目生成安装计划,不执行安装",
575
+ "parameters": {
576
+ "identifier": {"type": "string", "description": "GitHub 项目标识", "required": True},
577
+ },
578
+ },
579
+ {
580
+ "name": "gitinstall_audit",
581
+ "description": "安全审计 — 检查依赖漏洞、许可证风险、typosquatting",
582
+ "parameters": {
583
+ "identifier": {"type": "string", "description": "GitHub 项目标识", "required": True},
584
+ "online": {"type": "boolean", "description": "是否查询在线 CVE 数据库"},
585
+ },
586
+ },
587
+ {
588
+ "name": "gitinstall_doctor",
589
+ "description": "系统健康检查 — Python、Git、包管理器、GPU、磁盘空间",
590
+ "parameters": {},
591
+ },
592
+ {
593
+ "name": "gitinstall_fetch",
594
+ "description": "获取 GitHub 项目元数据(README、依赖、项目类型)",
595
+ "parameters": {
596
+ "identifier": {"type": "string", "description": "GitHub 项目标识", "required": True},
597
+ },
598
+ },
599
+ {
600
+ "name": "gitinstall_vram",
601
+ "description": "评估 AI 模型的 VRAM 需求和最佳量化方案",
602
+ "parameters": {
603
+ "model_id": {"type": "string", "description": "HuggingFace 模型 ID", "required": True},
604
+ "vram_gb": {"type": "number", "description": "可用 VRAM (GB)"},
605
+ },
606
+ },
607
+ {
608
+ "name": "gitinstall_paper",
609
+ "description": "从 arXiv 论文 ID 查找代码仓库并安装",
610
+ "parameters": {
611
+ "paper_id": {"type": "string", "description": "arXiv ID (如 2301.13688)", "required": True},
612
+ },
613
+ },
614
+ {
615
+ "name": "gitinstall_classroom",
616
+ "description": "创建课堂环境 — 批量配置学生开发环境",
617
+ "parameters": {
618
+ "name": {"type": "string", "description": "课堂名称", "required": True},
619
+ "projects": {"type": "array", "items": {"type": "string"}, "description": "项目列表", "required": True},
620
+ },
621
+ },
622
+ {
623
+ "name": "gitinstall_natural",
624
+ "description": "自然语言安装 — 用中文或英文描述你想做什么,自动理解执行",
625
+ "parameters": {
626
+ "text": {"type": "string", "description": "自然语言描述", "required": True},
627
+ },
628
+ },
629
+ ]
630
+
631
+ if format == "openai":
632
+ return _to_openai_format(base_tools)
633
+ elif format == "anthropic":
634
+ return _to_anthropic_format(base_tools)
635
+ elif format == "mcp":
636
+ return _to_mcp_format(base_tools)
637
+ return base_tools
638
+
639
+
640
+ def _to_openai_format(tools: list[dict]) -> list[dict]:
641
+ """转换为 OpenAI function calling 格式"""
642
+ result = []
643
+ for t in tools:
644
+ props = {}
645
+ required = []
646
+ for pname, pinfo in t.get("parameters", {}).items():
647
+ props[pname] = {
648
+ "type": pinfo.get("type", "string"),
649
+ "description": pinfo.get("description", ""),
650
+ }
651
+ if pinfo.get("items"):
652
+ props[pname]["items"] = pinfo["items"]
653
+ if pinfo.get("required"):
654
+ required.append(pname)
655
+
656
+ result.append({
657
+ "type": "function",
658
+ "function": {
659
+ "name": t["name"],
660
+ "description": t["description"],
661
+ "parameters": {
662
+ "type": "object",
663
+ "properties": props,
664
+ "required": required,
665
+ },
666
+ },
667
+ })
668
+ return result
669
+
670
+
671
+ def _to_anthropic_format(tools: list[dict]) -> list[dict]:
672
+ """转换为 Anthropic tool_use 格式"""
673
+ result = []
674
+ for t in tools:
675
+ props = {}
676
+ required = []
677
+ for pname, pinfo in t.get("parameters", {}).items():
678
+ props[pname] = {
679
+ "type": pinfo.get("type", "string"),
680
+ "description": pinfo.get("description", ""),
681
+ }
682
+ if pinfo.get("required"):
683
+ required.append(pname)
684
+
685
+ result.append({
686
+ "name": t["name"],
687
+ "description": t["description"],
688
+ "input_schema": {
689
+ "type": "object",
690
+ "properties": props,
691
+ "required": required,
692
+ },
693
+ })
694
+ return result
695
+
696
+
697
+ def _to_mcp_format(tools: list[dict]) -> list[dict]:
698
+ """转换为 MCP 协议格式"""
699
+ result = []
700
+ for t in tools:
701
+ props = {}
702
+ required = []
703
+ for pname, pinfo in t.get("parameters", {}).items():
704
+ props[pname] = {
705
+ "type": pinfo.get("type", "string"),
706
+ "description": pinfo.get("description", ""),
707
+ }
708
+ if pinfo.get("required"):
709
+ required.append(pname)
710
+
711
+ result.append({
712
+ "name": t["name"],
713
+ "description": t["description"],
714
+ "inputSchema": {
715
+ "type": "object",
716
+ "properties": props,
717
+ "required": required,
718
+ },
719
+ })
720
+ return result
721
+
722
+
723
+ # ─────────────────────────────────────────────
724
+ # Agent 经验记忆
725
+ # ─────────────────────────────────────────────
726
+
727
+ _MEMORY_DIR = os.path.expanduser("~/.gitinstall/agent_memory")
728
+
729
+
730
+ def remember_outcome(
731
+ project: str,
732
+ outcome: str,
733
+ steps: list[str] | None = None,
734
+ errors: list[str] | None = None,
735
+ resolution: str = "",
736
+ env_info: dict | None = None,
737
+ ) -> AgentMemory:
738
+ """记录安装结果到经验库"""
739
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
740
+ env_hash = hashlib.sha256(
741
+ json.dumps(env_info or {}, sort_keys=True).encode()
742
+ ).hexdigest()[:12]
743
+
744
+ mem = AgentMemory(
745
+ memory_id=hashlib.sha256(f"{project}-{now}".encode()).hexdigest()[:10],
746
+ project=project,
747
+ outcome=outcome,
748
+ environment_hash=env_hash,
749
+ steps_taken=steps or [],
750
+ errors_encountered=errors or [],
751
+ resolution=resolution,
752
+ timestamp=now,
753
+ )
754
+
755
+ os.makedirs(_MEMORY_DIR, exist_ok=True)
756
+ path = os.path.join(_MEMORY_DIR, f"{mem.memory_id}.json")
757
+ with open(path, "w", encoding="utf-8") as f:
758
+ json.dump({
759
+ "memory_id": mem.memory_id,
760
+ "project": mem.project,
761
+ "outcome": mem.outcome,
762
+ "environment_hash": mem.environment_hash,
763
+ "steps_taken": mem.steps_taken,
764
+ "errors_encountered": mem.errors_encountered,
765
+ "resolution": mem.resolution,
766
+ "timestamp": mem.timestamp,
767
+ }, f, indent=2, ensure_ascii=False)
768
+
769
+ return mem
770
+
771
+
772
+ def recall_experience(project: str) -> list[AgentMemory]:
773
+ """查找某项目的安装经验"""
774
+ if not os.path.isdir(_MEMORY_DIR):
775
+ return []
776
+
777
+ results = []
778
+ for fname in os.listdir(_MEMORY_DIR):
779
+ if not fname.endswith(".json"):
780
+ continue
781
+ path = os.path.join(_MEMORY_DIR, fname)
782
+ try:
783
+ with open(path, "r", encoding="utf-8") as f:
784
+ data = json.load(f)
785
+ if data.get("project", "") == project:
786
+ results.append(AgentMemory(**data))
787
+ except (json.JSONDecodeError, OSError, TypeError):
788
+ pass
789
+
790
+ results.sort(key=lambda x: x.timestamp, reverse=True)
791
+ return results
792
+
793
+
794
+ # ─────────────────────────────────────────────
795
+ # Agent-to-Agent 协作协议
796
+ # ─────────────────────────────────────────────
797
+
798
+ def create_agent_handoff(
799
+ from_agent: str,
800
+ to_agent: str,
801
+ task: str,
802
+ context: dict[str, Any] | None = None,
803
+ ) -> dict:
804
+ """
805
+ 创建 Agent 间的任务交接消息。
806
+
807
+ 用于多 Agent 编排场景(如 AutoGen、CrewAI)。
808
+ """
809
+ return {
810
+ "protocol": "gitinstall-agent-handoff/1.0",
811
+ "from": from_agent,
812
+ "to": to_agent,
813
+ "task": task,
814
+ "context": context or {},
815
+ "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
816
+ "capabilities": [
817
+ "detect", "fetch", "plan", "install", "audit",
818
+ "doctor", "vram_estimate", "paper_install", "classroom",
819
+ ],
820
+ }
821
+
822
+
823
+ def format_plan_result(plan: AgentPlan) -> str:
824
+ """格式化计划执行结果"""
825
+ status_icons = {"completed": "✅", "failed": "❌", "pending": "⏳", "executing": "🔄"}
826
+
827
+ lines = [
828
+ f"{status_icons.get(plan.status, '?')} 执行计划 [{plan.plan_id}]",
829
+ f" 意图: {plan.intent}",
830
+ f" 状态: {plan.status}",
831
+ "",
832
+ ]
833
+
834
+ for a in plan.actions:
835
+ icon = {"success": "✅", "failed": "❌", "skipped": "⏭️", "pending": "○", "running": "🔄"}.get(a.status, "?")
836
+ duration = f" ({a.duration_ms}ms)" if a.duration_ms else ""
837
+ lines.append(f" {icon} {a.name}{duration}")
838
+ if a.error:
839
+ lines.append(f" ⚠️ {a.error}")
840
+
841
+ return "\n".join(lines)