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.
- illusion/__init__.py +24 -0
- illusion/__main__.py +15 -0
- illusion/_frontend/dist/index.mjs +39208 -0
- illusion/_frontend/package.json +27 -0
- illusion/_frontend/src/App.tsx +624 -0
- illusion/_frontend/src/components/CommandPicker.tsx +98 -0
- illusion/_frontend/src/components/Composer.tsx +55 -0
- illusion/_frontend/src/components/ComposerController.tsx +128 -0
- illusion/_frontend/src/components/ConversationView.tsx +750 -0
- illusion/_frontend/src/components/Footer.tsx +25 -0
- illusion/_frontend/src/components/MarkdownContent.tsx +537 -0
- illusion/_frontend/src/components/MarkdownTable.tsx +245 -0
- illusion/_frontend/src/components/ModalHost.tsx +425 -0
- illusion/_frontend/src/components/MultilineTextInput.tsx +250 -0
- illusion/_frontend/src/components/PromptInput.tsx +64 -0
- illusion/_frontend/src/components/SelectModal.tsx +78 -0
- illusion/_frontend/src/components/SidePanel.tsx +175 -0
- illusion/_frontend/src/components/Spinner.tsx +77 -0
- illusion/_frontend/src/components/StatusBar.tsx +142 -0
- illusion/_frontend/src/components/SwarmPanel.tsx +141 -0
- illusion/_frontend/src/components/TodoPanel.tsx +126 -0
- illusion/_frontend/src/components/ToolCallDisplay.tsx +202 -0
- illusion/_frontend/src/components/TranscriptPane.tsx +79 -0
- illusion/_frontend/src/components/WelcomeBanner.tsx +37 -0
- illusion/_frontend/src/hooks/useBackendSession.ts +468 -0
- illusion/_frontend/src/hooks/useTerminalSize.ts +9 -0
- illusion/_frontend/src/i18n.ts +78 -0
- illusion/_frontend/src/index.tsx +42 -0
- illusion/_frontend/src/theme/ThemeContext.tsx +19 -0
- illusion/_frontend/src/theme/builtinThemes.ts +89 -0
- illusion/_frontend/src/types.ts +110 -0
- illusion/_frontend/src/utils/markdown.ts +33 -0
- illusion/_frontend/src/utils/thinking.ts +191 -0
- illusion/_frontend/tsconfig.json +13 -0
- illusion/_web_dist/assets/index-BseIw-ik.css +10 -0
- illusion/_web_dist/assets/index-C_0ZWMuW.js +82 -0
- illusion/_web_dist/index.html +16 -0
- illusion/api/__init__.py +36 -0
- illusion/api/client.py +568 -0
- illusion/api/codex_client.py +563 -0
- illusion/api/compat.py +138 -0
- illusion/api/effort.py +128 -0
- illusion/api/errors.py +57 -0
- illusion/api/openai_client.py +819 -0
- illusion/api/provider.py +148 -0
- illusion/api/registry.py +479 -0
- illusion/api/usage.py +45 -0
- illusion/auth/__init__.py +50 -0
- illusion/auth/copilot.py +419 -0
- illusion/auth/external.py +612 -0
- illusion/auth/flows.py +58 -0
- illusion/auth/manager.py +214 -0
- illusion/auth/storage.py +372 -0
- illusion/bridge/__init__.py +38 -0
- illusion/bridge/manager.py +190 -0
- illusion/bridge/session_runner.py +84 -0
- illusion/bridge/types.py +113 -0
- illusion/bridge/work_secret.py +131 -0
- illusion/cli.py +1228 -0
- illusion/commands/__init__.py +32 -0
- illusion/commands/registry.py +1934 -0
- illusion/config/__init__.py +39 -0
- illusion/config/i18n.py +522 -0
- illusion/config/paths.py +259 -0
- illusion/config/settings.py +564 -0
- illusion/coordinator/__init__.py +41 -0
- illusion/coordinator/agent_definitions.py +1093 -0
- illusion/coordinator/coordinator_mode.py +127 -0
- illusion/engine/__init__.py +95 -0
- illusion/engine/cost_tracker.py +55 -0
- illusion/engine/messages.py +369 -0
- illusion/engine/query.py +632 -0
- illusion/engine/query_engine.py +343 -0
- illusion/engine/stream_events.py +169 -0
- illusion/hooks/__init__.py +67 -0
- illusion/hooks/events.py +43 -0
- illusion/hooks/executor.py +397 -0
- illusion/hooks/hot_reload.py +74 -0
- illusion/hooks/loader.py +133 -0
- illusion/hooks/schemas.py +121 -0
- illusion/hooks/types.py +86 -0
- illusion/mcp/__init__.py +104 -0
- illusion/mcp/client.py +377 -0
- illusion/mcp/config.py +140 -0
- illusion/mcp/types.py +175 -0
- illusion/memory/__init__.py +36 -0
- illusion/memory/manager.py +94 -0
- illusion/memory/memdir.py +58 -0
- illusion/memory/paths.py +57 -0
- illusion/memory/scan.py +120 -0
- illusion/memory/search.py +83 -0
- illusion/memory/types.py +43 -0
- illusion/output_styles/__init__.py +15 -0
- illusion/output_styles/loader.py +64 -0
- illusion/permissions/__init__.py +39 -0
- illusion/permissions/checker.py +174 -0
- illusion/permissions/modes.py +38 -0
- illusion/platforms.py +148 -0
- illusion/plugins/__init__.py +71 -0
- illusion/plugins/bundled/__init__.py +0 -0
- illusion/plugins/installer.py +59 -0
- illusion/plugins/loader.py +301 -0
- illusion/plugins/schemas.py +51 -0
- illusion/plugins/types.py +56 -0
- illusion/prompts/__init__.py +29 -0
- illusion/prompts/claudemd.py +74 -0
- illusion/prompts/context.py +187 -0
- illusion/prompts/environment.py +189 -0
- illusion/prompts/system_prompt.py +155 -0
- illusion/py.typed +0 -0
- illusion/sandbox/__init__.py +29 -0
- illusion/sandbox/adapter.py +174 -0
- illusion/services/__init__.py +59 -0
- illusion/services/compact/__init__.py +1015 -0
- illusion/services/cron.py +338 -0
- illusion/services/cron_scheduler.py +715 -0
- illusion/services/file_history.py +258 -0
- illusion/services/lsp/__init__.py +455 -0
- illusion/services/session_storage.py +237 -0
- illusion/services/token_estimation.py +72 -0
- illusion/skills/__init__.py +60 -0
- illusion/skills/bundled/__init__.py +110 -0
- illusion/skills/bundled/content/batch.md +86 -0
- illusion/skills/bundled/content/coding-guidelines.md +70 -0
- illusion/skills/bundled/content/debug.md +38 -0
- illusion/skills/bundled/content/loop.md +82 -0
- illusion/skills/bundled/content/remember.md +105 -0
- illusion/skills/bundled/content/simplify.md +53 -0
- illusion/skills/bundled/content/skillify.md +113 -0
- illusion/skills/bundled/content/stuck.md +54 -0
- illusion/skills/bundled/content/update-config.md +329 -0
- illusion/skills/bundled/content/verify.md +74 -0
- illusion/skills/loader.py +219 -0
- illusion/skills/registry.py +40 -0
- illusion/skills/types.py +24 -0
- illusion/state/__init__.py +18 -0
- illusion/state/app_state.py +67 -0
- illusion/state/store.py +93 -0
- illusion/swarm/__init__.py +71 -0
- illusion/swarm/agent_executor.py +857 -0
- illusion/swarm/in_process.py +259 -0
- illusion/swarm/subprocess_backend.py +136 -0
- illusion/swarm/team_helpers.py +123 -0
- illusion/swarm/types.py +159 -0
- illusion/swarm/worktree.py +347 -0
- illusion/tasks/__init__.py +33 -0
- illusion/tasks/local_agent_task.py +42 -0
- illusion/tasks/local_shell_task.py +27 -0
- illusion/tasks/manager.py +377 -0
- illusion/tasks/stop_task.py +21 -0
- illusion/tasks/types.py +88 -0
- illusion/tools/__init__.py +126 -0
- illusion/tools/agent_tool.py +388 -0
- illusion/tools/ask_user_question_tool.py +186 -0
- illusion/tools/base.py +149 -0
- illusion/tools/bash_tool.py +413 -0
- illusion/tools/config_tool.py +90 -0
- illusion/tools/cron_tool.py +473 -0
- illusion/tools/enter_plan_mode_tool.py +147 -0
- illusion/tools/enter_worktree_tool.py +188 -0
- illusion/tools/exit_plan_mode_tool.py +69 -0
- illusion/tools/exit_worktree_tool.py +225 -0
- illusion/tools/file_edit_tool.py +283 -0
- illusion/tools/file_read_tool.py +294 -0
- illusion/tools/file_write_tool.py +184 -0
- illusion/tools/glob_tool.py +165 -0
- illusion/tools/grep_tool.py +190 -0
- illusion/tools/list_mcp_resources_tool.py +80 -0
- illusion/tools/lsp_tool.py +333 -0
- illusion/tools/mcp_auth_tool.py +100 -0
- illusion/tools/mcp_tool.py +75 -0
- illusion/tools/notebook_edit_tool.py +242 -0
- illusion/tools/powershell_tool.py +334 -0
- illusion/tools/read_mcp_resource_tool.py +63 -0
- illusion/tools/repl_tool.py +100 -0
- illusion/tools/send_message_tool.py +112 -0
- illusion/tools/shell_common.py +187 -0
- illusion/tools/skill_tool.py +86 -0
- illusion/tools/sleep_tool.py +62 -0
- illusion/tools/structured_output_tool.py +58 -0
- illusion/tools/task_create_tool.py +98 -0
- illusion/tools/task_get_tool.py +94 -0
- illusion/tools/task_list_tool.py +94 -0
- illusion/tools/task_output_tool.py +55 -0
- illusion/tools/task_stop_tool.py +52 -0
- illusion/tools/task_update_tool.py +224 -0
- illusion/tools/team_create_tool.py +236 -0
- illusion/tools/team_delete_tool.py +104 -0
- illusion/tools/todo_write_tool.py +198 -0
- illusion/tools/tool_search_tool.py +156 -0
- illusion/tools/web_fetch_tool.py +264 -0
- illusion/tools/web_search_tool.py +186 -0
- illusion/ui/__init__.py +23 -0
- illusion/ui/app.py +258 -0
- illusion/ui/backend_host.py +1180 -0
- illusion/ui/input.py +86 -0
- illusion/ui/output.py +363 -0
- illusion/ui/permission_dialog.py +47 -0
- illusion/ui/permission_store.py +99 -0
- illusion/ui/protocol.py +384 -0
- illusion/ui/react_launcher.py +280 -0
- illusion/ui/runtime.py +787 -0
- illusion/ui/textual_app.py +603 -0
- illusion/ui/web/__init__.py +10 -0
- illusion/ui/web/server.py +87 -0
- illusion/ui/web/ws_host.py +1197 -0
- illusion/utils/__init__.py +0 -0
- illusion/utils/ripgrep.py +299 -0
- illusion/utils/shell.py +248 -0
- illusion_code-0.1.0.dist-info/METADATA +1159 -0
- illusion_code-0.1.0.dist-info/RECORD +214 -0
- illusion_code-0.1.0.dist-info/WHEEL +4 -0
- illusion_code-0.1.0.dist-info/entry_points.txt +2 -0
- 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
|