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,564 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Settings 模型和加载逻辑模块
|
|
3
|
+
===========================
|
|
4
|
+
|
|
5
|
+
本模块提供 IllusionCode 的设置管理功能,包括:
|
|
6
|
+
- Settings: 主设置模型(env_N 分组格式)
|
|
7
|
+
- EnvConfig: 环境/提供商组配置
|
|
8
|
+
- 各种设置加载和保存函数
|
|
9
|
+
|
|
10
|
+
设置解析优先级(从高到低):
|
|
11
|
+
1. CLI 参数
|
|
12
|
+
2. 环境变量(ANTHROPIC_API_KEY, ANTHROPIC_MODEL 等)
|
|
13
|
+
3. 配置文件(~/.illusion/settings.json)
|
|
14
|
+
4. 默认值
|
|
15
|
+
|
|
16
|
+
类说明:
|
|
17
|
+
- Settings: 主设置模型,使用 env_N 分组管理多个环境配置
|
|
18
|
+
- EnvConfig: 单个环境的配置(api_format, base_url, api_key, model_N 等)
|
|
19
|
+
- PermissionSettings: 权限模式配置
|
|
20
|
+
- MemorySettings: 记忆系统配置
|
|
21
|
+
- SandboxSettings: 沙箱运行时配置
|
|
22
|
+
|
|
23
|
+
使用示例:
|
|
24
|
+
>>> from illusion.config.settings import load_settings, Settings
|
|
25
|
+
>>> settings = load_settings()
|
|
26
|
+
>>> print(f"当前模型: {settings.active_model_name}")
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import json # 导入 json 模块用于配置文件读写
|
|
32
|
+
import os # 导入 os 模块用于环境变量访问
|
|
33
|
+
from dataclasses import dataclass # 导入 dataclass 用于创建不可变数据结构
|
|
34
|
+
from pathlib import Path # 导入 Path 用于路径处理
|
|
35
|
+
from typing import Any # 导入 Any 类型用于泛型
|
|
36
|
+
|
|
37
|
+
from pydantic import BaseModel, Field # 导入 pydantic 模型基类
|
|
38
|
+
|
|
39
|
+
from illusion.hooks.schemas import HookDefinition # 导入钩子定义
|
|
40
|
+
from illusion.mcp.types import McpServerConfig # 导入 MCP 服务器配置
|
|
41
|
+
from illusion.permissions.modes import PermissionMode # 导入权限模式
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class PathRuleConfig(BaseModel):
|
|
45
|
+
"""路径权限规则配置
|
|
46
|
+
|
|
47
|
+
使用 glob 模式定义路径级别的权限规则。
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
pattern: glob 模式字符串
|
|
51
|
+
allow: 是否允许访问,默认为 True
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
pattern: str # glob 模式,用于匹配路径
|
|
55
|
+
allow: bool = True # 默认为允许访问
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class PermissionSettings(BaseModel):
|
|
59
|
+
"""权限模式配置
|
|
60
|
+
|
|
61
|
+
配置系统的权限控制和行为限制。
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
mode: 权限模式
|
|
65
|
+
allowed_tools: 允许的工具列表
|
|
66
|
+
denied_tools: 拒绝的工具列表
|
|
67
|
+
path_rules: 路径规则列表
|
|
68
|
+
denied_commands: 拒绝的命令列表
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
mode: PermissionMode = PermissionMode.DEFAULT # 权限模式,默认为默认模式
|
|
72
|
+
allowed_tools: list[str] = Field(default_factory=list) # 允许的工具列表
|
|
73
|
+
denied_tools: list[str] = Field(default_factory=list) # 拒绝的工具列表
|
|
74
|
+
path_rules: list[PathRuleConfig] = Field(default_factory=list) # 路径权限规则
|
|
75
|
+
denied_commands: list[str] = Field(default_factory=list) # 拒绝的命令列表
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class MemorySettings(BaseModel):
|
|
79
|
+
"""记忆系统配置
|
|
80
|
+
|
|
81
|
+
配置 AI 记忆系统的行为和限制。
|
|
82
|
+
|
|
83
|
+
Attributes:
|
|
84
|
+
enabled: 是否启用记忆功能
|
|
85
|
+
max_files: 最大记忆文件数
|
|
86
|
+
max_entrypoint_lines: 最大入口文件行数
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
enabled: bool = True # 默认启用记忆功能
|
|
90
|
+
max_files: int = 5 # 默认最多记忆 5 个文件
|
|
91
|
+
max_entrypoint_lines: int = 200 # 默认入口文件最多 200 行
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class SandboxNetworkSettings(BaseModel):
|
|
95
|
+
"""沙箱网络限制配置
|
|
96
|
+
|
|
97
|
+
传递给沙箱运行时的操作系统级网络限制配置。
|
|
98
|
+
|
|
99
|
+
Attributes:
|
|
100
|
+
allowed_domains: 允许访问的域名列表
|
|
101
|
+
denied_domains: 拒绝访问的域名列表
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
allowed_domains: list[str] = Field(default_factory=list) # 允许的域名
|
|
105
|
+
denied_domains: list[str] = Field(default_factory=list) # 拒绝的域名
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class SandboxFilesystemSettings(BaseModel):
|
|
109
|
+
"""沙箱文件系统限制配置
|
|
110
|
+
|
|
111
|
+
传递给沙箱运行时的操作系统级文件系统限制配置。
|
|
112
|
+
|
|
113
|
+
Attributes:
|
|
114
|
+
allow_read: 允许读取的路径列表
|
|
115
|
+
deny_read: 拒绝读取的路径列表
|
|
116
|
+
allow_write: 允许写入的路径列表
|
|
117
|
+
deny_write: 拒绝写入的路径列表
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
allow_read: list[str] = Field(default_factory=list) # 允许读取的路径
|
|
121
|
+
deny_read: list[str] = Field(default_factory=list) # 拒绝读取的路径
|
|
122
|
+
allow_write: list[str] = Field(default_factory=lambda: ["."]) # 默认允许写入当前目录
|
|
123
|
+
deny_write: list[str] = Field(default_factory=list) # 拒绝写入的路径
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class SandboxSettings(BaseModel):
|
|
127
|
+
"""沙箱运行时集成配置
|
|
128
|
+
|
|
129
|
+
配置与沙箱运行时的集成选项。
|
|
130
|
+
|
|
131
|
+
Attributes:
|
|
132
|
+
enabled: 是否启用沙箱
|
|
133
|
+
fail_if_unavailable: 沙箱不可用时是否失败
|
|
134
|
+
enabled_platforms: 启用的平台列表
|
|
135
|
+
network: 网络限制配置
|
|
136
|
+
filesystem: 文件系统限制配置
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
enabled: bool = False # 默认不启用沙箱
|
|
140
|
+
fail_if_unavailable: bool = False # 沙箱不可用时不失败
|
|
141
|
+
enabled_platforms: list[str] = Field(default_factory=list) # 启用的平台
|
|
142
|
+
network: SandboxNetworkSettings = Field(default_factory=SandboxNetworkSettings) # 网络配置
|
|
143
|
+
filesystem: SandboxFilesystemSettings = Field(default_factory=SandboxFilesystemSettings) # 文件系统配置
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass(frozen=True)
|
|
147
|
+
class ResolvedAuth:
|
|
148
|
+
"""规范化的认证材料
|
|
149
|
+
|
|
150
|
+
用于构造 API 客户端的标准化认证信息。
|
|
151
|
+
|
|
152
|
+
Attributes:
|
|
153
|
+
provider: 提供商名称
|
|
154
|
+
auth_kind: 认证类型
|
|
155
|
+
value: 认证值
|
|
156
|
+
source: 认证来源
|
|
157
|
+
state: 状态(默认为 "configured")
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
provider: str # 提供商
|
|
161
|
+
auth_kind: str # 认证类型(api_key、oauth 等)
|
|
162
|
+
value: str # 认证值
|
|
163
|
+
source: str # 来源描述
|
|
164
|
+
state: str = "configured" # 配置状态
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class EnvConfig(BaseModel):
|
|
168
|
+
"""环境/提供商组配置"""
|
|
169
|
+
api_format: str # "anthropic" / "openai"
|
|
170
|
+
base_url: str | None = None
|
|
171
|
+
api_key: str = ""
|
|
172
|
+
system_prompt: str | None = None
|
|
173
|
+
|
|
174
|
+
model_config = {"extra": "allow"} # 允许 model_N 动态字段
|
|
175
|
+
|
|
176
|
+
def get_model(self, model_key: str) -> str | None:
|
|
177
|
+
"""获取指定的模型名称,如 model_1, model_2"""
|
|
178
|
+
return getattr(self, model_key, None)
|
|
179
|
+
|
|
180
|
+
def list_models(self) -> dict[str, str]:
|
|
181
|
+
"""列出所有 model_N 字段"""
|
|
182
|
+
result = {}
|
|
183
|
+
extras = self.model_extra or {}
|
|
184
|
+
for key, value in extras.items():
|
|
185
|
+
if key.startswith("model_") and isinstance(value, str):
|
|
186
|
+
result[key] = value
|
|
187
|
+
return result
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class Settings(BaseModel):
|
|
191
|
+
"""IllusionCode 主设置模型(env_N 分组格式)"""
|
|
192
|
+
|
|
193
|
+
model_config = {"extra": "allow"} # 允许 env_N 动态字段
|
|
194
|
+
|
|
195
|
+
# 活跃模型引用(格式:env_N.model_N)
|
|
196
|
+
model: str = "env_1.model_1"
|
|
197
|
+
|
|
198
|
+
# 全局配置
|
|
199
|
+
context_window: int = 200_000
|
|
200
|
+
system_prompt: str | None = None
|
|
201
|
+
|
|
202
|
+
# 保留的非模型字段
|
|
203
|
+
max_tokens: int = 16384
|
|
204
|
+
max_turns: int = 200
|
|
205
|
+
permission: PermissionSettings = Field(default_factory=PermissionSettings)
|
|
206
|
+
hooks: dict[str, list[HookDefinition]] = Field(default_factory=dict)
|
|
207
|
+
memory: MemorySettings = Field(default_factory=MemorySettings)
|
|
208
|
+
sandbox: SandboxSettings = Field(default_factory=SandboxSettings)
|
|
209
|
+
enabled_plugins: dict[str, bool] = Field(default_factory=dict)
|
|
210
|
+
mcp_servers: dict[str, McpServerConfig] = Field(default_factory=dict)
|
|
211
|
+
ui_language: str = "zh-CN"
|
|
212
|
+
output_style: str = "default"
|
|
213
|
+
show_thinking: bool = True
|
|
214
|
+
fast_mode: bool = False
|
|
215
|
+
effort: str = "medium"
|
|
216
|
+
passes: int = 1
|
|
217
|
+
verbose: bool = False
|
|
218
|
+
|
|
219
|
+
# --- env_N 配置辅助方法 ---
|
|
220
|
+
|
|
221
|
+
def get_env(self, env_key: str) -> EnvConfig | None:
|
|
222
|
+
"""获取指定的环境配置"""
|
|
223
|
+
value = getattr(self, env_key, None)
|
|
224
|
+
if isinstance(value, dict):
|
|
225
|
+
return EnvConfig.model_validate(value)
|
|
226
|
+
return value if isinstance(value, EnvConfig) else None
|
|
227
|
+
|
|
228
|
+
def list_envs(self) -> dict[str, EnvConfig]:
|
|
229
|
+
"""列出所有 env_N 配置"""
|
|
230
|
+
result = {}
|
|
231
|
+
extras = self.model_extra or {}
|
|
232
|
+
for key, value in extras.items():
|
|
233
|
+
if key.startswith("env_"):
|
|
234
|
+
if isinstance(value, EnvConfig):
|
|
235
|
+
result[key] = value
|
|
236
|
+
elif isinstance(value, dict):
|
|
237
|
+
result[key] = EnvConfig.model_validate(value)
|
|
238
|
+
return result
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def _active_env_key(self) -> str:
|
|
242
|
+
"""解析 model 字段,返回 env key"""
|
|
243
|
+
if "." in self.model:
|
|
244
|
+
return self.model.split(".", 1)[0]
|
|
245
|
+
return "env_1"
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def _active_model_key(self) -> str:
|
|
249
|
+
"""解析 model 字段,返回 model key"""
|
|
250
|
+
if "." in self.model:
|
|
251
|
+
return self.model.split(".", 1)[1]
|
|
252
|
+
return "model_1"
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def _active_env(self) -> EnvConfig:
|
|
256
|
+
"""返回当前活跃的环境配置"""
|
|
257
|
+
env = self.get_env(self._active_env_key)
|
|
258
|
+
if env is None:
|
|
259
|
+
envs = self.list_envs()
|
|
260
|
+
if envs:
|
|
261
|
+
return next(iter(envs.values()))
|
|
262
|
+
return EnvConfig(api_format="anthropic")
|
|
263
|
+
return env
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def _active_model_name(self) -> str:
|
|
267
|
+
"""返回当前活跃的模型名称"""
|
|
268
|
+
env = self._active_env
|
|
269
|
+
model_name = env.get_model(self._active_model_key)
|
|
270
|
+
if model_name is None:
|
|
271
|
+
models = env.list_models()
|
|
272
|
+
if models:
|
|
273
|
+
return next(iter(models.values()))
|
|
274
|
+
return "claude-sonnet-4-6"
|
|
275
|
+
return model_name
|
|
276
|
+
|
|
277
|
+
# --- 兼容性属性 ---
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def active_model_name(self) -> str:
|
|
281
|
+
"""兼容性属性:当前活跃模型名称"""
|
|
282
|
+
return self._active_model_name
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def api_key(self) -> str:
|
|
286
|
+
"""兼容性属性:当前活跃环境的 API 密钥"""
|
|
287
|
+
return self._active_env.api_key
|
|
288
|
+
|
|
289
|
+
@property
|
|
290
|
+
def base_url(self) -> str | None:
|
|
291
|
+
"""兼容性属性:当前活跃环境的 base URL"""
|
|
292
|
+
return self._active_env.base_url
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
def provider(self) -> str:
|
|
296
|
+
"""兼容性属性:根据 api_format 推断提供商"""
|
|
297
|
+
fmt = self._active_env.api_format
|
|
298
|
+
if fmt == "anthropic":
|
|
299
|
+
return "anthropic"
|
|
300
|
+
return "openai"
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def api_format(self) -> str:
|
|
304
|
+
"""兼容性属性:当前活跃环境的 API 格式"""
|
|
305
|
+
return self._active_env.api_format
|
|
306
|
+
|
|
307
|
+
def resolve_api_key(self) -> str:
|
|
308
|
+
"""解析 API 密钥
|
|
309
|
+
|
|
310
|
+
优先级:EnvConfig.api_key > 环境变量 > credentials.json(env_N) > 旧格式 credentials.json(provider) > 空
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
str: API 密钥字符串
|
|
314
|
+
|
|
315
|
+
Raises:
|
|
316
|
+
ValueError: 未找到密钥时抛出
|
|
317
|
+
"""
|
|
318
|
+
env = self._active_env
|
|
319
|
+
|
|
320
|
+
# 检查 EnvConfig 中的 api_key
|
|
321
|
+
if env.api_key:
|
|
322
|
+
return env.api_key
|
|
323
|
+
|
|
324
|
+
# 检查环境变量
|
|
325
|
+
if env.api_format == "openai":
|
|
326
|
+
env_var_key = os.environ.get("OPENAI_API_KEY", "")
|
|
327
|
+
if env_var_key:
|
|
328
|
+
return env_var_key
|
|
329
|
+
env_var_key = os.environ.get("ANTHROPIC_API_KEY", "")
|
|
330
|
+
if env_var_key:
|
|
331
|
+
return env_var_key
|
|
332
|
+
|
|
333
|
+
# 从 credentials.json 的 env_N 读取
|
|
334
|
+
from illusion.auth.storage import load_env_credential
|
|
335
|
+
env_cred = load_env_credential(self._active_env_key, "api_key")
|
|
336
|
+
if env_cred:
|
|
337
|
+
return env_cred
|
|
338
|
+
|
|
339
|
+
# 兼容旧格式:按 provider 名读取
|
|
340
|
+
from illusion.auth.storage import load_credential
|
|
341
|
+
provider = self.provider
|
|
342
|
+
old_cred = load_credential(provider, "api_key")
|
|
343
|
+
if old_cred:
|
|
344
|
+
return old_cred
|
|
345
|
+
|
|
346
|
+
from illusion.config.i18n import t as _t
|
|
347
|
+
raise ValueError(_t("no_api_key"))
|
|
348
|
+
|
|
349
|
+
def resolve_auth(self) -> ResolvedAuth:
|
|
350
|
+
"""解析当前活跃环境的认证信息
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
ResolvedAuth: 解析后的认证对象
|
|
354
|
+
|
|
355
|
+
Raises:
|
|
356
|
+
ValueError: 认证配置错误时抛出
|
|
357
|
+
"""
|
|
358
|
+
env = self._active_env
|
|
359
|
+
provider = self.provider
|
|
360
|
+
api_format = env.api_format
|
|
361
|
+
|
|
362
|
+
# 检查 EnvConfig 中的 api_key
|
|
363
|
+
if env.api_key:
|
|
364
|
+
return ResolvedAuth(
|
|
365
|
+
provider=provider,
|
|
366
|
+
auth_kind="api_key",
|
|
367
|
+
value=env.api_key,
|
|
368
|
+
source="env_config",
|
|
369
|
+
state="configured",
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# 检查环境变量
|
|
373
|
+
if api_format == "openai":
|
|
374
|
+
openai_val = os.environ.get("OPENAI_API_KEY", "")
|
|
375
|
+
if openai_val:
|
|
376
|
+
return ResolvedAuth(
|
|
377
|
+
provider=provider,
|
|
378
|
+
auth_kind="api_key",
|
|
379
|
+
value=openai_val,
|
|
380
|
+
source="env:OPENAI_API_KEY",
|
|
381
|
+
state="configured",
|
|
382
|
+
)
|
|
383
|
+
anthropic_val = os.environ.get("ANTHROPIC_API_KEY", "")
|
|
384
|
+
if anthropic_val:
|
|
385
|
+
return ResolvedAuth(
|
|
386
|
+
provider=provider,
|
|
387
|
+
auth_kind="api_key",
|
|
388
|
+
value=anthropic_val,
|
|
389
|
+
source="env:ANTHROPIC_API_KEY",
|
|
390
|
+
state="configured",
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# 从 credentials.json 的 env_N 读取
|
|
394
|
+
from illusion.auth.storage import load_env_credential
|
|
395
|
+
env_cred = load_env_credential(self._active_env_key, "api_key")
|
|
396
|
+
if env_cred:
|
|
397
|
+
return ResolvedAuth(
|
|
398
|
+
provider=provider,
|
|
399
|
+
auth_kind="api_key",
|
|
400
|
+
value=env_cred,
|
|
401
|
+
source=f"file:{self._active_env_key}",
|
|
402
|
+
state="configured",
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# 兼容旧格式:按 provider 名读取
|
|
406
|
+
from illusion.auth.storage import load_credential
|
|
407
|
+
old_cred = load_credential(provider, "api_key")
|
|
408
|
+
if old_cred:
|
|
409
|
+
return ResolvedAuth(
|
|
410
|
+
provider=provider,
|
|
411
|
+
auth_kind="api_key",
|
|
412
|
+
value=old_cred,
|
|
413
|
+
source=f"file:{provider}",
|
|
414
|
+
state="configured",
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
from illusion.config.i18n import t as _t
|
|
418
|
+
raise ValueError(_t("no_auth"))
|
|
419
|
+
|
|
420
|
+
def merge_cli_overrides(self, **overrides: Any) -> Settings:
|
|
421
|
+
"""返回应用了 CLI 覆盖的新 Settings(仅非 None 值)
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
**overrides: 要覆盖的字段
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Settings: 应用覆盖后的新实例
|
|
428
|
+
"""
|
|
429
|
+
updates = {k: v for k, v in overrides.items() if v is not None}
|
|
430
|
+
if not updates:
|
|
431
|
+
return self
|
|
432
|
+
return self.model_copy(update=updates)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _apply_env_overrides(settings: Settings) -> Settings:
|
|
436
|
+
"""在加载的设置上应用环境变量覆盖到活跃的 EnvConfig
|
|
437
|
+
|
|
438
|
+
直接修改活跃 env 的 api_key、model、base_url 等字段,
|
|
439
|
+
而不是设置 Settings 的属性(属性会 shadow extras)。
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
settings: 原始设置
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
Settings: 应用环境变量覆盖后的设置
|
|
446
|
+
"""
|
|
447
|
+
env = settings._active_env
|
|
448
|
+
env_modified = False
|
|
449
|
+
|
|
450
|
+
model = os.environ.get("ANTHROPIC_MODEL")
|
|
451
|
+
if model:
|
|
452
|
+
env.model = model
|
|
453
|
+
env_modified = True
|
|
454
|
+
|
|
455
|
+
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
|
456
|
+
if api_key:
|
|
457
|
+
env.api_key = api_key
|
|
458
|
+
env_modified = True
|
|
459
|
+
|
|
460
|
+
base_url = os.environ.get("ANTHROPIC_BASE_URL")
|
|
461
|
+
if base_url:
|
|
462
|
+
env.base_url = base_url
|
|
463
|
+
env_modified = True
|
|
464
|
+
|
|
465
|
+
# 非模型相关的全局字段覆盖仍使用 model_copy
|
|
466
|
+
updates: dict[str, Any] = {}
|
|
467
|
+
|
|
468
|
+
max_tokens = os.environ.get("ILLUSION_MAX_TOKENS") or os.environ.get("illusion_MAX_TOKENS")
|
|
469
|
+
if max_tokens:
|
|
470
|
+
updates["max_tokens"] = int(max_tokens)
|
|
471
|
+
|
|
472
|
+
max_turns = os.environ.get("ILLUSION_MAX_TURNS") or os.environ.get("illusion_MAX_TURNS")
|
|
473
|
+
if max_turns:
|
|
474
|
+
updates["max_turns"] = int(max_turns)
|
|
475
|
+
|
|
476
|
+
effort = os.environ.get("ILLUSION_EFFORT") or os.environ.get("illusion_EFFORT")
|
|
477
|
+
if effort:
|
|
478
|
+
updates["effort"] = effort
|
|
479
|
+
|
|
480
|
+
sandbox_enabled = os.environ.get("ILLUSION_SANDBOX_ENABLED") or os.environ.get("illusion_SANDBOX_ENABLED")
|
|
481
|
+
sandbox_fail = os.environ.get("ILLUSION_SANDBOX_FAIL_IF_UNAVAILABLE") or os.environ.get("illusion_SANDBOX_FAIL_IF_UNAVAILABLE")
|
|
482
|
+
sandbox_updates: dict[str, Any] = {}
|
|
483
|
+
if sandbox_enabled is not None:
|
|
484
|
+
sandbox_updates["enabled"] = _parse_bool_env(sandbox_enabled)
|
|
485
|
+
if sandbox_fail is not None:
|
|
486
|
+
sandbox_updates["fail_if_unavailable"] = _parse_bool_env(sandbox_fail)
|
|
487
|
+
if sandbox_updates:
|
|
488
|
+
updates["sandbox"] = settings.sandbox.model_copy(update=sandbox_updates)
|
|
489
|
+
|
|
490
|
+
# 将修改后的 env 写回 settings 的 extras
|
|
491
|
+
if env_modified:
|
|
492
|
+
env_key = settings._active_env_key
|
|
493
|
+
updates[env_key] = env
|
|
494
|
+
|
|
495
|
+
if not updates:
|
|
496
|
+
return settings
|
|
497
|
+
return settings.model_copy(update=updates)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def _parse_bool_env(value: str) -> bool:
|
|
501
|
+
"""解析布尔环境变量
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
value: 环境变量值字符串
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
bool: 解析后的布尔值
|
|
508
|
+
"""
|
|
509
|
+
return value.strip().lower() in {"1", "true", "yes", "on"}
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def load_settings(config_path: Path | None = None) -> Settings:
|
|
513
|
+
"""从配置文件加载设置
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
config_path: settings.json 的路径。如果为 None,使用默认位置。
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
Settings: 应用环境变量覆盖后的 Settings 实例
|
|
520
|
+
"""
|
|
521
|
+
if config_path is None:
|
|
522
|
+
from illusion.config.paths import get_config_file_path
|
|
523
|
+
|
|
524
|
+
config_path = get_config_file_path()
|
|
525
|
+
|
|
526
|
+
if config_path.exists():
|
|
527
|
+
raw = json.loads(config_path.read_text(encoding="utf-8"))
|
|
528
|
+
# 兼容 mcpServers(camelCase)键,映射到 mcp_servers(snake_case)
|
|
529
|
+
if "mcpServers" in raw and "mcp_servers" not in raw:
|
|
530
|
+
raw["mcp_servers"] = raw.pop("mcpServers")
|
|
531
|
+
settings = Settings.model_validate(raw)
|
|
532
|
+
return _apply_env_overrides(settings)
|
|
533
|
+
|
|
534
|
+
return _apply_env_overrides(Settings())
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def save_settings(settings: Settings, config_path: Path | None = None) -> None:
|
|
538
|
+
"""将设置持久化到配置文件
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
settings: 要保存的 Settings 实例
|
|
542
|
+
config_path: 写入路径。如果为 None,使用默认位置
|
|
543
|
+
"""
|
|
544
|
+
if config_path is None:
|
|
545
|
+
from illusion.config.paths import get_config_file_path
|
|
546
|
+
|
|
547
|
+
config_path = get_config_file_path()
|
|
548
|
+
|
|
549
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
550
|
+
|
|
551
|
+
# 序列化并重排字段,env_N 置顶
|
|
552
|
+
data = settings.model_dump()
|
|
553
|
+
ordered: dict[str, object] = {}
|
|
554
|
+
for key in sorted(data):
|
|
555
|
+
if key.startswith("env_"):
|
|
556
|
+
ordered[key] = data[key]
|
|
557
|
+
for key, value in data.items():
|
|
558
|
+
if not key.startswith("env_"):
|
|
559
|
+
ordered[key] = value
|
|
560
|
+
|
|
561
|
+
config_path.write_text(
|
|
562
|
+
json.dumps(ordered, indent=2, ensure_ascii=False) + "\n",
|
|
563
|
+
encoding="utf-8",
|
|
564
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
协调器模块
|
|
3
|
+
==========
|
|
4
|
+
|
|
5
|
+
本模块提供代理定义和任务通知功能。
|
|
6
|
+
|
|
7
|
+
主要组件:
|
|
8
|
+
- AgentDefinition: 代理定义
|
|
9
|
+
- TaskNotification: 任务通知
|
|
10
|
+
- get_builtin_agent_definitions: 获取内置代理定义
|
|
11
|
+
- get_agent_definition: 获取指定代理定义
|
|
12
|
+
- get_all_agent_definitions: 获取所有代理定义
|
|
13
|
+
- format_task_notification: 序列化任务通知
|
|
14
|
+
- parse_task_notification: 解析任务通知
|
|
15
|
+
|
|
16
|
+
使用示例:
|
|
17
|
+
>>> from illusion.coordinator import AgentDefinition, get_agent_definition
|
|
18
|
+
>>> from illusion.coordinator import TaskNotification, format_task_notification
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from illusion.coordinator.agent_definitions import (
|
|
22
|
+
AgentDefinition,
|
|
23
|
+
get_agent_definition,
|
|
24
|
+
get_all_agent_definitions,
|
|
25
|
+
get_builtin_agent_definitions,
|
|
26
|
+
)
|
|
27
|
+
from illusion.coordinator.coordinator_mode import (
|
|
28
|
+
TaskNotification,
|
|
29
|
+
format_task_notification,
|
|
30
|
+
parse_task_notification,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"AgentDefinition",
|
|
35
|
+
"TaskNotification",
|
|
36
|
+
"format_task_notification",
|
|
37
|
+
"get_agent_definition",
|
|
38
|
+
"get_all_agent_definitions",
|
|
39
|
+
"get_builtin_agent_definitions",
|
|
40
|
+
"parse_task_notification",
|
|
41
|
+
]
|