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/api/provider.py
ADDED
|
@@ -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"
|
illusion/api/registry.py
ADDED
|
@@ -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
|