api-key-manager 2.2.0__tar.gz → 3.0.1__tar.gz
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.
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/PKG-INFO +31 -8
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/README.md +29 -6
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/api_key_manager.egg-info/PKG-INFO +31 -8
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/api_key_manager.egg-info/SOURCES.txt +3 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/__init__.py +2 -2
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/api_models.py +15 -10
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/checker.py +13 -8
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/cli.py +2 -2
- api_key_manager-3.0.1/key_manager/config.py +103 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/detector.py +72 -64
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/logger.py +2 -2
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/parser.py +9 -9
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/__init__.py +2 -1
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/base.py +20 -1
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/google.py +99 -99
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/ssrf.py +29 -2
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/storage.py +15 -2
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/tester.py +16 -13
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/validator.py +16 -12
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/web.py +125 -8
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/webhook.py +6 -5
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/pyproject.toml +2 -2
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_api_endpoints.py +193 -8
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_detector.py +71 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_e2e.py +4 -1
- api_key_manager-3.0.1/tests/test_parser_supplement.py +216 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_provider_detection.py +73 -14
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_security.py +3 -3
- api_key_manager-3.0.1/tests/test_ssrf_supplement.py +81 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_tester.py +46 -20
- api_key_manager-3.0.1/tests/test_validator_supplement.py +196 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_webhook.py +1 -1
- api_key_manager-2.2.0/key_manager/config.py +0 -61
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/api_key_manager.egg-info/dependency_links.txt +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/api_key_manager.egg-info/entry_points.txt +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/api_key_manager.egg-info/requires.txt +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/api_key_manager.egg-info/top_level.txt +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/__main__.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/core.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/errors.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/i18n.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/model_capabilities.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/ai302.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/anthropic.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/baichuan.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/cerebras.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/cohere.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/cstcloud.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/dashscope.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/dashscope_coding.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/deepseek.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/dmxapi.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/doubao.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/fireworks.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/grok.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/groq.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/huggingface.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/hyperbolic.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/infini.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/infini_coding.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/kimi.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/kimi_coding.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/longcat.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/mimo.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/mimo_plan.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/minimax.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/minimax_plan.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/mistral.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/models_registry.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/modelscope.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/nvidia.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/ocoolai.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/openai.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/opencode.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/opencode_zen.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/openrouter.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/perplexity.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/poe.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/ppio.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/replicate.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/siliconflow.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/stepfun.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/tencent_hunyuan.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/together.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/yi.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/zai.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/zhipu.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/providers/zhipu_coding.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/proxy.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/py.typed +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/key_manager/url_override.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/setup.cfg +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_base_check.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_bug_fixes.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_checker.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_core.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_errors.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_i18n.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_logger.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_openapi.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_parser.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_providers.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_proxy.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_storage.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_validator.py +0 -0
- {api_key_manager-2.2.0 → api_key_manager-3.0.1}/tests/test_web_fixes.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: api-key-manager
|
|
3
|
-
Version:
|
|
4
|
-
Summary: Batch manage API keys for
|
|
3
|
+
Version: 3.0.1
|
|
4
|
+
Summary: Batch manage API keys for 45+ AI providers with CLI and Web interfaces
|
|
5
5
|
Author: Townrain
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Townrain/API-Key-Manager
|
|
@@ -85,7 +85,8 @@ Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
|
85
85
|
|
|
86
86
|
| 服务商 | 前缀 | 显示名 |
|
|
87
87
|
|--------|------|--------|
|
|
88
|
-
|
|
|
88
|
+
| 阿里百炼 | `sk-ws-` / `sk-` |
|
|
89
|
+
| 阿里百炼编程 | `sk-sp-` |
|
|
89
90
|
| ModelScope | `ms-` | 魔搭 |
|
|
90
91
|
| Zhipu GLM | `sk-` | 智谱 |
|
|
91
92
|
| Kimi | `sk-` | 月之暗面 |
|
|
@@ -341,7 +342,7 @@ if best_score >= 200:
|
|
|
341
342
|
### 检测优先级
|
|
342
343
|
|
|
343
344
|
| 优先级 | 方法 | 说明 |
|
|
344
|
-
|
|
345
|
+
|--------|------|------|
|
|
345
346
|
| 1 | 模式匹配 | 唯一前缀,如 `sk-proj-` → OpenAI |
|
|
346
347
|
| 2 | 格式匹配 | 特殊格式,如 `{id}.{secret}` → 智谱 |
|
|
347
348
|
| 3 | 全并发探测 | 第一个返回200的服务商胜出 |
|
|
@@ -369,9 +370,6 @@ if best_score >= 200:
|
|
|
369
370
|
- 基于关键词的模式匹配(authentication、expired、rate limit 等)
|
|
370
371
|
- 长错误信息截断(超过100字符时截断并添加省略号)
|
|
371
372
|
|
|
372
|
-
## 项目结构
|
|
373
|
-
|
|
374
|
-
|
|
375
373
|
## 模型检测
|
|
376
374
|
|
|
377
375
|
### 模型列表来源
|
|
@@ -718,6 +716,32 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
|
|
|
718
716
|
|
|
719
717
|
## 更新日志
|
|
720
718
|
|
|
719
|
+
|
|
720
|
+
### v3.0.0 (2026-06-15)
|
|
721
|
+
|
|
722
|
+
- **精确匹配修复**: 能力检测从子串匹配改为 ^$ 锚定精确匹配,修复 o1 误匹配 o1-mini 等问题
|
|
723
|
+
- **API Bug 修复**: 修复 /api/stats/chart 运行时崩溃、/api/keys 和 /api/keys/export 丢失 key 字段
|
|
724
|
+
- **测试补充**: 新增 8 个端点的测试(test/token, test/concurrency, models/capabilities, models/check, progress/stream 等)
|
|
725
|
+
- **前端缓存优化**: 模型类型切换使用本地缓存过滤,不再重复请求 API
|
|
726
|
+
- **Cherry Studio 同步工作流修复**: 修复 GitHub Actions 路径错误和验证脚本
|
|
727
|
+
- **Autofill 样式修复**: 修复浏览器 autofill 导致输入框底色变白
|
|
728
|
+
|
|
729
|
+
### v2.2.2 (2026-06-14)
|
|
730
|
+
|
|
731
|
+
- **模型检测二级窗口**: 新增模型检测弹窗,支持搜索、类型筛选、全选、批量检测
|
|
732
|
+
- **模型类型图标**: 显示模型能力图标(视觉/推理/联网/工具/嵌入/重排/免费)
|
|
733
|
+
- **模型能力数据更新**: 从 Cherry Studio 同步最新模型能力数据(3184 条记录)
|
|
734
|
+
- **检测逻辑优化**: DeepSeek 欠费密钥正确识别,不再误判为其他服务商
|
|
735
|
+
- **指定模型检测**: 自动检测服务商时也支持指定模型检测
|
|
736
|
+
|
|
737
|
+
### v2.2.1 (2026-06-14)
|
|
738
|
+
|
|
739
|
+
- **检测逻辑修复**: 自动识别后必须调用 check() 验证 key 有效性
|
|
740
|
+
- **签名匹配修复**: 状态码不再硬编码 401,使用实际状态码
|
|
741
|
+
- **不可靠服务商**: ppio、nvidia、modelscope 不参与加分
|
|
742
|
+
- **模型选择器**: 新增模型下拉框,支持选择具体模型检测
|
|
743
|
+
- **Cherry Studio 同步**: 修复 ownedBy 映射(alibaba→dashscope 等)
|
|
744
|
+
- **测试补充**: 新增 test_parser_supplement、test_ssrf_supplement、test_validator_supplement
|
|
721
745
|
### v2.2.0 (2026-06-14)
|
|
722
746
|
|
|
723
747
|
- **检测逻辑重构**: 添加三步检测逻辑(/v1/models → 并发测试)
|
|
@@ -726,7 +750,6 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
|
|
|
726
750
|
- **超时控制**: 所有网络请求 5 秒超时
|
|
727
751
|
- **模型同步**: 从 Cherry Studio 同步模型数据,支持 ownedBy 映射
|
|
728
752
|
- **新增服务商**: OpenCode Go、OpenCode Zen
|
|
729
|
-
- **新增服务商**: OpenCode Go、OpenCode Zen
|
|
730
753
|
|
|
731
754
|
### v2.1.2 (2026-06-11)
|
|
732
755
|
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
|
|
53
53
|
| 服务商 | 前缀 | 显示名 |
|
|
54
54
|
|--------|------|--------|
|
|
55
|
-
|
|
|
55
|
+
| 阿里百炼 | `sk-ws-` / `sk-` |
|
|
56
|
+
| 阿里百炼编程 | `sk-sp-` |
|
|
56
57
|
| ModelScope | `ms-` | 魔搭 |
|
|
57
58
|
| Zhipu GLM | `sk-` | 智谱 |
|
|
58
59
|
| Kimi | `sk-` | 月之暗面 |
|
|
@@ -308,7 +309,7 @@ if best_score >= 200:
|
|
|
308
309
|
### 检测优先级
|
|
309
310
|
|
|
310
311
|
| 优先级 | 方法 | 说明 |
|
|
311
|
-
|
|
312
|
+
|--------|------|------|
|
|
312
313
|
| 1 | 模式匹配 | 唯一前缀,如 `sk-proj-` → OpenAI |
|
|
313
314
|
| 2 | 格式匹配 | 特殊格式,如 `{id}.{secret}` → 智谱 |
|
|
314
315
|
| 3 | 全并发探测 | 第一个返回200的服务商胜出 |
|
|
@@ -336,9 +337,6 @@ if best_score >= 200:
|
|
|
336
337
|
- 基于关键词的模式匹配(authentication、expired、rate limit 等)
|
|
337
338
|
- 长错误信息截断(超过100字符时截断并添加省略号)
|
|
338
339
|
|
|
339
|
-
## 项目结构
|
|
340
|
-
|
|
341
|
-
|
|
342
340
|
## 模型检测
|
|
343
341
|
|
|
344
342
|
### 模型列表来源
|
|
@@ -685,6 +683,32 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
|
|
|
685
683
|
|
|
686
684
|
## 更新日志
|
|
687
685
|
|
|
686
|
+
|
|
687
|
+
### v3.0.0 (2026-06-15)
|
|
688
|
+
|
|
689
|
+
- **精确匹配修复**: 能力检测从子串匹配改为 ^$ 锚定精确匹配,修复 o1 误匹配 o1-mini 等问题
|
|
690
|
+
- **API Bug 修复**: 修复 /api/stats/chart 运行时崩溃、/api/keys 和 /api/keys/export 丢失 key 字段
|
|
691
|
+
- **测试补充**: 新增 8 个端点的测试(test/token, test/concurrency, models/capabilities, models/check, progress/stream 等)
|
|
692
|
+
- **前端缓存优化**: 模型类型切换使用本地缓存过滤,不再重复请求 API
|
|
693
|
+
- **Cherry Studio 同步工作流修复**: 修复 GitHub Actions 路径错误和验证脚本
|
|
694
|
+
- **Autofill 样式修复**: 修复浏览器 autofill 导致输入框底色变白
|
|
695
|
+
|
|
696
|
+
### v2.2.2 (2026-06-14)
|
|
697
|
+
|
|
698
|
+
- **模型检测二级窗口**: 新增模型检测弹窗,支持搜索、类型筛选、全选、批量检测
|
|
699
|
+
- **模型类型图标**: 显示模型能力图标(视觉/推理/联网/工具/嵌入/重排/免费)
|
|
700
|
+
- **模型能力数据更新**: 从 Cherry Studio 同步最新模型能力数据(3184 条记录)
|
|
701
|
+
- **检测逻辑优化**: DeepSeek 欠费密钥正确识别,不再误判为其他服务商
|
|
702
|
+
- **指定模型检测**: 自动检测服务商时也支持指定模型检测
|
|
703
|
+
|
|
704
|
+
### v2.2.1 (2026-06-14)
|
|
705
|
+
|
|
706
|
+
- **检测逻辑修复**: 自动识别后必须调用 check() 验证 key 有效性
|
|
707
|
+
- **签名匹配修复**: 状态码不再硬编码 401,使用实际状态码
|
|
708
|
+
- **不可靠服务商**: ppio、nvidia、modelscope 不参与加分
|
|
709
|
+
- **模型选择器**: 新增模型下拉框,支持选择具体模型检测
|
|
710
|
+
- **Cherry Studio 同步**: 修复 ownedBy 映射(alibaba→dashscope 等)
|
|
711
|
+
- **测试补充**: 新增 test_parser_supplement、test_ssrf_supplement、test_validator_supplement
|
|
688
712
|
### v2.2.0 (2026-06-14)
|
|
689
713
|
|
|
690
714
|
- **检测逻辑重构**: 添加三步检测逻辑(/v1/models → 并发测试)
|
|
@@ -693,7 +717,6 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
|
|
|
693
717
|
- **超时控制**: 所有网络请求 5 秒超时
|
|
694
718
|
- **模型同步**: 从 Cherry Studio 同步模型数据,支持 ownedBy 映射
|
|
695
719
|
- **新增服务商**: OpenCode Go、OpenCode Zen
|
|
696
|
-
- **新增服务商**: OpenCode Go、OpenCode Zen
|
|
697
720
|
|
|
698
721
|
### v2.1.2 (2026-06-11)
|
|
699
722
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: api-key-manager
|
|
3
|
-
Version:
|
|
4
|
-
Summary: Batch manage API keys for
|
|
3
|
+
Version: 3.0.1
|
|
4
|
+
Summary: Batch manage API keys for 45+ AI providers with CLI and Web interfaces
|
|
5
5
|
Author: Townrain
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Townrain/API-Key-Manager
|
|
@@ -85,7 +85,8 @@ Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
|
85
85
|
|
|
86
86
|
| 服务商 | 前缀 | 显示名 |
|
|
87
87
|
|--------|------|--------|
|
|
88
|
-
|
|
|
88
|
+
| 阿里百炼 | `sk-ws-` / `sk-` |
|
|
89
|
+
| 阿里百炼编程 | `sk-sp-` |
|
|
89
90
|
| ModelScope | `ms-` | 魔搭 |
|
|
90
91
|
| Zhipu GLM | `sk-` | 智谱 |
|
|
91
92
|
| Kimi | `sk-` | 月之暗面 |
|
|
@@ -341,7 +342,7 @@ if best_score >= 200:
|
|
|
341
342
|
### 检测优先级
|
|
342
343
|
|
|
343
344
|
| 优先级 | 方法 | 说明 |
|
|
344
|
-
|
|
345
|
+
|--------|------|------|
|
|
345
346
|
| 1 | 模式匹配 | 唯一前缀,如 `sk-proj-` → OpenAI |
|
|
346
347
|
| 2 | 格式匹配 | 特殊格式,如 `{id}.{secret}` → 智谱 |
|
|
347
348
|
| 3 | 全并发探测 | 第一个返回200的服务商胜出 |
|
|
@@ -369,9 +370,6 @@ if best_score >= 200:
|
|
|
369
370
|
- 基于关键词的模式匹配(authentication、expired、rate limit 等)
|
|
370
371
|
- 长错误信息截断(超过100字符时截断并添加省略号)
|
|
371
372
|
|
|
372
|
-
## 项目结构
|
|
373
|
-
|
|
374
|
-
|
|
375
373
|
## 模型检测
|
|
376
374
|
|
|
377
375
|
### 模型列表来源
|
|
@@ -718,6 +716,32 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
|
|
|
718
716
|
|
|
719
717
|
## 更新日志
|
|
720
718
|
|
|
719
|
+
|
|
720
|
+
### v3.0.0 (2026-06-15)
|
|
721
|
+
|
|
722
|
+
- **精确匹配修复**: 能力检测从子串匹配改为 ^$ 锚定精确匹配,修复 o1 误匹配 o1-mini 等问题
|
|
723
|
+
- **API Bug 修复**: 修复 /api/stats/chart 运行时崩溃、/api/keys 和 /api/keys/export 丢失 key 字段
|
|
724
|
+
- **测试补充**: 新增 8 个端点的测试(test/token, test/concurrency, models/capabilities, models/check, progress/stream 等)
|
|
725
|
+
- **前端缓存优化**: 模型类型切换使用本地缓存过滤,不再重复请求 API
|
|
726
|
+
- **Cherry Studio 同步工作流修复**: 修复 GitHub Actions 路径错误和验证脚本
|
|
727
|
+
- **Autofill 样式修复**: 修复浏览器 autofill 导致输入框底色变白
|
|
728
|
+
|
|
729
|
+
### v2.2.2 (2026-06-14)
|
|
730
|
+
|
|
731
|
+
- **模型检测二级窗口**: 新增模型检测弹窗,支持搜索、类型筛选、全选、批量检测
|
|
732
|
+
- **模型类型图标**: 显示模型能力图标(视觉/推理/联网/工具/嵌入/重排/免费)
|
|
733
|
+
- **模型能力数据更新**: 从 Cherry Studio 同步最新模型能力数据(3184 条记录)
|
|
734
|
+
- **检测逻辑优化**: DeepSeek 欠费密钥正确识别,不再误判为其他服务商
|
|
735
|
+
- **指定模型检测**: 自动检测服务商时也支持指定模型检测
|
|
736
|
+
|
|
737
|
+
### v2.2.1 (2026-06-14)
|
|
738
|
+
|
|
739
|
+
- **检测逻辑修复**: 自动识别后必须调用 check() 验证 key 有效性
|
|
740
|
+
- **签名匹配修复**: 状态码不再硬编码 401,使用实际状态码
|
|
741
|
+
- **不可靠服务商**: ppio、nvidia、modelscope 不参与加分
|
|
742
|
+
- **模型选择器**: 新增模型下拉框,支持选择具体模型检测
|
|
743
|
+
- **Cherry Studio 同步**: 修复 ownedBy 映射(alibaba→dashscope 等)
|
|
744
|
+
- **测试补充**: 新增 test_parser_supplement、test_ssrf_supplement、test_validator_supplement
|
|
721
745
|
### v2.2.0 (2026-06-14)
|
|
722
746
|
|
|
723
747
|
- **检测逻辑重构**: 添加三步检测逻辑(/v1/models → 并发测试)
|
|
@@ -726,7 +750,6 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
|
|
|
726
750
|
- **超时控制**: 所有网络请求 5 秒超时
|
|
727
751
|
- **模型同步**: 从 Cherry Studio 同步模型数据,支持 ownedBy 映射
|
|
728
752
|
- **新增服务商**: OpenCode Go、OpenCode Zen
|
|
729
|
-
- **新增服务商**: OpenCode Go、OpenCode Zen
|
|
730
753
|
|
|
731
754
|
### v2.1.2 (2026-06-11)
|
|
732
755
|
|
|
@@ -89,12 +89,15 @@ tests/test_i18n.py
|
|
|
89
89
|
tests/test_logger.py
|
|
90
90
|
tests/test_openapi.py
|
|
91
91
|
tests/test_parser.py
|
|
92
|
+
tests/test_parser_supplement.py
|
|
92
93
|
tests/test_provider_detection.py
|
|
93
94
|
tests/test_providers.py
|
|
94
95
|
tests/test_proxy.py
|
|
95
96
|
tests/test_security.py
|
|
97
|
+
tests/test_ssrf_supplement.py
|
|
96
98
|
tests/test_storage.py
|
|
97
99
|
tests/test_tester.py
|
|
98
100
|
tests/test_validator.py
|
|
101
|
+
tests/test_validator_supplement.py
|
|
99
102
|
tests/test_web_fixes.py
|
|
100
103
|
tests/test_webhook.py
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Key Manager - Batch API key management for
|
|
1
|
+
"""Key Manager - Batch API key management for 45+ AI providers."""
|
|
2
2
|
from key_manager.core import KeyManager
|
|
3
3
|
from key_manager.providers import PROVIDERS, get_display_name
|
|
4
4
|
from key_manager.errors import KeyManagerError, ErrorCode
|
|
@@ -13,5 +13,5 @@ __all__ = [
|
|
|
13
13
|
"KeyStore",
|
|
14
14
|
]
|
|
15
15
|
|
|
16
|
-
__version__ = "
|
|
16
|
+
__version__ = "3.0.0"
|
|
17
17
|
|
|
@@ -71,6 +71,7 @@ class KeyListResponse(BaseModel):
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
class KeyExportItem(BaseModel):
|
|
74
|
+
key_masked: str = Field(..., description="Masked key for display")
|
|
74
75
|
provider: str = Field(..., description="Provider identifier")
|
|
75
76
|
max_tokens: int | None = Field(None, description="Maximum token limit")
|
|
76
77
|
max_concurrency: int | None = Field(None, description="Maximum concurrency limit")
|
|
@@ -111,11 +112,12 @@ class ImportResponse(BaseModel):
|
|
|
111
112
|
class CheckSingleRequest(BaseModel):
|
|
112
113
|
key: str = Field(..., min_length=1, description="API key to check")
|
|
113
114
|
provider: str = Field("", description="Provider name (auto-detected if empty)")
|
|
115
|
+
model: str | None = Field(None, description="Specific model to test (tests all if empty)")
|
|
114
116
|
custom_base_url: str | None = Field(None, description="Override provider base URL")
|
|
115
117
|
|
|
116
118
|
model_config = {
|
|
117
119
|
"json_schema_extra": {
|
|
118
|
-
"examples": [{"key": "sk-abc123", "provider": "openai"}]
|
|
120
|
+
"examples": [{"key": "sk-abc123", "provider": "openai", "model": "gpt-4o"}]
|
|
119
121
|
}
|
|
120
122
|
}
|
|
121
123
|
|
|
@@ -296,23 +298,26 @@ class StatsResponse(BaseModel):
|
|
|
296
298
|
total: int = Field(0, description="Total key count")
|
|
297
299
|
|
|
298
300
|
|
|
299
|
-
class StatsChartProviderEntry(BaseModel):
|
|
300
|
-
total: int = Field(0, description="Total keys for this provider")
|
|
301
|
-
valid: int = Field(0, description="Valid keys")
|
|
302
|
-
invalid: int = Field(0, description="Invalid keys")
|
|
303
|
-
error: int = Field(0, description="Errored keys")
|
|
304
|
-
|
|
305
|
-
|
|
306
301
|
class StatsChartStatuses(BaseModel):
|
|
307
302
|
valid: int = Field(0, description="Total valid keys")
|
|
308
303
|
invalid: int = Field(0, description="Total invalid keys")
|
|
309
304
|
error: int = Field(0, description="Total errored keys")
|
|
310
305
|
unknown: int = Field(0, description="Total unknown-status keys")
|
|
311
306
|
|
|
307
|
+
class StatsChartProviderEntry(BaseModel):
|
|
308
|
+
provider: str = Field(..., description="Provider identifier")
|
|
309
|
+
display_name: str = Field("", description="Human-readable provider name")
|
|
310
|
+
total: int = Field(0, description="Total keys for this provider")
|
|
311
|
+
valid: int = Field(0, description="Valid keys")
|
|
312
|
+
invalid: int = Field(0, description="Invalid keys")
|
|
313
|
+
error: int = Field(0, description="Errored keys")
|
|
314
|
+
statuses: StatsChartStatuses = Field(
|
|
315
|
+
default_factory=StatsChartStatuses, description="Status breakdown"
|
|
316
|
+
)
|
|
312
317
|
|
|
313
318
|
class StatsChartResponse(BaseModel):
|
|
314
|
-
providers:
|
|
315
|
-
default_factory=
|
|
319
|
+
providers: list[StatsChartProviderEntry] = Field(
|
|
320
|
+
default_factory=list, description="Per-provider breakdown for charts"
|
|
316
321
|
)
|
|
317
322
|
statuses: StatsChartStatuses = Field(
|
|
318
323
|
default_factory=StatsChartStatuses, description="Global status counts"
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import json
|
|
3
2
|
from datetime import datetime
|
|
4
|
-
from pathlib import Path
|
|
5
3
|
|
|
6
4
|
import httpx
|
|
7
5
|
|
|
8
6
|
from .providers import PROVIDERS
|
|
9
7
|
from .validator import validate_keys
|
|
10
8
|
|
|
11
|
-
|
|
12
9
|
async def run_check(keys_file: str = "./data/keys.json",
|
|
13
10
|
results_file: str = "./data/check_results.json",
|
|
14
11
|
logs_dir: str = "./data/logs",
|
|
@@ -16,14 +13,16 @@ async def run_check(keys_file: str = "./data/keys.json",
|
|
|
16
13
|
timeout: int = 30,
|
|
17
14
|
proxy: str = None,
|
|
18
15
|
retry_failed: bool = True,
|
|
19
|
-
retry_count: int = 2
|
|
16
|
+
retry_count: int = 2,
|
|
17
|
+
config: dict = None) -> dict:
|
|
20
18
|
results = await validate_keys(
|
|
21
19
|
keys_file=keys_file,
|
|
22
20
|
results_file=results_file,
|
|
23
21
|
logs_dir=logs_dir,
|
|
24
22
|
concurrency=concurrency,
|
|
25
23
|
timeout=timeout,
|
|
26
|
-
proxy=proxy
|
|
24
|
+
proxy=proxy,
|
|
25
|
+
config=config
|
|
27
26
|
)
|
|
28
27
|
|
|
29
28
|
# Retry failed keys
|
|
@@ -40,12 +39,18 @@ async def run_check(keys_file: str = "./data/keys.json",
|
|
|
40
39
|
concurrency=concurrency,
|
|
41
40
|
timeout=timeout,
|
|
42
41
|
proxy=proxy,
|
|
43
|
-
status_filter="error"
|
|
42
|
+
status_filter="error",
|
|
43
|
+
config=config
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
-
#
|
|
46
|
+
# Recompute summary from details to avoid double-counting
|
|
47
|
+
# The retry only re-checks error keys, so we need to update the summary
|
|
48
|
+
# based on what changed
|
|
49
|
+
results["summary"]["error"]["count"] = retry_results["summary"]["error"]["count"]
|
|
50
|
+
results["summary"]["error"]["keys"] = retry_results["summary"]["error"]["keys"]
|
|
47
51
|
results["summary"]["valid"]["count"] += retry_results["summary"]["valid"]["count"]
|
|
52
|
+
results["summary"]["valid"]["keys"].extend(retry_results["summary"]["valid"]["keys"])
|
|
48
53
|
results["summary"]["invalid"]["count"] += retry_results["summary"]["invalid"]["count"]
|
|
49
|
-
results["summary"]["
|
|
54
|
+
results["summary"]["invalid"]["keys"].extend(retry_results["summary"]["invalid"]["keys"])
|
|
50
55
|
|
|
51
56
|
return results
|
|
@@ -2,7 +2,7 @@ import argparse
|
|
|
2
2
|
import asyncio
|
|
3
3
|
import json
|
|
4
4
|
import sys
|
|
5
|
-
from datetime import datetime, timedelta
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
from rich.console import Console
|
|
@@ -153,7 +153,7 @@ def cmd_report(args, config):
|
|
|
153
153
|
return
|
|
154
154
|
|
|
155
155
|
days = args.days or 7
|
|
156
|
-
cutoff = datetime.
|
|
156
|
+
cutoff = datetime.now(timezone.utc) - timedelta(days=days)
|
|
157
157
|
|
|
158
158
|
stats = {}
|
|
159
159
|
for key, info in data["keys"].items():
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import logging
|
|
3
|
+
import yaml
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
DEFAULT_CONFIG = {
|
|
9
|
+
"scan": {
|
|
10
|
+
"directories": ["./data/input"],
|
|
11
|
+
"recursive": True,
|
|
12
|
+
},
|
|
13
|
+
"check": {
|
|
14
|
+
"interval_hours": 6,
|
|
15
|
+
"timeout_seconds": 30,
|
|
16
|
+
"concurrency": 100,
|
|
17
|
+
"retry_failed": True,
|
|
18
|
+
"retry_count": 2,
|
|
19
|
+
},
|
|
20
|
+
"test": {
|
|
21
|
+
"token_test": True,
|
|
22
|
+
"token_auto_detect": True,
|
|
23
|
+
"token_steps": [1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576],
|
|
24
|
+
"token_max_manual": None,
|
|
25
|
+
"concurrency_test": True,
|
|
26
|
+
"concurrency_steps": [1, 5, 10, 20, 50, 100],
|
|
27
|
+
"concurrency_timeout_seconds": 120,
|
|
28
|
+
},
|
|
29
|
+
"storage": {
|
|
30
|
+
"keys_file": "./data/keys.json",
|
|
31
|
+
"check_results_file": "./data/check_results.json",
|
|
32
|
+
"test_results_file": "./data/test_results.json",
|
|
33
|
+
"logs_dir": "./data/logs",
|
|
34
|
+
},
|
|
35
|
+
"auth": {
|
|
36
|
+
"api_key": "",
|
|
37
|
+
},
|
|
38
|
+
"rate_limit": {
|
|
39
|
+
"requests_per_minute": 60,
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _deep_merge(base: dict, override: dict) -> dict:
|
|
45
|
+
result = base.copy()
|
|
46
|
+
for key, value in override.items():
|
|
47
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
48
|
+
result[key] = _deep_merge(result[key], value)
|
|
49
|
+
else:
|
|
50
|
+
result[key] = value
|
|
51
|
+
return result
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _validate_config(config: dict) -> dict:
|
|
55
|
+
"""Validate config values and warn about unknown keys."""
|
|
56
|
+
valid_keys = set(DEFAULT_CONFIG.keys())
|
|
57
|
+
unknown_keys = set(config.keys()) - valid_keys
|
|
58
|
+
if unknown_keys:
|
|
59
|
+
logger.warning(f"Unknown config keys (ignored): {unknown_keys}")
|
|
60
|
+
|
|
61
|
+
# Validate numeric values
|
|
62
|
+
check_config = config.get("check", {})
|
|
63
|
+
if check_config.get("concurrency", 100) < 1:
|
|
64
|
+
logger.warning("check.concurrency must be >= 1, using default")
|
|
65
|
+
check_config["concurrency"] = 100
|
|
66
|
+
if check_config.get("timeout_seconds", 30) < 1:
|
|
67
|
+
logger.warning("check.timeout_seconds must be >= 1, using default")
|
|
68
|
+
check_config["timeout_seconds"] = 30
|
|
69
|
+
|
|
70
|
+
rate_limit = config.get("rate_limit", {})
|
|
71
|
+
if rate_limit.get("requests_per_minute", 60) < 0:
|
|
72
|
+
logger.warning("rate_limit.requests_per_minute must be >= 0, using default")
|
|
73
|
+
rate_limit["requests_per_minute"] = 60
|
|
74
|
+
|
|
75
|
+
return config
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def load_config(path: str = "config.yaml") -> dict:
|
|
79
|
+
config_path = Path(path)
|
|
80
|
+
if config_path.exists():
|
|
81
|
+
try:
|
|
82
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
83
|
+
user_config = yaml.safe_load(f) or {}
|
|
84
|
+
if not isinstance(user_config, dict):
|
|
85
|
+
logger.warning(f"Config file {path} is not a dict, using defaults")
|
|
86
|
+
return copy.deepcopy(DEFAULT_CONFIG)
|
|
87
|
+
config = _deep_merge(DEFAULT_CONFIG, user_config)
|
|
88
|
+
return _validate_config(config)
|
|
89
|
+
except yaml.YAMLError as e:
|
|
90
|
+
logger.error(f"Failed to parse config file {path}: {e}")
|
|
91
|
+
return copy.deepcopy(DEFAULT_CONFIG)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.error(f"Failed to load config file {path}: {e}")
|
|
94
|
+
return copy.deepcopy(DEFAULT_CONFIG)
|
|
95
|
+
return copy.deepcopy(DEFAULT_CONFIG)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def save_config(config: dict, path: str = "config.yaml"):
|
|
99
|
+
try:
|
|
100
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
101
|
+
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.error(f"Failed to save config file {path}: {e}")
|