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
illusion/auth/manager.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
统一认证管理器模块
|
|
3
|
+
==================
|
|
4
|
+
|
|
5
|
+
本模块为 IllusionCode 提供统一的认证状态管理功能。
|
|
6
|
+
|
|
7
|
+
主要功能:
|
|
8
|
+
- 管理环境(env_N)认证状态
|
|
9
|
+
- 切换和配置环境
|
|
10
|
+
- 存储和加载凭据(按 env_N 分组)
|
|
11
|
+
|
|
12
|
+
类说明:
|
|
13
|
+
- AuthManager: 认证管理器类,负责所有认证相关的操作
|
|
14
|
+
|
|
15
|
+
使用示例:
|
|
16
|
+
>>> from illusion.auth import AuthManager
|
|
17
|
+
>>> manager = AuthManager()
|
|
18
|
+
>>> status = manager.get_env_credential_statuses()
|
|
19
|
+
>>> print(status)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from illusion.auth.storage import (
|
|
28
|
+
clear_env_credentials,
|
|
29
|
+
load_env_credential,
|
|
30
|
+
store_env_credential,
|
|
31
|
+
)
|
|
32
|
+
from illusion.config.i18n import t as _t
|
|
33
|
+
|
|
34
|
+
log = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AuthManager:
|
|
38
|
+
"""认证管理器
|
|
39
|
+
|
|
40
|
+
通过 :mod:`illusion.auth.storage` 读写凭据,
|
|
41
|
+
并通过设置跟踪当前活动的环境。
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, settings: Any | None = None) -> None:
|
|
45
|
+
self._settings = settings
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def settings(self) -> Any:
|
|
49
|
+
"""获取设置对象(延迟加载)"""
|
|
50
|
+
if self._settings is None:
|
|
51
|
+
from illusion.config import load_settings
|
|
52
|
+
self._settings = load_settings()
|
|
53
|
+
return self._settings
|
|
54
|
+
|
|
55
|
+
def get_active_env_key(self) -> str:
|
|
56
|
+
"""获取当前活动的环境键名(如 "env_1")"""
|
|
57
|
+
return self.settings._active_env_key
|
|
58
|
+
|
|
59
|
+
def list_envs(self) -> dict[str, Any]:
|
|
60
|
+
"""获取所有环境配置"""
|
|
61
|
+
return self.settings.list_envs()
|
|
62
|
+
|
|
63
|
+
def get_env_credential_statuses(self) -> dict[str, Any]:
|
|
64
|
+
"""获取所有环境的凭据状态
|
|
65
|
+
|
|
66
|
+
返回以 env_key 为键的字典::
|
|
67
|
+
|
|
68
|
+
{
|
|
69
|
+
"env_1": {
|
|
70
|
+
"api_format": "anthropic",
|
|
71
|
+
"base_url": "...",
|
|
72
|
+
"model": "claude-sonnet-4-6",
|
|
73
|
+
"has_credential": True,
|
|
74
|
+
"active": True,
|
|
75
|
+
},
|
|
76
|
+
...
|
|
77
|
+
}
|
|
78
|
+
"""
|
|
79
|
+
active_env_key = self.get_active_env_key()
|
|
80
|
+
envs = self.list_envs()
|
|
81
|
+
result: dict[str, Any] = {}
|
|
82
|
+
|
|
83
|
+
for env_key, env in envs.items():
|
|
84
|
+
models = env.list_models()
|
|
85
|
+
model_name = next(iter(models.values())) if models else "(无模型)"
|
|
86
|
+
has_cred = bool(load_env_credential(env_key, "api_key")) or bool(env.api_key)
|
|
87
|
+
result[env_key] = {
|
|
88
|
+
"api_format": env.api_format,
|
|
89
|
+
"base_url": env.base_url or "",
|
|
90
|
+
"model": model_name,
|
|
91
|
+
"has_credential": has_cred,
|
|
92
|
+
"active": env_key == active_env_key,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
def save_settings(self) -> None:
|
|
98
|
+
"""保存内存中的设置到持久化存储"""
|
|
99
|
+
from illusion.config import save_settings
|
|
100
|
+
save_settings(self.settings)
|
|
101
|
+
|
|
102
|
+
def use_env(self, env_key: str) -> None:
|
|
103
|
+
"""切换到指定的环境
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
env_key: 环境键名(如 "env_1")
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
ValueError: 环境不存在
|
|
110
|
+
"""
|
|
111
|
+
env = self.settings.get_env(env_key)
|
|
112
|
+
if env is None:
|
|
113
|
+
raise ValueError(_t("unknown_env", env_key=env_key))
|
|
114
|
+
models = env.list_models()
|
|
115
|
+
if models:
|
|
116
|
+
model_key = next(iter(models.keys()))
|
|
117
|
+
self.settings.model = f"{env_key}.{model_key}"
|
|
118
|
+
else:
|
|
119
|
+
self.settings.model = f"{env_key}.model_1"
|
|
120
|
+
self._settings = self.settings
|
|
121
|
+
self.save_settings()
|
|
122
|
+
log.info("已切换到环境 %s", env_key)
|
|
123
|
+
|
|
124
|
+
def update_env(
|
|
125
|
+
self,
|
|
126
|
+
env_key: str,
|
|
127
|
+
*,
|
|
128
|
+
api_format: str | None = None,
|
|
129
|
+
base_url: str | None = None,
|
|
130
|
+
api_key: str | None = None,
|
|
131
|
+
) -> None:
|
|
132
|
+
"""更新环境配置
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
env_key: 环境键名
|
|
136
|
+
api_format: API 格式
|
|
137
|
+
base_url: 基础 URL
|
|
138
|
+
api_key: API 密钥
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
ValueError: 环境不存在
|
|
142
|
+
"""
|
|
143
|
+
env = self.settings.get_env(env_key)
|
|
144
|
+
if env is None:
|
|
145
|
+
raise ValueError(_t("unknown_env", env_key=env_key))
|
|
146
|
+
updates: dict[str, Any] = {}
|
|
147
|
+
if api_format is not None:
|
|
148
|
+
updates["api_format"] = api_format
|
|
149
|
+
if base_url is not None:
|
|
150
|
+
updates["base_url"] = base_url
|
|
151
|
+
if api_key is not None:
|
|
152
|
+
updates["api_key"] = api_key
|
|
153
|
+
if updates:
|
|
154
|
+
updated_env = env.model_copy(update=updates)
|
|
155
|
+
setattr(self.settings, env_key, updated_env)
|
|
156
|
+
self._settings = self.settings
|
|
157
|
+
self.save_settings()
|
|
158
|
+
|
|
159
|
+
def remove_env(self, env_key: str) -> None:
|
|
160
|
+
"""移除环境配置
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
env_key: 环境键名
|
|
164
|
+
|
|
165
|
+
Raises:
|
|
166
|
+
ValueError: 环境不存在或正在使用
|
|
167
|
+
"""
|
|
168
|
+
if env_key == self.get_active_env_key():
|
|
169
|
+
raise ValueError(_t("cannot_remove_active_env"))
|
|
170
|
+
envs = self.settings.list_envs()
|
|
171
|
+
if env_key not in envs:
|
|
172
|
+
raise ValueError(_t("unknown_env", env_key=env_key))
|
|
173
|
+
extras = dict(self.settings.model_extra or {})
|
|
174
|
+
if env_key in extras:
|
|
175
|
+
del extras[env_key]
|
|
176
|
+
self._settings = self.settings.model_copy(update=extras)
|
|
177
|
+
self.save_settings()
|
|
178
|
+
|
|
179
|
+
def store_env_api_key(self, env_key: str, api_key: str) -> None:
|
|
180
|
+
"""存储环境的 API 密钥到 credentials.json
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
env_key: 环境键名(如 "env_1")
|
|
184
|
+
api_key: API 密钥
|
|
185
|
+
"""
|
|
186
|
+
store_env_credential(env_key, "api_key", api_key)
|
|
187
|
+
|
|
188
|
+
def clear_env_api_key(self, env_key: str) -> None:
|
|
189
|
+
"""删除环境的 API 密钥
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
env_key: 环境键名(如 "env_1")
|
|
193
|
+
"""
|
|
194
|
+
clear_env_credentials(env_key)
|
|
195
|
+
|
|
196
|
+
def store_credential(self, provider: str, key: str, value: str) -> None:
|
|
197
|
+
"""存储凭据(兼容旧接口,按 provider 存储)
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
provider: 提供商名称
|
|
201
|
+
key: 键名
|
|
202
|
+
value: 凭据值
|
|
203
|
+
"""
|
|
204
|
+
from illusion.auth.storage import store_credential
|
|
205
|
+
store_credential(provider, key, value)
|
|
206
|
+
|
|
207
|
+
def clear_credential(self, provider: str) -> None:
|
|
208
|
+
"""删除凭据(兼容旧接口,按 provider 清除)
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
provider: 提供商名称
|
|
212
|
+
"""
|
|
213
|
+
from illusion.auth.storage import clear_provider_credentials
|
|
214
|
+
clear_provider_credentials(provider)
|
illusion/auth/storage.py
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""
|
|
2
|
+
安全凭据存储模块
|
|
3
|
+
================
|
|
4
|
+
|
|
5
|
+
本模块为 IllusionCode 提供安全的凭据存储功能。
|
|
6
|
+
|
|
7
|
+
默认后端:~/.illusion/credentials.json,权限 600
|
|
8
|
+
可选后端:系统 keyring(如果安装了 keyring 包)
|
|
9
|
+
|
|
10
|
+
函数说明:
|
|
11
|
+
- store_credential: 存储凭据
|
|
12
|
+
- load_credential: 加载凭据
|
|
13
|
+
- clear_provider_credentials: 清除提供商凭据
|
|
14
|
+
- store_external_binding/load_external_binding: 外部绑定存储/加载
|
|
15
|
+
- encrypt/decrypt: 轻量级混淆加密
|
|
16
|
+
|
|
17
|
+
使用示例:
|
|
18
|
+
>>> from illusion.auth.storage import store_credential, load_credential
|
|
19
|
+
>>> store_credential("anthropic", "api_key", "sk-...")
|
|
20
|
+
>>> key = load_credential("anthropic", "api_key")
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import logging
|
|
27
|
+
from dataclasses import asdict, dataclass
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
from illusion.config.paths import get_config_dir
|
|
32
|
+
|
|
33
|
+
# 模块级日志记录器
|
|
34
|
+
log = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
# 常量定义
|
|
37
|
+
_CREDS_FILE_NAME = "credentials.json" # 凭据文件名
|
|
38
|
+
_KEYRING_SERVICE = "illusion" # keyring 服务名
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class ExternalAuthBinding:
|
|
43
|
+
"""指向外部 CLI 管理的凭据的指针
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
provider: 提供商名称
|
|
47
|
+
source_path: 源路径
|
|
48
|
+
source_kind: 源类型
|
|
49
|
+
managed_by: 管理程序
|
|
50
|
+
profile_label: 配置标签
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
provider: str
|
|
54
|
+
source_path: str
|
|
55
|
+
source_kind: str
|
|
56
|
+
managed_by: str
|
|
57
|
+
profile_label: str = ""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# 文件后端(始终可用)
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _creds_path() -> Path:
|
|
66
|
+
"""获取凭据文件路径"""
|
|
67
|
+
return get_config_dir() / _CREDS_FILE_NAME
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _load_creds_file() -> dict[str, Any]:
|
|
71
|
+
"""加载凭据文件
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
dict[str, Any]: 凭据数据字典
|
|
75
|
+
"""
|
|
76
|
+
path = _creds_path()
|
|
77
|
+
if not path.exists():
|
|
78
|
+
return {}
|
|
79
|
+
try:
|
|
80
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
81
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
82
|
+
log.warning("Failed to read credentials file: %s", exc)
|
|
83
|
+
return {}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _save_creds_file(data: dict[str, Any]) -> None:
|
|
87
|
+
"""保存凭据文件
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
data: 凭据数据字典
|
|
91
|
+
"""
|
|
92
|
+
path = _creds_path()
|
|
93
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
95
|
+
try:
|
|
96
|
+
path.chmod(0o600)
|
|
97
|
+
except OSError:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
# Keyring 后端(可选)
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _keyring_available() -> bool:
|
|
107
|
+
"""检查 keyring 是否可用
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
bool: keyring 是否可用
|
|
111
|
+
"""
|
|
112
|
+
try:
|
|
113
|
+
import keyring # noqa: F401
|
|
114
|
+
|
|
115
|
+
return True
|
|
116
|
+
except ImportError:
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _keyring_key(provider: str, key: str) -> str:
|
|
121
|
+
"""生成 keyring 键
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
provider: 提供商名称
|
|
125
|
+
key: 键名
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
str: 组合键名
|
|
129
|
+
"""
|
|
130
|
+
return f"{provider}:{key}"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
# 公共 API
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def store_credential(provider: str, key: str, value: str, *, use_keyring: bool | None = None) -> None:
|
|
139
|
+
"""持久化 provider 下的凭据
|
|
140
|
+
|
|
141
|
+
如果未设置 use_keyring,在可用时使用 keyring。
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
provider: 提供商名称
|
|
145
|
+
key: 键名
|
|
146
|
+
value: 凭据值
|
|
147
|
+
use_keyring: 是否使用 keyring(可选)
|
|
148
|
+
"""
|
|
149
|
+
if use_keyring is None:
|
|
150
|
+
use_keyring = _keyring_available()
|
|
151
|
+
|
|
152
|
+
if use_keyring:
|
|
153
|
+
try:
|
|
154
|
+
import keyring
|
|
155
|
+
|
|
156
|
+
keyring.set_password(_KEYRING_SERVICE, _keyring_key(provider, key), value)
|
|
157
|
+
log.debug("Stored %s/%s in keyring", provider, key)
|
|
158
|
+
return
|
|
159
|
+
except Exception as exc:
|
|
160
|
+
log.warning("Keyring store failed, falling back to file: %s", exc)
|
|
161
|
+
|
|
162
|
+
data = _load_creds_file()
|
|
163
|
+
data.setdefault(provider, {})[key] = value
|
|
164
|
+
_save_creds_file(data)
|
|
165
|
+
log.debug("Stored %s/%s in credentials file", provider, key)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def load_credential(provider: str, key: str, *, use_keyring: bool | None = None) -> str | None:
|
|
169
|
+
"""返回存储的凭据,未找到返回 None
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
provider: 提供商名称
|
|
173
|
+
key: 键名
|
|
174
|
+
use_keyring: 是否使用 keyring(可选)
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
str | None: 凭据值或 None
|
|
178
|
+
"""
|
|
179
|
+
if use_keyring is None:
|
|
180
|
+
use_keyring = _keyring_available()
|
|
181
|
+
|
|
182
|
+
if use_keyring:
|
|
183
|
+
try:
|
|
184
|
+
import keyring
|
|
185
|
+
|
|
186
|
+
value = keyring.get_password(_KEYRING_SERVICE, _keyring_key(provider, key))
|
|
187
|
+
if value is not None:
|
|
188
|
+
return value
|
|
189
|
+
except Exception as exc:
|
|
190
|
+
log.warning("Keyring load failed, falling back to file: %s", exc)
|
|
191
|
+
|
|
192
|
+
data = _load_creds_file()
|
|
193
|
+
return data.get(provider, {}).get(key)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def clear_provider_credentials(provider: str, *, use_keyring: bool | None = None) -> None:
|
|
197
|
+
"""删除 provider 的所有存储凭据
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
provider: 提供商名称
|
|
201
|
+
use_keyring: 是否使用 keyring(可选)
|
|
202
|
+
"""
|
|
203
|
+
if use_keyring is None:
|
|
204
|
+
use_keyring = _keyring_available()
|
|
205
|
+
|
|
206
|
+
if use_keyring:
|
|
207
|
+
try:
|
|
208
|
+
import keyring
|
|
209
|
+
from keyring.errors import PasswordDeleteError
|
|
210
|
+
|
|
211
|
+
# 尝试常见键;静默忽略缺失的
|
|
212
|
+
for key in ("api_key", "token", "github_token"):
|
|
213
|
+
try:
|
|
214
|
+
keyring.delete_password(_KEYRING_SERVICE, _keyring_key(provider, key))
|
|
215
|
+
except (PasswordDeleteError, Exception):
|
|
216
|
+
pass
|
|
217
|
+
except ImportError:
|
|
218
|
+
pass
|
|
219
|
+
|
|
220
|
+
data = _load_creds_file()
|
|
221
|
+
if provider in data:
|
|
222
|
+
del data[provider]
|
|
223
|
+
_save_creds_file(data)
|
|
224
|
+
log.debug("Cleared credentials for provider: %s", provider)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def list_stored_providers() -> list[str]:
|
|
228
|
+
"""返回文件中存储了凭据的提供商列表
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
list[str]: 提供商名称列表
|
|
232
|
+
"""
|
|
233
|
+
return list(_load_creds_file().keys())
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# ---------------------------------------------------------------------------
|
|
237
|
+
# env_N 凭据存储(按环境分组)
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def store_env_credential(env_key: str, key: str, value: str) -> None:
|
|
242
|
+
"""按 env_N 分组存储凭据
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
env_key: 环境键名(如 "env_1")
|
|
246
|
+
key: 键名(如 "api_key")
|
|
247
|
+
value: 凭据值
|
|
248
|
+
"""
|
|
249
|
+
data = _load_creds_file()
|
|
250
|
+
data.setdefault(env_key, {})[key] = value
|
|
251
|
+
_save_creds_file(data)
|
|
252
|
+
log.debug("Stored %s/%s in credentials file (env)", env_key, key)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def load_env_credential(env_key: str, key: str) -> str | None:
|
|
256
|
+
"""按 env_N 读取凭据,未找到返回 None
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
env_key: 环境键名(如 "env_1")
|
|
260
|
+
key: 键名(如 "api_key")
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
str | None: 凭据值或 None
|
|
264
|
+
"""
|
|
265
|
+
data = _load_creds_file()
|
|
266
|
+
return data.get(env_key, {}).get(key)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def clear_env_credentials(env_key: str) -> None:
|
|
270
|
+
"""删除 env_N 的所有存储凭据
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
env_key: 环境键名(如 "env_1")
|
|
274
|
+
"""
|
|
275
|
+
data = _load_creds_file()
|
|
276
|
+
if env_key in data:
|
|
277
|
+
del data[env_key]
|
|
278
|
+
_save_creds_file(data)
|
|
279
|
+
log.debug("Cleared credentials for env: %s", env_key)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def store_external_binding(binding: ExternalAuthBinding) -> None:
|
|
283
|
+
"""持久化描述 provider 外部认证源的元数据
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
binding: 外部认证绑定
|
|
287
|
+
"""
|
|
288
|
+
data = _load_creds_file()
|
|
289
|
+
entry = data.setdefault(binding.provider, {})
|
|
290
|
+
entry["external_binding"] = asdict(binding)
|
|
291
|
+
_save_creds_file(data)
|
|
292
|
+
log.debug("Stored external auth binding for provider: %s", binding.provider)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def load_external_binding(provider: str) -> ExternalAuthBinding | None:
|
|
296
|
+
"""如果存在,加载 provider 的外部认证绑定元数据
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
provider: 提供商名称
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
ExternalAuthBinding | None: 外部绑定或 None
|
|
303
|
+
"""
|
|
304
|
+
entry = _load_creds_file().get(provider, {})
|
|
305
|
+
if not isinstance(entry, dict):
|
|
306
|
+
return None
|
|
307
|
+
raw = entry.get("external_binding")
|
|
308
|
+
if not isinstance(raw, dict):
|
|
309
|
+
return None
|
|
310
|
+
try:
|
|
311
|
+
return ExternalAuthBinding(
|
|
312
|
+
provider=str(raw["provider"]),
|
|
313
|
+
source_path=str(raw["source_path"]),
|
|
314
|
+
source_kind=str(raw["source_kind"]),
|
|
315
|
+
managed_by=str(raw["managed_by"]),
|
|
316
|
+
profile_label=str(raw.get("profile_label", "") or ""),
|
|
317
|
+
)
|
|
318
|
+
except KeyError:
|
|
319
|
+
log.warning("Ignoring malformed external auth binding for provider: %s", provider)
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# ---------------------------------------------------------------------------
|
|
324
|
+
# 加密/解密辅助函数(轻量级 XOR 混淆,非真正加密)
|
|
325
|
+
# ---------------------------------------------------------------------------
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _obfuscation_key() -> bytes:
|
|
329
|
+
"""返回从主目录路径派生的每用户混淆密钥
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
bytes: 32 字节混淆密钥
|
|
333
|
+
"""
|
|
334
|
+
seed = str(Path.home()).encode() + b"illusion-v1"
|
|
335
|
+
# 通过 SHA-256 简单重复密钥拉伸到 32 字节以保持确定性
|
|
336
|
+
import hashlib
|
|
337
|
+
|
|
338
|
+
return hashlib.sha256(seed).digest()
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def encrypt(plaintext: str) -> str:
|
|
342
|
+
"""轻量级混淆 plaintext(base64 编码 XOR)。非加密。
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
plaintext: 明文
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
str: 混淆后的字符串
|
|
349
|
+
"""
|
|
350
|
+
import base64
|
|
351
|
+
|
|
352
|
+
key = _obfuscation_key()
|
|
353
|
+
data = plaintext.encode("utf-8")
|
|
354
|
+
xored = bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
|
|
355
|
+
return base64.urlsafe_b64encode(xored).decode("ascii")
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def decrypt(ciphertext: str) -> str:
|
|
359
|
+
"""encrypt 的反向操作
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
ciphertext: 混淆的字符串
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
str: 明文
|
|
366
|
+
"""
|
|
367
|
+
import base64
|
|
368
|
+
|
|
369
|
+
key = _obfuscation_key()
|
|
370
|
+
data = base64.urlsafe_b64decode(ciphertext.encode("ascii"))
|
|
371
|
+
xored = bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
|
|
372
|
+
return xored.decode("utf-8")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
桥接模块
|
|
3
|
+
========
|
|
4
|
+
|
|
5
|
+
本模块提供 IllusionCode 桥接会话管理功能。
|
|
6
|
+
|
|
7
|
+
主要组件:
|
|
8
|
+
- BridgeSessionManager: 桥接会话管理器
|
|
9
|
+
- BridgeSessionRecord: 桥接会话记录
|
|
10
|
+
- BridgeConfig: 桥接配置
|
|
11
|
+
- SessionHandle: 会话句柄
|
|
12
|
+
- WorkData: 工作数据
|
|
13
|
+
- WorkSecret: 工作密钥
|
|
14
|
+
- get_bridge_manager: 获取桥接管理器
|
|
15
|
+
- spawn_session: 生成会话
|
|
16
|
+
|
|
17
|
+
使用示例:
|
|
18
|
+
>>> from illusion.bridge import BridgeSessionManager, spawn_session
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from illusion.bridge.manager import BridgeSessionManager, BridgeSessionRecord, get_bridge_manager
|
|
22
|
+
from illusion.bridge.session_runner import SessionHandle, spawn_session
|
|
23
|
+
from illusion.bridge.types import BridgeConfig, WorkData, WorkSecret
|
|
24
|
+
from illusion.bridge.work_secret import build_sdk_url, decode_work_secret, encode_work_secret
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"BridgeSessionManager",
|
|
28
|
+
"BridgeSessionRecord",
|
|
29
|
+
"BridgeConfig",
|
|
30
|
+
"SessionHandle",
|
|
31
|
+
"WorkData",
|
|
32
|
+
"WorkSecret",
|
|
33
|
+
"build_sdk_url",
|
|
34
|
+
"decode_work_secret",
|
|
35
|
+
"encode_work_secret",
|
|
36
|
+
"get_bridge_manager",
|
|
37
|
+
"spawn_session",
|
|
38
|
+
]
|