illusion-code 0.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 (214) hide show
  1. illusion/__init__.py +24 -0
  2. illusion/__main__.py +15 -0
  3. illusion/_frontend/dist/index.mjs +39208 -0
  4. illusion/_frontend/package.json +27 -0
  5. illusion/_frontend/src/App.tsx +624 -0
  6. illusion/_frontend/src/components/CommandPicker.tsx +98 -0
  7. illusion/_frontend/src/components/Composer.tsx +55 -0
  8. illusion/_frontend/src/components/ComposerController.tsx +128 -0
  9. illusion/_frontend/src/components/ConversationView.tsx +750 -0
  10. illusion/_frontend/src/components/Footer.tsx +25 -0
  11. illusion/_frontend/src/components/MarkdownContent.tsx +537 -0
  12. illusion/_frontend/src/components/MarkdownTable.tsx +245 -0
  13. illusion/_frontend/src/components/ModalHost.tsx +425 -0
  14. illusion/_frontend/src/components/MultilineTextInput.tsx +250 -0
  15. illusion/_frontend/src/components/PromptInput.tsx +64 -0
  16. illusion/_frontend/src/components/SelectModal.tsx +78 -0
  17. illusion/_frontend/src/components/SidePanel.tsx +175 -0
  18. illusion/_frontend/src/components/Spinner.tsx +77 -0
  19. illusion/_frontend/src/components/StatusBar.tsx +142 -0
  20. illusion/_frontend/src/components/SwarmPanel.tsx +141 -0
  21. illusion/_frontend/src/components/TodoPanel.tsx +126 -0
  22. illusion/_frontend/src/components/ToolCallDisplay.tsx +202 -0
  23. illusion/_frontend/src/components/TranscriptPane.tsx +79 -0
  24. illusion/_frontend/src/components/WelcomeBanner.tsx +37 -0
  25. illusion/_frontend/src/hooks/useBackendSession.ts +468 -0
  26. illusion/_frontend/src/hooks/useTerminalSize.ts +9 -0
  27. illusion/_frontend/src/i18n.ts +78 -0
  28. illusion/_frontend/src/index.tsx +42 -0
  29. illusion/_frontend/src/theme/ThemeContext.tsx +19 -0
  30. illusion/_frontend/src/theme/builtinThemes.ts +89 -0
  31. illusion/_frontend/src/types.ts +110 -0
  32. illusion/_frontend/src/utils/markdown.ts +33 -0
  33. illusion/_frontend/src/utils/thinking.ts +191 -0
  34. illusion/_frontend/tsconfig.json +13 -0
  35. illusion/_web_dist/assets/index-BseIw-ik.css +10 -0
  36. illusion/_web_dist/assets/index-C_0ZWMuW.js +82 -0
  37. illusion/_web_dist/index.html +16 -0
  38. illusion/api/__init__.py +36 -0
  39. illusion/api/client.py +568 -0
  40. illusion/api/codex_client.py +563 -0
  41. illusion/api/compat.py +138 -0
  42. illusion/api/effort.py +128 -0
  43. illusion/api/errors.py +57 -0
  44. illusion/api/openai_client.py +819 -0
  45. illusion/api/provider.py +148 -0
  46. illusion/api/registry.py +479 -0
  47. illusion/api/usage.py +45 -0
  48. illusion/auth/__init__.py +50 -0
  49. illusion/auth/copilot.py +419 -0
  50. illusion/auth/external.py +612 -0
  51. illusion/auth/flows.py +58 -0
  52. illusion/auth/manager.py +214 -0
  53. illusion/auth/storage.py +372 -0
  54. illusion/bridge/__init__.py +38 -0
  55. illusion/bridge/manager.py +190 -0
  56. illusion/bridge/session_runner.py +84 -0
  57. illusion/bridge/types.py +113 -0
  58. illusion/bridge/work_secret.py +131 -0
  59. illusion/cli.py +1228 -0
  60. illusion/commands/__init__.py +32 -0
  61. illusion/commands/registry.py +1934 -0
  62. illusion/config/__init__.py +39 -0
  63. illusion/config/i18n.py +522 -0
  64. illusion/config/paths.py +259 -0
  65. illusion/config/settings.py +564 -0
  66. illusion/coordinator/__init__.py +41 -0
  67. illusion/coordinator/agent_definitions.py +1093 -0
  68. illusion/coordinator/coordinator_mode.py +127 -0
  69. illusion/engine/__init__.py +95 -0
  70. illusion/engine/cost_tracker.py +55 -0
  71. illusion/engine/messages.py +369 -0
  72. illusion/engine/query.py +632 -0
  73. illusion/engine/query_engine.py +343 -0
  74. illusion/engine/stream_events.py +169 -0
  75. illusion/hooks/__init__.py +67 -0
  76. illusion/hooks/events.py +43 -0
  77. illusion/hooks/executor.py +397 -0
  78. illusion/hooks/hot_reload.py +74 -0
  79. illusion/hooks/loader.py +133 -0
  80. illusion/hooks/schemas.py +121 -0
  81. illusion/hooks/types.py +86 -0
  82. illusion/mcp/__init__.py +104 -0
  83. illusion/mcp/client.py +377 -0
  84. illusion/mcp/config.py +140 -0
  85. illusion/mcp/types.py +175 -0
  86. illusion/memory/__init__.py +36 -0
  87. illusion/memory/manager.py +94 -0
  88. illusion/memory/memdir.py +58 -0
  89. illusion/memory/paths.py +57 -0
  90. illusion/memory/scan.py +120 -0
  91. illusion/memory/search.py +83 -0
  92. illusion/memory/types.py +43 -0
  93. illusion/output_styles/__init__.py +15 -0
  94. illusion/output_styles/loader.py +64 -0
  95. illusion/permissions/__init__.py +39 -0
  96. illusion/permissions/checker.py +174 -0
  97. illusion/permissions/modes.py +38 -0
  98. illusion/platforms.py +148 -0
  99. illusion/plugins/__init__.py +71 -0
  100. illusion/plugins/bundled/__init__.py +0 -0
  101. illusion/plugins/installer.py +59 -0
  102. illusion/plugins/loader.py +301 -0
  103. illusion/plugins/schemas.py +51 -0
  104. illusion/plugins/types.py +56 -0
  105. illusion/prompts/__init__.py +29 -0
  106. illusion/prompts/claudemd.py +74 -0
  107. illusion/prompts/context.py +187 -0
  108. illusion/prompts/environment.py +189 -0
  109. illusion/prompts/system_prompt.py +155 -0
  110. illusion/py.typed +0 -0
  111. illusion/sandbox/__init__.py +29 -0
  112. illusion/sandbox/adapter.py +174 -0
  113. illusion/services/__init__.py +59 -0
  114. illusion/services/compact/__init__.py +1015 -0
  115. illusion/services/cron.py +338 -0
  116. illusion/services/cron_scheduler.py +715 -0
  117. illusion/services/file_history.py +258 -0
  118. illusion/services/lsp/__init__.py +455 -0
  119. illusion/services/session_storage.py +237 -0
  120. illusion/services/token_estimation.py +72 -0
  121. illusion/skills/__init__.py +60 -0
  122. illusion/skills/bundled/__init__.py +110 -0
  123. illusion/skills/bundled/content/batch.md +86 -0
  124. illusion/skills/bundled/content/coding-guidelines.md +70 -0
  125. illusion/skills/bundled/content/debug.md +38 -0
  126. illusion/skills/bundled/content/loop.md +82 -0
  127. illusion/skills/bundled/content/remember.md +105 -0
  128. illusion/skills/bundled/content/simplify.md +53 -0
  129. illusion/skills/bundled/content/skillify.md +113 -0
  130. illusion/skills/bundled/content/stuck.md +54 -0
  131. illusion/skills/bundled/content/update-config.md +329 -0
  132. illusion/skills/bundled/content/verify.md +74 -0
  133. illusion/skills/loader.py +219 -0
  134. illusion/skills/registry.py +40 -0
  135. illusion/skills/types.py +24 -0
  136. illusion/state/__init__.py +18 -0
  137. illusion/state/app_state.py +67 -0
  138. illusion/state/store.py +93 -0
  139. illusion/swarm/__init__.py +71 -0
  140. illusion/swarm/agent_executor.py +857 -0
  141. illusion/swarm/in_process.py +259 -0
  142. illusion/swarm/subprocess_backend.py +136 -0
  143. illusion/swarm/team_helpers.py +123 -0
  144. illusion/swarm/types.py +159 -0
  145. illusion/swarm/worktree.py +347 -0
  146. illusion/tasks/__init__.py +33 -0
  147. illusion/tasks/local_agent_task.py +42 -0
  148. illusion/tasks/local_shell_task.py +27 -0
  149. illusion/tasks/manager.py +377 -0
  150. illusion/tasks/stop_task.py +21 -0
  151. illusion/tasks/types.py +88 -0
  152. illusion/tools/__init__.py +126 -0
  153. illusion/tools/agent_tool.py +388 -0
  154. illusion/tools/ask_user_question_tool.py +186 -0
  155. illusion/tools/base.py +149 -0
  156. illusion/tools/bash_tool.py +413 -0
  157. illusion/tools/config_tool.py +90 -0
  158. illusion/tools/cron_tool.py +473 -0
  159. illusion/tools/enter_plan_mode_tool.py +147 -0
  160. illusion/tools/enter_worktree_tool.py +188 -0
  161. illusion/tools/exit_plan_mode_tool.py +69 -0
  162. illusion/tools/exit_worktree_tool.py +225 -0
  163. illusion/tools/file_edit_tool.py +283 -0
  164. illusion/tools/file_read_tool.py +294 -0
  165. illusion/tools/file_write_tool.py +184 -0
  166. illusion/tools/glob_tool.py +165 -0
  167. illusion/tools/grep_tool.py +190 -0
  168. illusion/tools/list_mcp_resources_tool.py +80 -0
  169. illusion/tools/lsp_tool.py +333 -0
  170. illusion/tools/mcp_auth_tool.py +100 -0
  171. illusion/tools/mcp_tool.py +75 -0
  172. illusion/tools/notebook_edit_tool.py +242 -0
  173. illusion/tools/powershell_tool.py +334 -0
  174. illusion/tools/read_mcp_resource_tool.py +63 -0
  175. illusion/tools/repl_tool.py +100 -0
  176. illusion/tools/send_message_tool.py +112 -0
  177. illusion/tools/shell_common.py +187 -0
  178. illusion/tools/skill_tool.py +86 -0
  179. illusion/tools/sleep_tool.py +62 -0
  180. illusion/tools/structured_output_tool.py +58 -0
  181. illusion/tools/task_create_tool.py +98 -0
  182. illusion/tools/task_get_tool.py +94 -0
  183. illusion/tools/task_list_tool.py +94 -0
  184. illusion/tools/task_output_tool.py +55 -0
  185. illusion/tools/task_stop_tool.py +52 -0
  186. illusion/tools/task_update_tool.py +224 -0
  187. illusion/tools/team_create_tool.py +236 -0
  188. illusion/tools/team_delete_tool.py +104 -0
  189. illusion/tools/todo_write_tool.py +198 -0
  190. illusion/tools/tool_search_tool.py +156 -0
  191. illusion/tools/web_fetch_tool.py +264 -0
  192. illusion/tools/web_search_tool.py +186 -0
  193. illusion/ui/__init__.py +23 -0
  194. illusion/ui/app.py +258 -0
  195. illusion/ui/backend_host.py +1180 -0
  196. illusion/ui/input.py +86 -0
  197. illusion/ui/output.py +363 -0
  198. illusion/ui/permission_dialog.py +47 -0
  199. illusion/ui/permission_store.py +99 -0
  200. illusion/ui/protocol.py +384 -0
  201. illusion/ui/react_launcher.py +280 -0
  202. illusion/ui/runtime.py +787 -0
  203. illusion/ui/textual_app.py +603 -0
  204. illusion/ui/web/__init__.py +10 -0
  205. illusion/ui/web/server.py +87 -0
  206. illusion/ui/web/ws_host.py +1197 -0
  207. illusion/utils/__init__.py +0 -0
  208. illusion/utils/ripgrep.py +299 -0
  209. illusion/utils/shell.py +248 -0
  210. illusion_code-0.1.0.dist-info/METADATA +1159 -0
  211. illusion_code-0.1.0.dist-info/RECORD +214 -0
  212. illusion_code-0.1.0.dist-info/WHEEL +4 -0
  213. illusion_code-0.1.0.dist-info/entry_points.txt +2 -0
  214. illusion_code-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,333 @@
1
+ """
2
+ 轻量级代码智能工具
3
+ ==================
4
+
5
+ 本模块提供基于 AST 的代码智能查询功能,用于 Python 代码的定义、引用、符号搜索等。
6
+
7
+ 主要组件:
8
+ - LspTool: 代码智能查询工具
9
+
10
+ 使用示例:
11
+ >>> from illusion.tools import LspTool
12
+ >>> tool = LspTool()
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from pathlib import Path
18
+ from typing import Any, Literal
19
+
20
+ from pydantic import BaseModel, Field, model_validator
21
+
22
+ from illusion.services.lsp import (
23
+ find_references,
24
+ go_to_definition,
25
+ go_to_implementation,
26
+ hover,
27
+ incoming_calls,
28
+ list_document_symbols,
29
+ outgoing_calls,
30
+ prepare_call_hierarchy,
31
+ workspace_symbol_search,
32
+ )
33
+ from illusion.tools.base import BaseTool, ToolExecutionContext, ToolResult
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # 驼峰 → 蛇形操作名映射(兼容两种命名风格)
37
+ # ---------------------------------------------------------------------------
38
+ _OP_CAMEL_TO_SNAKE: dict[str, str] = {
39
+ "goToDefinition": "go_to_definition",
40
+ "findReferences": "find_references",
41
+ "documentSymbol": "document_symbol",
42
+ "workspaceSymbol": "workspace_symbol",
43
+ "goToImplementation": "go_to_implementation",
44
+ "prepareCallHierarchy": "prepare_call_hierarchy",
45
+ "incomingCalls": "incoming_calls",
46
+ "outgoingCalls": "outgoing_calls",
47
+ }
48
+
49
+
50
+ class LspToolInput(BaseModel):
51
+ """代码智能查询参数。
52
+
53
+ 属性:
54
+ operation: 要执行的代码智能操作(支持驼峰和蛇形两种命名)
55
+ file_path: 用于基于文件的操作的源文件路径
56
+ symbol: 要查找的显式符号名称
57
+ line: 基于位置查询的 1-based 行号
58
+ character: 基于位置查询的 1-based 字符偏移
59
+ query: workspace_symbol 的子字符串查询
60
+ """
61
+
62
+ operation: Literal[
63
+ "document_symbol",
64
+ "workspace_symbol",
65
+ "go_to_definition",
66
+ "find_references",
67
+ "hover",
68
+ "go_to_implementation",
69
+ "prepare_call_hierarchy",
70
+ "incoming_calls",
71
+ "outgoing_calls",
72
+ ] = Field(description="The code intelligence operation to perform")
73
+ file_path: str | None = Field(default=None, description="Path to the source file for file-based operations")
74
+ symbol: str | None = Field(default=None, description="Explicit symbol name to look up")
75
+ line: int | None = Field(default=None, ge=1, description="1-based line number for position-based lookups")
76
+ character: int | None = Field(default=None, ge=1, description="1-based character offset for position-based lookups")
77
+ query: str | None = Field(default=None, description="Substring query for workspace_symbol")
78
+
79
+ @model_validator(mode="before")
80
+ @classmethod
81
+ def normalize_operation(cls, data: Any) -> Any:
82
+ """将驼峰操作名映射为蛇形。"""
83
+ if isinstance(data, dict) and "operation" in data:
84
+ op: str = data["operation"]
85
+ if op in _OP_CAMEL_TO_SNAKE:
86
+ data["operation"] = _OP_CAMEL_TO_SNAKE[op]
87
+ return data
88
+
89
+ @model_validator(mode="after")
90
+ def validate_arguments(self) -> "LspToolInput":
91
+ # workspace_symbol 需要 query 参数
92
+ if self.operation == "workspace_symbol":
93
+ if not self.query:
94
+ raise ValueError("workspace_symbol requires query")
95
+ return self
96
+ # 其他操作需要 file_path
97
+ if not self.file_path:
98
+ raise ValueError(f"{self.operation} requires file_path")
99
+ # document_symbol 不需要 symbol 或 line
100
+ if self.operation == "document_symbol":
101
+ return self
102
+ # 其余操作需要 symbol 或 line
103
+ if not self.symbol and self.line is None:
104
+ raise ValueError(f"{self.operation} requires symbol or line")
105
+ return self
106
+
107
+
108
+ class LspTool(BaseTool):
109
+ """Python 源文件的只读代码智能(基于 AST 分析)。
110
+
111
+ 用于查询代码定义、引用、悬停信息、调用层次等。
112
+ """
113
+
114
+ name = "lsp"
115
+ description = """Interact with Language Server Protocol (LSP) servers to get code intelligence features.
116
+
117
+ Supported operations:
118
+ - goToDefinition: Find where a symbol is defined
119
+ - findReferences: Find all references to a symbol
120
+ - hover: Get hover information (documentation, type info) for a symbol
121
+ - documentSymbol: Get all symbols (functions, classes, variables) in a document
122
+ - workspaceSymbol: Search for symbols across the entire workspace
123
+ - goToImplementation: Find implementations of an interface or abstract method
124
+ - prepareCallHierarchy: Get call hierarchy item at a position (functions/methods)
125
+ - incomingCalls: Find all functions/methods that call the function at a position
126
+ - outgoingCalls: Find all functions/methods called by the function at a position
127
+
128
+ All operations require:
129
+ - filePath: The file to operate on
130
+ - line: The line number (1-based, as shown in editors)
131
+ - character: The character offset (1-based, as shown in editors)
132
+
133
+ Note: LSP servers must be configured for the file type. If no server is available, an error will be returned."""
134
+ input_model = LspToolInput
135
+
136
+ def is_read_only(self, arguments: LspToolInput) -> bool:
137
+ del arguments
138
+ return True
139
+
140
+ async def execute(self, arguments: LspToolInput, context: ToolExecutionContext) -> ToolResult:
141
+ # 获取工作区根目录
142
+ root = context.cwd.resolve()
143
+
144
+ # workspace_symbol 操作 — 不需要 file_path
145
+ if arguments.operation == "workspace_symbol":
146
+ results = workspace_symbol_search(root, arguments.query or "")
147
+ return ToolResult(output=_format_symbol_locations(results, root))
148
+
149
+ # 解析文件路径(后续操作都需要)
150
+ assert arguments.file_path is not None # 已在上述验证
151
+ file_path = _resolve_path(root, arguments.file_path)
152
+ if not file_path.exists():
153
+ return ToolResult(output=f"File not found: {file_path}", is_error=True)
154
+ if file_path.suffix != ".py":
155
+ return ToolResult(output="The lsp tool currently supports Python files only.", is_error=True)
156
+
157
+ # document_symbol
158
+ if arguments.operation == "document_symbol":
159
+ return ToolResult(output=_format_symbol_locations(list_document_symbols(file_path), root))
160
+
161
+ # go_to_definition
162
+ if arguments.operation == "go_to_definition":
163
+ results = go_to_definition(
164
+ root=root,
165
+ file_path=file_path,
166
+ symbol=arguments.symbol,
167
+ line=arguments.line,
168
+ character=arguments.character,
169
+ )
170
+ return ToolResult(output=_format_symbol_locations(results, root))
171
+
172
+ # find_references
173
+ if arguments.operation == "find_references":
174
+ results = find_references(
175
+ root=root,
176
+ file_path=file_path,
177
+ symbol=arguments.symbol,
178
+ line=arguments.line,
179
+ character=arguments.character,
180
+ )
181
+ return ToolResult(output=_format_references(results, root))
182
+
183
+ # hover
184
+ if arguments.operation == "hover":
185
+ result = hover(
186
+ root=root,
187
+ file_path=file_path,
188
+ symbol=arguments.symbol,
189
+ line=arguments.line,
190
+ character=arguments.character,
191
+ )
192
+ if result is None:
193
+ return ToolResult(output="(no hover result)")
194
+ parts = [
195
+ f"{result.kind} {result.name}",
196
+ f"path: {_display_path(result.path, root)}:{result.line}:{result.character}",
197
+ ]
198
+ if result.signature:
199
+ parts.append(f"signature: {result.signature}")
200
+ if result.docstring:
201
+ parts.append(f"docstring: {result.docstring.strip()}")
202
+ return ToolResult(output="\n".join(parts))
203
+
204
+ # go_to_implementation
205
+ if arguments.operation == "go_to_implementation":
206
+ results = go_to_implementation(
207
+ root=root,
208
+ file_path=file_path,
209
+ symbol=arguments.symbol,
210
+ line=arguments.line,
211
+ character=arguments.character,
212
+ )
213
+ return ToolResult(output=_format_symbol_locations(results, root))
214
+
215
+ # prepare_call_hierarchy
216
+ if arguments.operation == "prepare_call_hierarchy":
217
+ result = prepare_call_hierarchy(
218
+ root=root,
219
+ file_path=file_path,
220
+ symbol=arguments.symbol,
221
+ line=arguments.line,
222
+ character=arguments.character,
223
+ )
224
+ if result is None:
225
+ return ToolResult(output="(no call hierarchy item)")
226
+ return ToolResult(output=_format_hierarchy_item(result, root))
227
+
228
+ # incoming_calls
229
+ if arguments.operation == "incoming_calls":
230
+ results = incoming_calls(
231
+ root=root,
232
+ file_path=file_path,
233
+ symbol=arguments.symbol,
234
+ line=arguments.line,
235
+ character=arguments.character,
236
+ )
237
+ return ToolResult(output=_format_incoming_calls(results, root))
238
+
239
+ # outgoing_calls
240
+ if arguments.operation == "outgoing_calls":
241
+ results = outgoing_calls(
242
+ root=root,
243
+ file_path=file_path,
244
+ symbol=arguments.symbol,
245
+ line=arguments.line,
246
+ character=arguments.character,
247
+ )
248
+ return ToolResult(output=_format_outgoing_calls(results, root))
249
+
250
+
251
+ # ---------------------------------------------------------------------------
252
+ # 路径辅助函数
253
+ # ---------------------------------------------------------------------------
254
+
255
+
256
+ def _resolve_path(base: Path, candidate: str) -> Path:
257
+ """解析相对路径为绝对路径。"""
258
+ path = Path(candidate).expanduser()
259
+ if not path.is_absolute():
260
+ path = base / path
261
+ return path.resolve()
262
+
263
+
264
+ def _display_path(path: Path, root: Path) -> str:
265
+ """将路径显示为相对于根目录的路径。"""
266
+ try:
267
+ return str(path.relative_to(root))
268
+ except ValueError:
269
+ return str(path)
270
+
271
+
272
+ # ---------------------------------------------------------------------------
273
+ # 结果格式化函数
274
+ # ---------------------------------------------------------------------------
275
+
276
+
277
+ def _format_symbol_locations(results, root: Path) -> str:
278
+ """格式化符号位置结果。"""
279
+ if not results:
280
+ return "(no results)"
281
+ lines = []
282
+ for item in results:
283
+ lines.append(
284
+ f"{item.kind} {item.name} - {_display_path(item.path, root)}:{item.line}:{item.character}"
285
+ )
286
+ if item.signature:
287
+ lines.append(f" signature: {item.signature}")
288
+ if item.docstring:
289
+ lines.append(f" docstring: {item.docstring.strip()}")
290
+ return "\n".join(lines)
291
+
292
+
293
+ def _format_references(results: list[tuple[Path, int, str]], root: Path) -> str:
294
+ """格式化引用结果。"""
295
+ if not results:
296
+ return "(no results)"
297
+ return "\n".join(f"{_display_path(path, root)}:{line}:{text}" for path, line, text in results)
298
+
299
+
300
+ def _format_hierarchy_item(item, root: Path) -> str:
301
+ """格式化调用层次项。"""
302
+ parts = [
303
+ f"{item.kind} {item.name}",
304
+ f"path: {_display_path(item.path, root)}:{item.line}:{item.character}",
305
+ ]
306
+ if item.signature:
307
+ parts.append(f"signature: {item.signature}")
308
+ if item.docstring:
309
+ parts.append(f"docstring: {item.docstring.strip()}")
310
+ return "\n".join(parts)
311
+
312
+
313
+ def _format_incoming_calls(results: list[tuple[Path, int, str, str]], root: Path) -> str:
314
+ """格式化入向调用结果。"""
315
+ if not results:
316
+ return "(no incoming calls)"
317
+ lines = []
318
+ for path, line, caller, text in results:
319
+ lines.append(f"{_display_path(path, root)}:{line} [{caller}] {text}")
320
+ return "\n".join(lines)
321
+
322
+
323
+ def _format_outgoing_calls(results: list[tuple[str, Path, int]], root: Path) -> str:
324
+ """格式化出向调用结果。"""
325
+ if not results:
326
+ return "(no outgoing calls)"
327
+ lines = []
328
+ for name, path, line in results:
329
+ if path != Path() and line > 0:
330
+ lines.append(f"{name} -> {_display_path(path, root)}:{line}")
331
+ else:
332
+ lines.append(f"{name} -> (definition not found)")
333
+ return "\n".join(lines)
@@ -0,0 +1,100 @@
1
+ """
2
+ MCP 认证配置工具
3
+ ================
4
+
5
+ 本模块提供更新 MCP 认证配置的功能。
6
+
7
+ 主要组件:
8
+ - McpAuthTool: 配置 MCP 服务器认证的工具
9
+
10
+ 使用示例:
11
+ >>> from illusion.tools import McpAuthTool
12
+ >>> tool = McpAuthTool()
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from pydantic import BaseModel, Field
18
+
19
+ from illusion.config.settings import load_settings, save_settings
20
+ from illusion.mcp.types import McpHttpServerConfig, McpStdioServerConfig, McpWebSocketServerConfig
21
+ from illusion.tools.base import BaseTool, ToolExecutionContext, ToolResult
22
+
23
+
24
+ class McpAuthToolInput(BaseModel):
25
+ """MCP 认证更新参数。
26
+
27
+ 属性:
28
+ server_name: 配置的 MCP 服务器名称
29
+ mode: 认证模式:bearer、header 或 env
30
+ value: 要保存的密钥值
31
+ key: 可选的 header 或 env 键覆盖
32
+ """
33
+
34
+ server_name: str = Field(description="Configured MCP server name")
35
+ mode: str = Field(description="Auth mode: bearer, header, or env")
36
+ value: str = Field(description="Secret value to persist")
37
+ key: str | None = Field(default=None, description="Header or env key override")
38
+
39
+
40
+ class McpAuthTool(BaseTool):
41
+ """为服务器持久化 MCP 认证设置。
42
+
43
+ 用于配置 MCP 服务器的认证信息。
44
+ """
45
+
46
+ name = "mcp_auth"
47
+ description = "Configure auth for an MCP server and reconnect all active sessions when possible."
48
+ input_model = McpAuthToolInput
49
+
50
+ async def execute(self, arguments: McpAuthToolInput, context: ToolExecutionContext) -> ToolResult:
51
+ # 加载设置
52
+ settings = load_settings()
53
+ mcp_manager = context.metadata.get("mcp_manager")
54
+ # 尝试从设置或 mcp_manager 获取服务器配置
55
+ config = settings.mcp_servers.get(arguments.server_name)
56
+ if config is None and mcp_manager is not None:
57
+ getter = getattr(mcp_manager, "get_server_config", None)
58
+ if callable(getter):
59
+ config = getter(arguments.server_name)
60
+ if config is None:
61
+ return ToolResult(output=f"Unknown MCP server: {arguments.server_name}", is_error=True)
62
+
63
+ # 根据服务器类型处理认证
64
+ if isinstance(config, McpStdioServerConfig):
65
+ # stdio 服务器支持 env 或 bearer 模式
66
+ if arguments.mode not in {"env", "bearer"}:
67
+ return ToolResult(output="stdio MCP auth supports env or bearer modes", is_error=True)
68
+ env_key = arguments.key or "MCP_AUTH_TOKEN"
69
+ env = dict(config.env or {})
70
+ env[env_key] = f"Bearer {arguments.value}" if arguments.mode == "bearer" else arguments.value
71
+ updated = config.model_copy(update={"env": env})
72
+ elif isinstance(config, (McpHttpServerConfig, McpWebSocketServerConfig)):
73
+ # http/ws 服务器支持 header 或 bearer 模式
74
+ if arguments.mode not in {"header", "bearer"}:
75
+ return ToolResult(output="http/ws MCP auth supports header or bearer modes", is_error=True)
76
+ header_key = arguments.key or "Authorization"
77
+ headers = dict(config.headers)
78
+ headers[header_key] = (
79
+ f"Bearer {arguments.value}" if arguments.mode == "bearer" else arguments.value
80
+ )
81
+ updated = config.model_copy(update={"headers": headers})
82
+ else:
83
+ return ToolResult(output="Unsupported MCP server config type", is_error=True)
84
+
85
+ # 保存设置
86
+ settings.mcp_servers[arguments.server_name] = updated
87
+ save_settings(settings)
88
+
89
+ # 尝试重新连接
90
+ if mcp_manager is not None:
91
+ try:
92
+ mcp_manager.update_server_config(arguments.server_name, updated)
93
+ await mcp_manager.reconnect_all()
94
+ except Exception as exc: # 防御性处理
95
+ return ToolResult(
96
+ output=f"Saved MCP auth for {arguments.server_name}, but reconnect failed: {exc}",
97
+ is_error=True,
98
+ )
99
+
100
+ return ToolResult(output=f"Saved MCP auth for {arguments.server_name}")
@@ -0,0 +1,75 @@
1
+ """
2
+ MCP 工具适配器
3
+ =============
4
+
5
+ 本模块提供将 MCP(Model Context Protocol)工具暴露为普通 IllusionCode 工具的功能。
6
+
7
+ 主要组件:
8
+ - McpToolAdapter: 将一个 MCP 工具作为普通工具暴露
9
+
10
+ 使用示例:
11
+ >>> from illusion.tools.mcp_tool import McpToolAdapter
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import re
17
+
18
+ from pydantic import BaseModel, Field, create_model
19
+
20
+ from illusion.mcp.client import McpClientManager
21
+ from illusion.mcp.types import McpToolInfo
22
+ from illusion.tools.base import BaseTool, ToolExecutionContext, ToolResult
23
+
24
+
25
+ class McpToolAdapter(BaseTool):
26
+ """将一个 MCP 工具作为普通 IllusionCode 工具暴露。
27
+
28
+ 用于集成 MCP 服务器提供的工具。
29
+ """
30
+
31
+ def __init__(self, manager: McpClientManager, tool_info: McpToolInfo) -> None:
32
+ self._manager = manager
33
+ self._tool_info = tool_info
34
+ # 清理服务器和工具名称以形成有效的工具名
35
+ server_segment = _sanitize_tool_segment(tool_info.server_name)
36
+ tool_segment = _sanitize_tool_segment(tool_info.name)
37
+ self.name = f"mcp__{server_segment}__{tool_segment}"
38
+ self.description = tool_info.description or f"MCP tool {tool_info.name}"
39
+ self.input_model = _input_model_from_schema(self.name, tool_info.input_schema)
40
+
41
+ async def execute(self, arguments: BaseModel, context: ToolExecutionContext) -> ToolResult:
42
+ del context
43
+ # 调用 MCP 工具
44
+ output = await self._manager.call_tool(
45
+ self._tool_info.server_name,
46
+ self._tool_info.name,
47
+ arguments.model_dump(mode="json"),
48
+ )
49
+ return ToolResult(output=output)
50
+
51
+
52
+ def _input_model_from_schema(tool_name: str, schema: dict[str, object]) -> type[BaseModel]:
53
+ """从 JSON schema 创建 Pydantic 输入模型。"""
54
+ properties = schema.get("properties", {})
55
+ if not isinstance(properties, dict):
56
+ return create_model(f"{tool_name.title()}Input")
57
+
58
+ fields = {}
59
+ required = set(schema.get("required", [])) if isinstance(schema.get("required", []), list) else set()
60
+ for key in properties:
61
+ default = ... if key in required else None
62
+ fields[key] = (object | None, Field(default=default))
63
+ return create_model(f"{tool_name.title().replace('-', '_')}Input", **fields)
64
+
65
+
66
+ def _sanitize_tool_segment(value: str) -> str:
67
+ """清理工具段以形成有效的标识符。"""
68
+ # 移除非字母数字字符
69
+ sanitized = re.sub(r"[^A-Za-z0-9_-]", "_", value)
70
+ if not sanitized:
71
+ return "tool"
72
+ # 确保以字母开头
73
+ if not sanitized[0].isalpha():
74
+ return f"mcp_{sanitized}"
75
+ return sanitized