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,148 @@
1
+ """
2
+ 提供商/认证能力辅助模块
3
+ ======================
4
+
5
+ 本模块提供 API 提供商检测和认证状态查询功能。
6
+
7
+ 主要功能:
8
+ - 检测当前活动的 API 提供商
9
+ - 查询认证状态
10
+ - 提供提供商元数据
11
+
12
+ 类型说明:
13
+ - ProviderInfo: 提供商元数据数据类
14
+
15
+ 函数说明:
16
+ - detect_provider: 推断活动提供商
17
+ - auth_status: 返回认证状态字符串
18
+
19
+ 使用示例:
20
+ >>> from illusion.config.settings import load_settings
21
+ >>> from illusion.api.provider import detect_provider, auth_status
22
+ >>> settings = load_settings()
23
+ >>> provider_info = detect_provider(settings)
24
+ >>> print(f"当前提供商: {provider_info.name}")
25
+ >>> print(f"认证状态: {auth_status(settings)}")
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from dataclasses import dataclass
31
+
32
+ from illusion.api.registry import detect_provider_from_registry
33
+ from illusion.config.settings import Settings
34
+
35
+ # 提供商认证类型映射
36
+ _AUTH_KIND: dict[str, str] = {
37
+ "anthropic": "api_key",
38
+ "openai_compat": "api_key",
39
+ "openai_codex": "external_oauth",
40
+ "anthropic_claude": "external_oauth",
41
+ "copilot": "copilot_oauth",
42
+ }
43
+
44
+ # 语音模式支持原因映射
45
+ _VOICE_REASON: dict[str, str] = {
46
+ "anthropic": (
47
+ "voice mode shell exists, but live voice auth/streaming is not configured in this build"
48
+ ),
49
+ "openai_compat": "voice mode is not wired for OpenAI-compatible providers in this build",
50
+ "openai_codex": "voice mode is not supported for Codex subscription auth",
51
+ "anthropic_claude": "voice mode is not supported for Claude subscription auth",
52
+ "copilot": "voice mode is not supported for Copilot subscription auth",
53
+ }
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class ProviderInfo:
58
+ """提供商元数据数据类
59
+
60
+ 用于 UI 和诊断的已解析提供商信息。
61
+
62
+ Attributes:
63
+ name: 提供商名称
64
+ auth_kind: 认证类型(api_key、oauth_device、external_oauth)
65
+ voice_supported: 是否支持语音模式
66
+ voice_reason: 不支持语音模式的原因
67
+ """
68
+
69
+ name: str
70
+ auth_kind: str
71
+ voice_supported: bool
72
+ voice_reason: str
73
+
74
+
75
+ def detect_provider(settings: Settings) -> ProviderInfo:
76
+ """使用注册表推断活动提供商和大致能力集
77
+
78
+ Args:
79
+ settings: 应用设置对象
80
+
81
+ Returns:
82
+ ProviderInfo: 提供商元数据
83
+ """
84
+ # 从注册表检测
85
+ spec = detect_provider_from_registry(
86
+ model=settings.active_model_name,
87
+ api_key=settings.api_key or None,
88
+ base_url=settings.base_url,
89
+ )
90
+
91
+ if spec is not None:
92
+ backend = spec.backend_type
93
+ return ProviderInfo(
94
+ name=spec.name,
95
+ auth_kind=_AUTH_KIND.get(backend, "api_key"),
96
+ voice_supported=False,
97
+ voice_reason=_VOICE_REASON.get(backend, "voice mode is not supported for this provider"),
98
+ )
99
+
100
+ # 回退:使用 api_format 选择默认
101
+ if settings.api_format == "openai":
102
+ return ProviderInfo(
103
+ name="openai-compatible",
104
+ auth_kind="api_key",
105
+ voice_supported=False,
106
+ voice_reason=_VOICE_REASON["openai_compat"],
107
+ )
108
+ return ProviderInfo(
109
+ name="anthropic",
110
+ auth_kind="api_key",
111
+ voice_supported=False,
112
+ voice_reason=_VOICE_REASON["anthropic"],
113
+ )
114
+
115
+
116
+ def auth_status(settings: Settings) -> str:
117
+ """返回简洁的认证状态字符串
118
+
119
+ Args:
120
+ settings: 应用设置对象
121
+
122
+ Returns:
123
+ str: 认证状态描述
124
+ """
125
+ # 检测是否为 Copilot 提供商(认证存储在独立文件中)
126
+ provider_info = detect_provider(settings)
127
+ if provider_info.name == "copilot":
128
+ from illusion.auth.copilot import CopilotAuth
129
+ copilot = CopilotAuth()
130
+ if copilot.is_authenticated():
131
+ return "configured (copilot)"
132
+ return "missing"
133
+ if provider_info.name == "codex":
134
+ from illusion.auth.external import default_binding_for_provider, describe_external_binding
135
+ binding = default_binding_for_provider("openai_codex")
136
+ state = describe_external_binding(binding)
137
+ return "configured (codex)" if state.configured else "missing"
138
+
139
+ # 尝试解析认证
140
+ try:
141
+ resolved = settings.resolve_auth()
142
+ except ValueError:
143
+ return "missing"
144
+
145
+ # 解析认证源
146
+ if resolved.source.startswith("external:"):
147
+ return f"configured ({resolved.source.removeprefix('external:')})"
148
+ return "configured"
@@ -0,0 +1,479 @@
1
+ """
2
+ LLM 提供商注册表模块
3
+ ===================
4
+
5
+ 本模块作为 LLM 提供商元数据的单一事实来源。
6
+
7
+ 添加新提供商:
8
+ 1. 在下面的 PROVIDERS 中添加 ProviderSpec。
9
+ 完成。检测、显示和配置都由此派生。
10
+
11
+ 顺序很重要 - 它控制匹配优先级。网关和云提供商优先,
12
+ 标准提供商按关键字,本地/特殊提供商最后。
13
+
14
+ 类型说明:
15
+ - ProviderSpec: 提供商元数据数据类
16
+
17
+ 函数说明:
18
+ - find_by_name: 按名称查找提供商
19
+ - detect_provider_from_registry: 检测最佳匹配的 ProviderSpec
20
+
21
+ 使用示例:
22
+ >>> from illusion.api.registry import PROVIDERS, detect_provider_from_registry
23
+ >>> spec = detect_provider_from_registry("claude-3-sonnet", None, None)
24
+ >>> print(f"检测到的提供商: {spec.name}")
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from dataclasses import dataclass
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class ProviderSpec:
34
+ """LLM 提供商元数据
35
+
36
+ Attributes:
37
+ name: 规范名称,如 "dashscope"
38
+ keywords: 模型名称关键字元组,用于检测(小写)
39
+ env_key: 主要 API 密钥环境变量
40
+ display_name: 状态/诊断中显示的名称
41
+ backend_type: 后端类型("anthropic"、"openai_compat")
42
+ default_base_url: 该提供商的备用基础 URL
43
+ detect_by_key_prefix: 匹配 api_key 前缀,如 "sk-or-"
44
+ detect_by_base_keyword: 匹配 base_url 中的子字符串
45
+ is_gateway: 是否为网关(OpenRouter、AiHubMix 等)
46
+ is_local: 是否为本地部署(vLLM、Ollama)
47
+ is_oauth: 是否使用 OAuth 而非 API 密钥
48
+ supports_image: 是否支持图片输入
49
+ """
50
+ name: str
51
+ keywords: tuple[str, ...]
52
+ env_key: str
53
+ display_name: str
54
+ backend_type: str
55
+ default_base_url: str
56
+ detect_by_key_prefix: str
57
+ detect_by_base_keyword: str
58
+ is_gateway: bool
59
+ is_local: bool
60
+ is_oauth: bool
61
+ supports_image: bool = True
62
+
63
+ @property
64
+ def label(self) -> str:
65
+ """返回显示标签"""
66
+ return self.display_name or self.name.title()
67
+
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # PROVIDERS 注册表 — 顺序 = 检测优先级。
71
+ # ---------------------------------------------------------------------------
72
+
73
+ PROVIDERS: tuple[ProviderSpec, ...] = (
74
+ # === 网关(通过 api_key 前缀 / base_url 关键字检测) ============
75
+ # OpenRouter:全局网关,密钥以 "sk-or-" 开头
76
+ ProviderSpec(
77
+ name="openrouter",
78
+ keywords=("openrouter",),
79
+ env_key="OPENROUTER_API_KEY",
80
+ display_name="OpenRouter",
81
+ backend_type="openai_compat",
82
+ default_base_url="https://openrouter.ai/api/v1",
83
+ detect_by_key_prefix="sk-or-",
84
+ detect_by_base_keyword="openrouter",
85
+ is_gateway=True,
86
+ is_local=False,
87
+ is_oauth=False,
88
+ ),
89
+ # AiHubMix:OpenAI 兼容网关
90
+ ProviderSpec(
91
+ name="aihubmix",
92
+ keywords=("aihubmix",),
93
+ env_key="OPENAI_API_KEY",
94
+ display_name="AiHubMix",
95
+ backend_type="openai_compat",
96
+ default_base_url="https://aihubmix.com/v1",
97
+ detect_by_key_prefix="",
98
+ detect_by_base_keyword="aihubmix",
99
+ is_gateway=True,
100
+ is_local=False,
101
+ is_oauth=False,
102
+ ),
103
+ # SiliconFlow(硅基流动):OpenAI 兼容网关
104
+ ProviderSpec(
105
+ name="siliconflow",
106
+ keywords=("siliconflow",),
107
+ env_key="OPENAI_API_KEY",
108
+ display_name="SiliconFlow",
109
+ backend_type="openai_compat",
110
+ default_base_url="https://api.siliconflow.cn/v1",
111
+ detect_by_key_prefix="",
112
+ detect_by_base_keyword="siliconflow",
113
+ is_gateway=True,
114
+ is_local=False,
115
+ is_oauth=False,
116
+ ),
117
+ # VolcEngine(火山引擎 / Ark):OpenAI 兼容网关
118
+ ProviderSpec(
119
+ name="volcengine",
120
+ keywords=("volcengine", "volces", "ark"),
121
+ env_key="OPENAI_API_KEY",
122
+ display_name="VolcEngine",
123
+ backend_type="openai_compat",
124
+ default_base_url="https://ark.cn-beijing.volces.com/api/v3",
125
+ detect_by_key_prefix="",
126
+ detect_by_base_keyword="volces",
127
+ is_gateway=True,
128
+ is_local=False,
129
+ is_oauth=False,
130
+ ),
131
+ # === 订阅提供商(OAuth 认证) ============
132
+ # GitHub Copilot:GitHub OAuth 设备码认证 + Copilot token
133
+ ProviderSpec(
134
+ name="copilot",
135
+ keywords=("copilot",),
136
+ env_key="COPILOT_TOKEN",
137
+ display_name="GitHub Copilot",
138
+ backend_type="openai_compat",
139
+ default_base_url="https://api.githubcopilot.com",
140
+ detect_by_key_prefix="tid=",
141
+ detect_by_base_keyword="githubcopilot",
142
+ is_gateway=False,
143
+ is_local=False,
144
+ is_oauth=True,
145
+ ),
146
+ # OpenAI Codex:ChatGPT 订阅认证,使用 Codex Responses API
147
+ ProviderSpec(
148
+ name="codex",
149
+ keywords=("codex",),
150
+ env_key="CODEX_TOKEN",
151
+ display_name="OpenAI Codex",
152
+ backend_type="openai_codex",
153
+ default_base_url="https://chatgpt.com/backend-api",
154
+ detect_by_key_prefix="",
155
+ detect_by_base_keyword="chatgpt.com/backend-api",
156
+ is_gateway=False,
157
+ is_local=False,
158
+ is_oauth=True,
159
+ ),
160
+ # === 标准云提供商(按模型名称关键字匹配) ============
161
+ # Anthropic:claude-* 模型的原生 SDK
162
+ ProviderSpec(
163
+ name="anthropic",
164
+ keywords=("anthropic", "claude"),
165
+ env_key="ANTHROPIC_API_KEY",
166
+ display_name="Anthropic",
167
+ backend_type="anthropic",
168
+ default_base_url="",
169
+ detect_by_key_prefix="",
170
+ detect_by_base_keyword="",
171
+ is_gateway=False,
172
+ is_local=False,
173
+ is_oauth=False,
174
+ ),
175
+ # OpenAI:gpt-* 模型
176
+ ProviderSpec(
177
+ name="openai",
178
+ keywords=("openai", "gpt", "o1", "o3", "o4"),
179
+ env_key="OPENAI_API_KEY",
180
+ display_name="OpenAI",
181
+ backend_type="openai_compat",
182
+ default_base_url="",
183
+ detect_by_key_prefix="",
184
+ detect_by_base_keyword="",
185
+ is_gateway=False,
186
+ is_local=False,
187
+ is_oauth=False,
188
+ ),
189
+ # DeepSeek
190
+ ProviderSpec(
191
+ name="deepseek",
192
+ keywords=("deepseek",),
193
+ env_key="DEEPSEEK_API_KEY",
194
+ display_name="DeepSeek",
195
+ backend_type="openai_compat",
196
+ default_base_url="https://api.deepseek.com/v1",
197
+ detect_by_key_prefix="",
198
+ detect_by_base_keyword="deepseek",
199
+ is_gateway=False,
200
+ is_local=False,
201
+ is_oauth=False,
202
+ ),
203
+ # Google Gemini
204
+ ProviderSpec(
205
+ name="gemini",
206
+ keywords=("gemini",),
207
+ env_key="GEMINI_API_KEY",
208
+ display_name="Gemini",
209
+ backend_type="openai_compat",
210
+ default_base_url="https://generativelanguage.googleapis.com/v1beta/openai",
211
+ detect_by_key_prefix="",
212
+ detect_by_base_keyword="googleapis",
213
+ is_gateway=False,
214
+ is_local=False,
215
+ is_oauth=False,
216
+ ),
217
+ # DashScope(Qwen / 阿里云)
218
+ ProviderSpec(
219
+ name="dashscope",
220
+ keywords=("qwen", "dashscope"),
221
+ env_key="DASHSCOPE_API_KEY",
222
+ display_name="DashScope",
223
+ backend_type="openai_compat",
224
+ default_base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
225
+ detect_by_key_prefix="",
226
+ detect_by_base_keyword="dashscope",
227
+ is_gateway=False,
228
+ is_local=False,
229
+ is_oauth=False,
230
+ ),
231
+ # Moonshot / Kimi
232
+ ProviderSpec(
233
+ name="moonshot",
234
+ keywords=("moonshot", "kimi"),
235
+ env_key="MOONSHOT_API_KEY",
236
+ display_name="Moonshot",
237
+ backend_type="openai_compat",
238
+ default_base_url="https://api.moonshot.ai/v1",
239
+ detect_by_key_prefix="",
240
+ detect_by_base_keyword="moonshot",
241
+ is_gateway=False,
242
+ is_local=False,
243
+ is_oauth=False,
244
+ ),
245
+ # MiniMax
246
+ ProviderSpec(
247
+ name="minimax",
248
+ keywords=("minimax",),
249
+ env_key="MINIMAX_API_KEY",
250
+ display_name="MiniMax",
251
+ backend_type="openai_compat",
252
+ default_base_url="https://api.minimax.io/v1",
253
+ detect_by_key_prefix="",
254
+ detect_by_base_keyword="minimax",
255
+ is_gateway=False,
256
+ is_local=False,
257
+ is_oauth=False,
258
+ ),
259
+ # Zhipu AI / GLM
260
+ ProviderSpec(
261
+ name="zhipu",
262
+ keywords=("zhipu", "glm", "chatglm"),
263
+ env_key="ZHIPUAI_API_KEY",
264
+ display_name="Zhipu AI",
265
+ backend_type="openai_compat",
266
+ default_base_url="https://open.bigmodel.cn/api/paas/v4",
267
+ detect_by_key_prefix="",
268
+ detect_by_base_keyword="bigmodel",
269
+ is_gateway=False,
270
+ is_local=False,
271
+ is_oauth=False,
272
+ ),
273
+ # Groq
274
+ ProviderSpec(
275
+ name="groq",
276
+ keywords=("groq",),
277
+ env_key="GROQ_API_KEY",
278
+ display_name="Groq",
279
+ backend_type="openai_compat",
280
+ default_base_url="https://api.groq.com/openai/v1",
281
+ detect_by_key_prefix="gsk_",
282
+ detect_by_base_keyword="groq",
283
+ is_gateway=False,
284
+ is_local=False,
285
+ is_oauth=False,
286
+ ),
287
+ # Mistral
288
+ ProviderSpec(
289
+ name="mistral",
290
+ keywords=("mistral", "mixtral", "codestral"),
291
+ env_key="MISTRAL_API_KEY",
292
+ display_name="Mistral",
293
+ backend_type="openai_compat",
294
+ default_base_url="https://api.mistral.ai/v1",
295
+ detect_by_key_prefix="",
296
+ detect_by_base_keyword="mistral",
297
+ is_gateway=False,
298
+ is_local=False,
299
+ is_oauth=False,
300
+ ),
301
+ # StepFun(阶跃星辰)
302
+ ProviderSpec(
303
+ name="stepfun",
304
+ keywords=("step-", "stepfun"),
305
+ env_key="STEPFUN_API_KEY",
306
+ display_name="StepFun",
307
+ backend_type="openai_compat",
308
+ default_base_url="https://api.stepfun.com/v1",
309
+ detect_by_key_prefix="",
310
+ detect_by_base_keyword="stepfun",
311
+ is_gateway=False,
312
+ is_local=False,
313
+ is_oauth=False,
314
+ ),
315
+ # Baidu / ERNIE
316
+ ProviderSpec(
317
+ name="baidu",
318
+ keywords=("ernie", "baidu"),
319
+ env_key="QIANFAN_ACCESS_KEY",
320
+ display_name="Baidu",
321
+ backend_type="openai_compat",
322
+ default_base_url="https://qianfan.baidubce.com/v2",
323
+ detect_by_key_prefix="",
324
+ detect_by_base_keyword="baidubce",
325
+ is_gateway=False,
326
+ is_local=False,
327
+ is_oauth=False,
328
+ ),
329
+ # === 云平台提供商(通过 base_url 检测) ====================
330
+ # AWS Bedrock
331
+ ProviderSpec(
332
+ name="bedrock",
333
+ keywords=("bedrock",),
334
+ env_key="AWS_ACCESS_KEY_ID",
335
+ display_name="AWS Bedrock",
336
+ backend_type="openai_compat",
337
+ default_base_url="",
338
+ detect_by_key_prefix="",
339
+ detect_by_base_keyword="bedrock",
340
+ is_gateway=False,
341
+ is_local=False,
342
+ is_oauth=False,
343
+ ),
344
+ # Google Vertex AI
345
+ ProviderSpec(
346
+ name="vertex",
347
+ keywords=("vertex",),
348
+ env_key="GOOGLE_APPLICATION_CREDENTIALS",
349
+ display_name="Vertex AI",
350
+ backend_type="openai_compat",
351
+ default_base_url="",
352
+ detect_by_key_prefix="",
353
+ detect_by_base_keyword="aiplatform",
354
+ is_gateway=False,
355
+ is_local=False,
356
+ is_oauth=False,
357
+ ),
358
+ # === 本地部署(按关键字或 base_url 匹配) =================
359
+ # Ollama
360
+ ProviderSpec(
361
+ name="ollama",
362
+ keywords=("ollama",),
363
+ env_key="",
364
+ display_name="Ollama",
365
+ backend_type="openai_compat",
366
+ default_base_url="http://localhost:11434/v1",
367
+ detect_by_key_prefix="",
368
+ detect_by_base_keyword="localhost:11434",
369
+ is_gateway=False,
370
+ is_local=True,
371
+ is_oauth=False,
372
+ ),
373
+ # vLLM / 任意 OpenAI 兼容本地服务器
374
+ ProviderSpec(
375
+ name="vllm",
376
+ keywords=("vllm",),
377
+ env_key="",
378
+ display_name="vLLM/Local",
379
+ backend_type="openai_compat",
380
+ default_base_url="",
381
+ detect_by_key_prefix="",
382
+ detect_by_base_keyword="",
383
+ is_gateway=False,
384
+ is_local=True,
385
+ is_oauth=False,
386
+ ),
387
+ )
388
+
389
+
390
+ # ---------------------------------------------------------------------------
391
+ # 查找辅助函数
392
+ # ---------------------------------------------------------------------------
393
+
394
+
395
+ def find_by_name(name: str) -> ProviderSpec | None:
396
+ """按规范名称查找提供商规格,如 "dashscope"。
397
+
398
+ Args:
399
+ name: 提供商名称
400
+
401
+ Returns:
402
+ ProviderSpec | None: 提供商规格,如果未找到则返回 None
403
+ """
404
+ for spec in PROVIDERS:
405
+ if spec.name == name:
406
+ return spec
407
+ return None
408
+
409
+
410
+ def _match_by_model(model: str) -> ProviderSpec | None:
411
+ """按模型名称关键字匹配标准/网关提供商(不区分大小写)。
412
+
413
+ Args:
414
+ model: 模型名称
415
+
416
+ Returns:
417
+ ProviderSpec | None: 提供商规格,如果未找到则返回 None
418
+ """
419
+ model_lower = model.lower()
420
+ model_normalized = model_lower.replace("-", "_")
421
+ model_prefix = model_lower.split("/", 1)[0] if "/" in model_lower else ""
422
+ normalized_prefix = model_prefix.replace("-", "_")
423
+
424
+ # 过滤非本地非 OAuth 的规格
425
+ std_specs = [s for s in PROVIDERS if not s.is_local and not s.is_oauth]
426
+
427
+ # 优先显式提供商前缀匹配(如 "deepseek/..." → deepseek 规格)
428
+ for spec in std_specs:
429
+ if model_prefix and normalized_prefix == spec.name:
430
+ return spec
431
+
432
+ # 回退到关键字扫描
433
+ for spec in std_specs:
434
+ if any(
435
+ kw in model_lower or kw.replace("-", "_") in model_normalized
436
+ for kw in spec.keywords
437
+ ):
438
+ return spec
439
+ return None
440
+
441
+
442
+ def detect_provider_from_registry(
443
+ model: str,
444
+ api_key: str | None = None,
445
+ base_url: str | None = None,
446
+ ) -> ProviderSpec | None:
447
+ """检测给定输入的最佳匹配 ProviderSpec。
448
+
449
+ 检测优先级:
450
+ 1. api_key 前缀(如 "sk-or-" → OpenRouter)
451
+ 2. base_url 关键字(如 URL 中的 "aihubmix" → AiHubMix)
452
+ 3. 模型名称关键字(如 "qwen" → DashScope)
453
+
454
+ Args:
455
+ model: 模型名称
456
+ api_key: API 密钥(可选)
457
+ base_url: 基础 URL(可选)
458
+
459
+ Returns:
460
+ ProviderSpec | None: 最佳匹配的提供商规格
461
+ """
462
+ # 1. api_key 前缀
463
+ if api_key:
464
+ for spec in PROVIDERS:
465
+ if spec.detect_by_key_prefix and api_key.startswith(spec.detect_by_key_prefix):
466
+ return spec
467
+
468
+ # 2. base_url 关键字
469
+ if base_url:
470
+ base_lower = base_url.lower()
471
+ for spec in PROVIDERS:
472
+ if spec.detect_by_base_keyword and spec.detect_by_base_keyword in base_lower:
473
+ return spec
474
+
475
+ # 3. 模型关键字
476
+ if model:
477
+ return _match_by_model(model)
478
+
479
+ return None
illusion/api/usage.py ADDED
@@ -0,0 +1,45 @@
1
+ """
2
+ 使用量追踪模块
3
+ ==============
4
+
5
+ 本模块提供 API 使用量追踪的数据模型。
6
+
7
+ 主要功能:
8
+ - 记录输入/输出令牌数
9
+ - 计算总令牌数
10
+
11
+ 类说明:
12
+ - UsageSnapshot: 模型提供商返回的使用量快照
13
+
14
+ 使用示例:
15
+ >>> from illusion.api.usage import UsageSnapshot
16
+ >>> usage = UsageSnapshot(input_tokens=1000, output_tokens=500)
17
+ >>> print(f"总令牌数: {usage.total_tokens}")
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from pydantic import BaseModel
23
+
24
+
25
+ class UsageSnapshot(BaseModel):
26
+ """模型提供商返回的令牌使用量
27
+
28
+ 记录一次 API 调用消耗的输入和输出令牌数量。
29
+
30
+ Attributes:
31
+ input_tokens: 输入令牌数量(默认 0)
32
+ output_tokens: 输出令牌数量(默认 0)
33
+ """
34
+
35
+ input_tokens: int = 0
36
+ output_tokens: int = 0
37
+
38
+ @property
39
+ def total_tokens(self) -> int:
40
+ """返回总令牌数量
41
+
42
+ Returns:
43
+ int: 输入令牌与输出令牌之和
44
+ """
45
+ return self.input_tokens + self.output_tokens