Metadata-Version: 2.4
Name: api-key-manager
Version: 4.1.0
Summary: Batch manage API keys for 45+ AI providers with CLI and Web interfaces
Author: Townrain
License: MIT
Project-URL: Homepage, https://github.com/Townrain/API-Key-Manager
Project-URL: Documentation, https://github.com/Townrain/API-Key-Manager#readme
Project-URL: Repository, https://github.com/Townrain/API-Key-Manager
Keywords: api-key,ai,openai,anthropic,google,deepseek
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.27.2
Requires-Dist: pyyaml>=6.0.2
Requires-Dist: rich>=13.9.0
Requires-Dist: fastapi>=0.115.0
Requires-Dist: uvicorn>=0.32.0
Requires-Dist: python-multipart>=0.0.9
Requires-Dist: cryptography>=42.0.0
Requires-Dist: jinja2>=3.0.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
Requires-Dist: ruff>=0.4.0; extra == "dev"

# API Key Manager

批量管理 45+ AI 服务商 API 密钥的 Python 工具，支持 CLI 和 Web 两种界面。

## 功能特性

- **批量导入** - 从 JSON 文件导入 API 密钥，自动去重
- **密钥验证** - 并发验证密钥有效性，支持 45+ AI 服务商
- **能力测试** - 测试 Token 上限和并发能力
- **模型筛选** - 按类型筛选模型（推理/视觉/联网/免费/嵌入/重排/工具）
- **智能检测** - 前缀匹配 + 模式匹配 + 错误签名匹配，三级提供商自动识别
- **自动发现** - 新增服务商只需创建 Python 文件，自动注册
- **可扩展架构** - 支持 YAML 配置自定义服务商，Web API 管理
- **Web 界面** - 赛博朋克风格的管理界面
- **代理支持** - 支持 HTTP/SOCKS 代理
- **加密存储** - AES-256-GCM 加密存储 API 密钥，随机盐值
- **安全防护** - 路径遍历防护、SSRF 防护、时序安全认证
- **API 文档** - Swagger UI 和 Redoc 自动文档
- **国际化** - 支持中英文错误信息
- **SDK 支持** - Python 和 TypeScript 客户端库
- **Webhook 通知** - 事件驱动的 Webhook 通知系统

## 系统架构

![系统架构流程图](docs/images/flowchart.png)


## 支持的 AI 服务商

### 国际

| 服务商 | 前缀 |
|--------|------|
| OpenAI | `sk-proj-` |
| Anthropic | `sk-ant-api03-` |
| Google Gemini | `AIza` |
| DeepSeek | `sk-` |
| Groq | `gsk_` |
| Mistral | `sk-` |
| Cohere | `sk-` |
| Perplexity | `pplx-` |
| Together AI | `sk-` |
| Replicate | `r8_` |
| Hugging Face | `hf_` |
| Fireworks | `fw_` |
| OpenRouter | `sk-or-v1-` |
| Grok (xAI) | `xai-` |
| Cerebras | `sk-` |
| NVIDIA | `sk-` |
| Hyperbolic | `sk-` |
| Poe | `sk-` |

### 中国

| 服务商 | 前缀 | 显示名 |
|--------|------|--------|
| 阿里百炼 | `sk-ws-` / `sk-` |
| 阿里百炼编程 | `sk-sp-` |
| ModelScope | `ms-` | 魔搭 |
| Zhipu GLM | `sk-` | 智谱 |
| Kimi | `sk-` | 月之暗面 |
| MiniMax | `sk-` | MiniMax |
| SiliconFlow | `sk-` | 硅基流动 |
| Baichuan | `sk-` | 百川 |
| Yi | `sk-` | 零一万物 |
| StepFun | `sk-` | 阶跃星辰 |
| Doubao | `sk-` | 豆包 |
| Infini | `sk-` | 无问芯穹 |
| MiMo | `sk-` | 小米 |
| Tencent Hunyuan | `sk-` | 腾讯混元 |
| CSTCloud | `sk-` | 中算云 |

### 新增服务商

| 服务商 | 说明 |
|--------|------|
| LongCat | 新增 |
| AI302 | 新增 |
| PPIO | 新增 |
| DMXAPI | 新增 |
| OCoolAI | 新增 |
| ZAI | 新增 |
| MiMo Plan | 计划版 |
| MiniMax Plan | 计划版 |
| DashScope Coding | 编程版 |
| Zhipu Coding | 编程版 |
| Kimi Coding | 编程版 |
| Infini Coding | 编程版 |

## 添加自定义服务商

### 方式一：Python 文件（推荐）

创建 `key_manager/providers/my_llm.py`：

```python
from .base import ProviderBase


class MyLlmProvider(ProviderBase):
    name = "my-llm"
    base_url = "https://api.my-llm.com/v1"
    check_endpoint = "/models"
    check_model = "my-model"
    display_name = "My LLM"
    key_prefixes = ["myllm-"]
    error_signatures = ["my-llm", "invalid api key"]
    website_url = "https://my-llm.com"
    docs_url = "https://docs.my-llm.com"

    def build_headers(self, key: str) -> dict:
        return {"Authorization": f"Bearer {key}"}
```

重启服务即可自动发现。

### 方式二：YAML 配置

在 `config.yaml` 中添加：

```yaml
providers:
  custom:
    - name: "my-llm"
      base_url: "https://api.my-llm.com/v1"
      check_endpoint: "/models"
      check_model: "my-model"
      display_name: "My LLM"
      key_prefixes: ["myllm-"]
      auth_type: bearer
```

### 方式三：Web API

```bash
# 添加服务商
curl -X POST http://localhost:18001/api/providers \
  -H "Content-Type: application/json" \
  -d '{"name":"my-llm","base_url":"https://api.my-llm.com/v1","check_endpoint":"/models"}'

# 列出所有服务商
curl http://localhost:18001/api/providers

# 删除服务商
curl -X DELETE http://localhost:18001/api/providers/my-llm
```

## 快速开始

### 安装

```bash
# 克隆项目
git clone https://github.com/Townrain/API-Key-Manager.git
cd key

# 安装依赖
pip install -r requirements.txt

# 安装开发依赖（含测试工具）
pip install -e ".[dev]"
```

### CLI 使用

```bash
# 导入密钥
python main.py import --file data/input/example.json
python main.py import --dir ./data/input

# 验证密钥
python main.py check
python main.py check --provider openai
python main.py check --key sk-xxx

# 测试密钥
python main.py test
python main.py test --skip-token
python main.py test --skip-concurrency

# 列出密钥
python main.py list --provider anthropic --status valid
python main.py list --status invalid

# 生成报告
python main.py report --days 7
```

### Web 界面

```bash
# 启动 Web 服务器
python web.py

# 访问以下地址：
# 主界面：http://localhost:18001
# API 文档：http://localhost:18001/docs
# Redoc：http://localhost:18001/redoc
```

## 安全特性

### 加密存储

API 密钥默认使用 AES-256-GCM 加密存储，每次加密使用随机盐值：

```bash
# 设置加密密钥（环境变量）
set KEY_MANAGER_SECRET=your-secret-key

# 启动服务
python web.py
```

加密后的 `keys.json` 格式：
```json
{
  "encrypted": true,
  "salt": "base64-encoded-random-salt",
  "nonce": "base64-encoded-nonce",
  "data": "base64-encoded-ciphertext"
}
```

### 安全防护

- **路径遍历防护** - 导入端点验证路径在允许目录内
- **SSRF 防护** - `custom_base_url` 验证域名白名单，阻止私有 IP，已接入 `check/single` 和 `balance` 端点
- **时序安全认证** - 使用 `hmac.compare_digest()` 防止时序攻击
- **认证警告** - 未配置 API Key 时启动警告
- **密钥掩码** - API 响应中只返回 `key_masked`，不暴露完整密钥
- **Webhook 安全** - Webhook 端点使用正确的 API 方法，防止运行时错误

### API 认证

```bash
# 设置 API Key（环境变量）
set KEY_MANAGER_API_KEY=your-api-key

# 或在 config.yaml 中配置
# auth:
#   api_key: "your-api-key"
```

## 提供商智能检测

### 检测策略

系统采用**全并发探测**策略，自动识别 45+ 个 AI 服务商的 API 密钥。

### 检测流程（重要！）

```
1. 前缀匹配 - 检查唯一前缀（如 sk-proj- → OpenAI，AIza → Google）
2. 格式匹配 - 检查特殊格式（如智谱的 {id}.{secret} 格式）
3. 全并发探测 - 同时向所有服务商发送请求
4. 签名匹配 - 如果无200响应，通过错误响应体签名识别服务商
```

### ⚠️ 关键概念：/v1/models vs /chat/completions

**这两个端点的作用完全不同，不能混用！**

| 端点 | 作用 | 返回200的含义 |
|------|------|--------------|
| `/v1/models` | 获取模型列表 | 只表示可以获取模型列表，**不能**判断密钥是否有效 |
| `/chat/completions` | 调用模型 | 表示密钥对该提供商有效，**这才是判断提供商的依据** |

**常见错误：**
- ❌ 用 `/v1/models` 返回200来判断提供商 → 错误！
- ✅ 用 `/chat/completions` 返回200来判断提供商 → 正确！

### 检测流程详解

#### Step 1: 前缀匹配

检查密钥是否匹配唯一前缀：

```python
# 唯一前缀 → 直接返回
"sk-proj-" → OpenAI
"sk-ant-api03-" → Anthropic
"AIza" → Google
"ms-" → ModelScope

# 共享前缀 → 需要进一步探测
"sk-" → 20+ 提供商（DeepSeek, OpenAI, 等）
```

#### Step 2: 格式匹配

检查密钥是否匹配特殊格式：

```python
# 智谱/Z.AI 格式：{id}.{secret}
50bcde33b8774aa8a2cc1bd6d39444ae.ifriyNWRLStzpLEs
→ 返回 ["zhipu", "zai"]
```

#### Step 3: 全并发探测（核心逻辑）

**重要：这一步用 `/chat/completions` 验证，不是 `/v1/models`！**

```python
async def detect_provider(client, key):
    # Step 3.1: 获取所有提供商的模型列表（/v1/models）
    # 这一步只是为了获取模型列表，不能判断提供商
    models = {}
    for name, provider in PROVIDERS.items():
        resp = await client.get(f"{provider.base_url}/v1/models")
        if resp.status_code == 200:
            models[name] = extract_models(resp.json())
    
    # Step 3.2: 并发测试所有（提供商，模型）对的 /chat/completions
    # 这一步才是判断提供商的依据！
    tasks = []
    for name, model_list in models.items():
        for model in model_list:
            tasks.append(try_chat_completion(name, model))
    
    # 第一个返回200的提供商胜出
    for coro in asyncio.as_completed(tasks):
        name, valid, status_code = await coro
        if valid:  # /chat/completions 返回200
            return name
```

#### Step 4: 签名匹配

如果所有 `/chat/completions` 都失败，通过错误响应体识别提供商：

```python
# 错误签名匹配
UNIQUE_SIGNATURES = {
    "dashscope": ["model-studio", "modelstudio", "apikey-error"],
    "anthropic": ["request not allowed", "anthropic", "x-api-key"],
    "openai": ["platform.openai.com"],
    # ... 更多签名
}

# 匹配阈值：至少2个签名匹配（200分）才返回结果
if best_score >= 200:
    return best_name
```

### 免费模型的重要性

**某些提供商（如 OpenCode Zen）提供免费模型，这些模型对检测非常重要！**

```python
# OpenCode Zen 的模型列表：
# - claude-fable-5 (付费)
# - deepseek-v4-flash-free (免费) ✅
# - mimo-v2.5-free (免费) ✅
# - ...

# 如果密钥只对免费模型有效：
# - /chat/completions + claude-fable-5 → 401 (付费模型，无权限)
# - /chat/completions + deepseek-v4-flash-free → 200 (免费模型，有权限) ✅
```

**检测逻辑会并发测试所有模型，包括免费模型。只要有一个模型返回200，提供商就会被正确识别。**

### 检测优先级

| 优先级 | 方法 | 端点 | 说明 |
|--------|------|------|------|
| 1 | 前缀匹配 | - | 唯一前缀，如 `sk-proj-` → OpenAI |
| 2 | 格式匹配 | - | 特殊格式，如 `{id}.{secret}` → 智谱 |
| 3 | 全并发探测 | `/chat/completions` | 第一个返回200的提供商胜出 |
| 4 | 签名匹配 | - | 通过错误响应体识别，需至少2个签名匹配 |

### 常见问题

#### Q: 为什么我的密钥被错误识别为其他提供商？

**A: 可能是因为：**
1. 密钥使用共享前缀（如 `sk-`），需要通过 `/chat/completions` 验证
2. 密钥只对免费模型有效，但检测逻辑没有测试免费模型
3. 提供商的 `/v1/models` 返回200，但 `/chat/completions` 返回401

#### Q: 为什么 `/v1/models` 返回200，但检测失败？

**A: 因为 `/v1/models` 返回200不能判断提供商！**
- `/v1/models` 只表示可以获取模型列表
- `/chat/completions` 返回200才能判断提供商

#### Q: 如何确保检测正确？

**A: 确保以下几点：**
1. 密钥对至少一个模型有调用权限（包括免费模型）
2. 提供商的 `/chat/completions` 端点正常工作
3. 密钥没有过期或被撤销

### 检测流程图

```
输入: API Key
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 1: 前缀匹配                                            │
│   - 唯一前缀 → 直接返回（如 sk-proj- → OpenAI）              │
│   - 共享前缀 → 继续下一步                                    │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 2: 格式匹配                                            │
│   - 智谱格式 {id}.{secret} → 返回 ["zhipu", "zai"]          │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 3: 全并发探测（用 /chat/completions 验证）               │
│                                                             │
│   3.1 获取所有提供商的模型列表（/v1/models）                  │
│       - 这一步只是为了获取模型，不能判断提供商                 │
│                                                             │
│   3.2 并发测试所有（提供商，模型）对的 /chat/completions       │
│       - 第一个返回200的提供商胜出                             │
│       - 包括免费模型和付费模型                                │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│ Step 4: 签名匹配（如果 /chat/completions 都失败）             │
│   - 通过错误响应体中的关键词识别提供商                        │
│   - 需要至少2个签名匹配（200分）才返回结果                    │
└─────────────────────────────────────────────────────────────┘
```

### 代码实现

检测逻辑在 `key_manager/detector.py` 中实现：

```python
async def detect_provider(client, key: str, suspected_provider: str = None) -> str:
    """Detect provider by concurrently probing ALL providers with multiple models.
    
    Strategy:
    1. If suspected_provider given, try it first
    2. If key matches unique pattern, try that provider
    3. Otherwise, concurrently probe ALL providers with their top 5 models
    4. First provider returning 200 wins
    """
    # Step 1: If suspected provider, try it first
    if suspected_provider:
        provider_name = suspected_provider.lower()
        if provider_name in PROVIDERS:
            return provider_name
    
    # Step 2: Try format matching (e.g., Zhipu's {id}.{secret})
    format_candidates = detect_by_format(key)
    if format_candidates:
        for name in format_candidates:
            if name in PROVIDERS:
                return name
    
    # Step 3: Try prefix matching
    prefix_candidates = detect_by_prefix(key)
    if prefix_candidates:
        if len(prefix_candidates) == 1:
            return prefix_candidates[0]
        # If multiple candidates, continue to Step 4
    
    # Step 4: Concurrently probe ALL providers
    # First, get models from all providers concurrently
    model_tasks = [get_provider_models(name, provider) for name, provider in PROVIDERS.items()]
    model_results = await asyncio.gather(*model_tasks)
    
    # Build tasks: (provider_name, model) pairs
    tasks = []
    for name, models, is_valid in model_results:
        if models:
            for model in models:
                tasks.append((name, model))
    
    # Concurrently check all (provider, model) pairs
    all_tasks = [try_model(name, model) for name, model in tasks]
    
    # First provider returning 200 wins
    for coro in asyncio.as_completed(all_tasks):
        name, valid, body, status_code = await coro
        if valid:
            return name
    
    # Step 5: Signature matching
    return match_by_signature(error_bodies)
```

## 错误信息简化

系统会自动将服务商返回的原始错误信息简化为用户友好的提示：

| 原始错误信息 | 简化后 |
|-------------|--------|
| `Access denied, please make sure your account is in good standing...` | 余额不足 |
| `Invalid API Key` | Key 无效 |
| `Authentication fails` | 认证失败 |
| `Token expired` | Key 已过期 |
| `Rate limit exceeded` | 请求过于频繁 |
| `Account suspended` | 账号被封禁 |
| `Access denied` | 无权限访问 |
| `Model does not exist` | 模型不存在 |

错误信息简化在 `base.py` 的 `simplify_error()` 函数中实现，支持：

- 基于状态码的简化（401 → Key 无效，402 → 余额不足，429 → 请求过于频繁）
- 基于关键词的模式匹配（authentication、expired、rate limit 等）
- 长错误信息截断（超过100字符时截断并添加省略号）

## 模型检测

### 模型列表来源

系统从 Cherry Studio 同步模型数据，生成 `models_registry.py` 文件，包含每个服务商的静态模型列表：

```python
PROVIDER_MODELS = {
    "openai": ["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo", ...],
    "anthropic": ["claude-3-opus", "claude-3-sonnet", ...],
    "dashscope": ["qwen-turbo", "qwen-plus", "qwen-max", ...],
    "siliconflow": ["Qwen/Qwen2.5-7B-Instruct", ...],
    # ... 45+ 服务商
}
```

### 模型检测流程

当用户点击「检测可用模型」时：

1. 获取服务商的模型列表（优先使用 API 返回，回退到静态列表）
2. 并发检测每个模型的可用性（batch_size 动态调整）
3. 每个模型发送一个最小请求（`POST /chat/completions`，`max_tokens: 5`）
4. 返回200的模型标记为可用，其他标记为失败
5. 失败的模型会串行重试（最多2次）

```python
# 模型检测逻辑
async def check_model(http, model):
    resp = await client.post(
        f"{provider.get_base_url()}/chat/completions",
        json={"model": model, "messages": [...], "max_tokens": 5}
    )
    return model, 200 if resp.status_code == 200 else resp.status_code

# 动态并发控制
batch_size = 5  # 初始并发数
for i in range(0, len(models), batch_size):
    batch = models[i:i+batch_size]
    results = await asyncio.gather(*[check_model(http, m) for m in batch])
    
    # 全部成功 → 并发数 +1
    if all_success:
        batch_size += 1
    # 有失败 → 保持当前并发数
    
    # 失败模型串行重试
    for model in failed_models:
        _, code = await check_model(http, model)
        if code == 200:
            # 重试成功
```

### 模型能力检测

系统支持按类型筛选模型：

| 类型 | 说明 | 筛选方法 |
|------|------|----------|
| 视觉模型 | 支持图像输入 | `is_vision_model()` |
| 工具模型 | 支持函数调用 | `is_tool_model()` |
| 推理模型 | 支持思维链 | `is_reasoning_model()` |
| 联网模型 | 支持网络搜索 | `is_websearch_model()` |
| 免费模型 | 免费额度 | `is_free_model()` |
| 嵌入模型 | 文本嵌入 | `is_embedding_model()` |
| 重排模型 | 搜索重排 | `is_rerank_model()` |

能力数据从 Cherry Studio 同步，存储在 `data/model_capabilities.json` 中。

## 项目结构
```
key/
├── key_manager/                    # 核心包
│   ├── __init__.py                 # 包导出
│   ├── cli.py                      # CLI 入口
│   ├── web/                        # Web 模块 (v4.0.0 重构)
│   │   ├── __init__.py             # 包导出
│   │   ├── _app.py                 # FastAPI 应用入口
│   │   ├── middleware.py           # 中间件和错误处理器
│   │   ├── progress.py             # ProgressTracker和SSE辅助
│   │   └── routes/                 # 路由模块
│   │       ├── keys.py             # 密钥管理路由
│   │       ├── check.py            # 验证路由
│   │       ├── test.py             # 测试路由
│   │       ├── balance.py          # 余额查询路由
│   │       ├── models.py           # 模型路由
│   │       ├── providers.py        # 提供商路由
│   │       ├── stats.py            # 统计路由
│   │       └── misc.py             # 杂项路由
│   ├── config.py                   # 配置加载
│   ├── storage.py                  # AES-256-GCM 加密存储
│   ├── errors.py                   # 结构化错误码
│   ├── api_models.py               # Pydantic 模型
│   ├── parser.py                   # JSON 导入 + 路径验证
│   ├── detector.py                 # 智能提供商检测
│   ├── validator.py                # 并发验证引擎
│   ├── checker.py                  # 重试包装器
│   ├── tester.py                   # 能力测试
│   ├── ssrf.py                     # SSRF 防护
│   ├── logger.py                   # 日志系统
│   ├── proxy.py                    # 代理检测
│   ├── webhook.py                  # Webhook 通知
│   ├── i18n.py                     # 国际化
│   ├── model_capabilities.py       # 模型能力检测
│   └── providers/                  # 45+ 提供商实现
│       ├── __init__.py             # 注册表
│       ├── base.py                 # ABC 接口
│       ├── openai.py               # OpenAI
│       ├── anthropic.py            # Anthropic
│       └── ...                     # 更多提供商
├── static/                         # 前端静态资源 (v4.0.0 重构)
│   ├── css/
│   │   ├── tokens.css              # CSS 变量
│   │   ├── base.css                # 基础样式
│   │   ├── components.css          # 组件入口 (@import)
│   │   ├── components/             # 独立组件样式
│   │   │   ├── button.css          # 按钮
│   │   │   ├── form.css            # 表单控件
│   │   │   ├── card.css            # 卡片、操作区
│   │   │   ├── table.css           # 表格、徽章、分页
│   │   │   ├── stat.css            # 统计卡片
│   │   │   ├── nav.css             # 导航标签
│   │   │   └── overlay.css         # 进度、Toast、日志
│   │   ├── modals.css              # 模态框样式
│   │   └── animations.css          # 动画
│   └── js/
│       ├── state.js                # 全局状态
│       ├── utils.js                # 工具函数
│       ├── api/                    # API 模块
│       │   ├── client.js           # 通用 fetch 逻辑
│       │   ├── index.js            # 重新导出
│       │   ├── stats.js            # 统计 API
│       │   ├── keys.js             # 密钥 API
│       │   ├── check.js            # 验证 API
│       │   ├── test.js             # 测试 API
│       │   ├── balance.js          # 余额 API
│       │   ├── models.js           # 模型 API
│       │   ├── providers.js        # 提供商 API
│       │   └── misc.js             # 杂项 API
│       ├── toast.js                # Toast 通知
│       ├── progress.js             # 进度覆盖层
│       ├── confirm.js              # 确认模态框
│       ├── keys-table.js           # 表格渲染
│       ├── providers.js            # 提供商网格
│       ├── batch.js                # 批量结果
│       ├── modals.js               # 模态框
│       ├── model-detect.js         # 模型检测
│       └── init.js                 # 入口点
├── templates/
│   └── index.html                  # Web UI 入口 (467行)
├── tests/                          # 测试套件
│   ├── conftest.py                 # 共享 fixtures 和帮助函数
│   ├── test_detector.py            # 提供商检测测试
│   ├── test_parser.py              # 解析器测试
│   ├── test_validator.py           # 验证器测试
│   ├── test_checker.py             # 检查器测试
│   ├── test_providers.py           # 提供商合约测试
│   ├── test_security.py            # 安全回归测试
│   ├── test_storage.py             # 加密存储测试
│   ├── test_errors.py              # 错误系统测试
│   ├── test_i18n.py                # 国际化测试
│   ├── test_e2e.py                 # 端到端测试
│   ├── test_webhook.py             # Webhook 测试
│   ├── test_bug_fixes.py           # Bug 修复回归测试
│   └── test_provider_refactoring.py # 提供商重构测试
├── sdk/                            # SDK
│   ├── python/                     # Python SDK
│   └── typescript/                 # TypeScript SDK
├── config.yaml                     # 配置文件
├── pyproject.toml                  # 项目配置
├── main.py                         # CLI 入口
└── web.py                          # Web 入口
```

## 配置说明

编辑 `config.yaml`：

```yaml
# 代理设置
proxy: "http://127.0.0.1:7890"  # 或 socks5://127.0.0.1:7890

# 验证设置
check:
  concurrency: 100              # 并发数
  timeout_seconds: 30           # 超时时间
  retry_failed: true            # 失败重试
  retry_count: 2                # 重试次数

# 测试设置
test:
  token_steps:
    - 1024
    - 4096
    - 16384
    - 65536
  concurrency_steps:
    - 1
    - 5
    - 10
    - 20

# 认证设置
auth:
  api_key: "your-secret-api-key"  # API 认证

# 速率限制
rate_limit:
  requests_per_minute: 60
```

## API 端点

| 方法 | 端点 | 说明 |
|------|------|------|
| GET | `/api/keys` | 获取密钥列表 |
| GET | `/api/keys/export` | 导出有效密钥 |
| POST | `/api/import` | 导入密钥 |
| POST | `/api/import/upload` | 上传 JSON 文件导入 |
| POST | `/api/check/single` | 验证单个密钥 |
| POST | `/api/check/batch` | 批量验证 |
| POST | `/api/test/single` | 测试单个密钥 |
| POST | `/api/test/token` | 测试 Token 上限 |
| POST | `/api/test/concurrency` | 测试并发能力 |
| GET | `/api/models` | 获取模型列表 |
| POST | `/api/models/check` | 检测可用模型（SSE 流） |
| GET | `/api/providers` | 获取服务商列表 |
| GET | `/api/stats` | 获取统计信息 |
| GET | `/api/logs` | 获取日志 |
| POST | `/api/webhooks` | 创建 Webhook |
| GET | `/docs` | Swagger UI 文档 |
| GET | `/redoc` | Redoc 文档 |

## Webhook 使用

### 支持的事件类型

| 事件 | 说明 |
|------|------|
| `key.imported` | 密钥导入完成 |
| `key.checked` | 密钥验证完成 |
| `key.tested` | 密钥测试完成 |
| `key.deleted` | 密钥删除 |
| `batch.check.completed` | 批量验证完成 |
| `batch.test.completed` | 批量测试完成 |
| `error.occurred` | 发生错误 |

### 配置 Webhook

```yaml
webhooks:
  - url: "https://example.com/webhook"
    events:
      - "key.imported"
      - "key.checked"
    secret: "your-webhook-secret"  # HMAC-SHA256 签名
    active: true
    max_retries: 3
```

### 签名验证

```python
import hmac
import hashlib
import json

def verify_signature(payload, secret, signature):
    body = json.dumps(payload, separators=(",", ":"), sort_keys=True)
    expected = hmac.new(
        secret.encode("utf-8"),
        body.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()
    return signature == f"sha256={expected}"
```

## 测试

```bash
# 运行所有测试
python -m pytest tests/ -v

# 运行特定测试
python -m pytest tests/test_security.py -v
python -m pytest tests/test_detector.py -v

# 运行测试并查看覆盖率
python -m pytest tests/ --cov=key_manager --cov-report=term-missing
```

### 测试覆盖

| 模块 | 测试文件 | 测试数 |
|------|---------|--------|
| 提供商检测 | `test_detector.py` | 44 |
| 密钥解析 | `test_parser.py` | 24 |
| 验证器 | `test_validator.py` | 13 |
| 检查器 | `test_checker.py` | 4 |
| 提供商合约 | `test_providers.py` | 220 |
| 安全回归 | `test_security.py` | 21 |
| 加密存储 | `test_storage.py` | 26 |
| 错误系统 | `test_errors.py` | 28 |
| 国际化 | `test_i18n.py` | 37 |
| 端到端 | `test_e2e.py` | 18 |
| Webhook | `test_webhook.py` | 35 |
| OpenAPI | `test_openapi.py` | 26 |
| 代理检测 | `test_proxy.py` | 19 |
| 日志系统 | `test_logger.py` | 21 |
| 能力测试 | `test_tester.py` | 11 |
| 核心门面 | `test_core.py` | 22 |
| Bug 修复 | `test_bug_fixes.py` | 15 |
| 提供商重构 | `test_provider_refactoring.py` | 32 |

**总测试数**: 728+ | **覆盖率**: 80%
## SDK 使用

### Python SDK

```bash
cd sdk/python
pip install -e .
```

```python
from key_manager_sdk import KeyManagerClient

client = KeyManagerClient(base_url="http://localhost:18001")

# 获取密钥列表
keys = client.keys()

# 验证单个密钥
result = client.check_single(key="sk-xxx", provider="openai")
```

### TypeScript SDK

```bash
cd sdk/typescript
npm install
```

```typescript
npm install @api-key-manager/sdk

const client = new KeyManagerClient({ baseUrl: 'http://localhost:18001' });

// 获取密钥列表
const keys = await client.keys();

// 验证单个密钥
const result = await client.checkSingle({ key: 'sk-xxx', provider: 'openai' });
```

## 依赖

- Python 3.10+
- httpx - 异步 HTTP 客户端
- FastAPI - Web 框架
- uvicorn - ASGI 服务器
- PyYAML - 配置解析
- Rich - 终端美化
- cryptography - 加密存储
- pydantic - 数据验证

## 已知问题和限制

### 1. 中转站服务商误识别

由于某些中转站服务商（如 Z.AI、DMXAPI、OCoolAI 等）使用与原厂相同的 API 端点和模型，当密钥无效时，错误响应可能包含原厂的签名关键词，导致误识别。

例如：一个硅基流动的密钥，如果被发送到阿里百炼的端点，阿里百炼会返回包含 "model-studio" 和 "apikey-error" 的错误响应，这可能导致系统误认为该密钥是阿里百炼的。

**解决方案**：系统要求至少2个签名匹配（200分）才返回识别结果，以减少误报。

### 2. 签名匹配的局限性

签名匹配依赖于服务商返回的错误响应体中的关键词。如果服务商更改了错误消息格式，签名可能失效。

**建议**：定期运行 `verify_signatures.py` 脚本验证签名的有效性。

### 3. 并发检测的超时问题

全并发探测时，某些服务商可能响应较慢（超过10秒超时）。这可能导致有效的服务商被跳过。

**解决方案**：系统会对失败的模型进行串行重试（最多2次）。

### 4. 智谱/Z.AI 密钥的双重检测

智谱和 Z.AI 使用相同的 GLM 模型和 API 格式，但使用不同的 Base URL：

- 智谱：`https://open.bigmodel.cn/api/paas/v4`
- Z.AI：`https://api.z.ai/api/paas/v4`

同一个密钥可能在两个平台都能工作，系统会返回第一个响应200的服务商。

### 5. 模型列表的时效性

模型列表从 Cherry Studio 同步，每日更新一次。新发布的模型可能需要等待同步后才能被检测到。


## 更新日志


### v4.1.0 (2026-06-22)

- **Web 模块技术债务优化**:
  - 修复 `middleware.py` 中的死代码（`now - 300.0` 表达式未使用）
  - 提取 `build_chat_url()` 工具函数，消除 3 处重复的 Chat URL 构造逻辑
  - 提取 `resolve_provider()` 工具函数，消除 5 处重复的提供商检测+验证模式
  - 修复 `keys.py` 中弃用的 `datetime.utcnow()`，改用 `datetime.now(timezone.utc)`
  - 为所有静默异常块添加 `logger.debug()` 日志记录
  - 将内联导入（`import re as _re`、`import time as _time`）移至模块顶部
  - 统一 `test.py` 模型端点的错误响应格式，使用 `ErrorResponse` 替代原始 dict
  - 删除 `_app.py` 中的重复注释
  - 代码行数减少 ~110 行，新增 `web/utils.py` 共享工具模块

- **操作日志清理功能**:
  - 新增 `DELETE /api/logs` API 端点，支持清理当天或指定日期的主日志
  - 新增 `ProjectLogger.clear_main_log()` 方法

- **测试验证**:
  - 全量测试通过：682 passed, 1 skipped
  - 覆盖率：78.40%（超过 60% 要求）

- **前端清理优化**:
  - CSS: 添加缺失的 `--neon-cyan-dim` 变量，新增 `.btn-sm` `.btn-danger` `.url-input` `.toolbar-divider` 工具类
  - CSS: Confirm 模态框和添加服务商表单的内联样式提取为 CSS 类
  - CSS: 签名报告样式拆分到 `components/signature-report.css`
  - CSS: 内联 `style=` 从 71 减少到 39，`onfocus`/`onblur` 从 10 减少到 0
  - JS: 提取 `selectCustomOption`、`toggleCustomSelect`、`toggleLogs` 到 `ui-helpers.js`
  - JS: 新增 `clearLogs()` 函数，带确认对话框
  - HTML: `index.html` 从 531 行精简到 508 行

### v4.0.0 (2026-06-21)

- **前端模块化重构**: 将单体 `index.html` (4725行) 重构为模块化结构:
  - CSS 拆分: 5个主题文件 + 7个组件文件
    - `tokens.css` — CSS 变量
    - `base.css` — 基础样式、布局、响应式
    - `components.css` — 组件入口 (@import)
    - `components/` — 7个独立组件 (button, form, card, table, stat, nav, overlay)
    - `modals.css` — 模态框样式
    - `animations.css` — 动画和过渡
  - JS 模块化: 12个ES模块
    - `state.js` — 全局状态管理
    - `utils.js` — 纯工具函数
    - `api/` — 9个API模块 (client, stats, keys, check, test, balance, models, providers, misc)
    - UI模块: toast, progress, confirm, keys-table, providers, batch, modals, model-detect
    - `init.js` — 入口点和事件绑定
  - `index.html` 精简至 467行
- **API模块拆分**: 按后端路由结构拆分前端API模块:
  - `api/client.js` — 通用fetch逻辑 (safeFetch)
  - `api/index.js` — 重新导出所有函数
  - 每个路由模块对应独立文件
- **静态文件服务**: 添加 `StaticFiles` 中间件
- **错误处理增强**: loadKeys/loadStats 添加 try-catch 错误处理
- **ES模块修复**: 修复 State 对象只读绑定问题

- **Web模块重构**: 将单体 `web.py` (2094行) 重构为模块化包结构:
  - `web/_app.py` — 应用入口 (239行)
  - `web/middleware.py` — 中间件和错误处理器
  - `web/progress.py` — ProgressTracker和SSE辅助
  - `web/routes/` — 8个路由模块 (keys, check, test, balance, models, providers, stats, misc)
- **测试套件重构**:
  - 合并补充测试文件 (test_parser_supplement, test_validator_supplement, test_ssrf_supplement)
  - 删除重复测试文件 (test_web_fixes)
  - 修复 mock 路径以匹配新的 web 模块结构
  - 提取共享 fixtures 和帮助函数到 conftest.py
  - 移除冗余 @pytest.mark.asyncio 装饰器
  - 修复速率限制中间件的测试隔离问题
- **代码去重**: 消除 `api_check_single` 中的重复模型检查逻辑
- **调试系统清理**: 移除 `sys.path.insert` hack，使用干净的可选导入模式
- **重复路由修复**: 合并重复的 `GET /api/providers` 路由
- **日志显示修复**: 修复日志显示为 `[object Object]` 的问题
- **可扩展性**: 在 `routes/__init__.py` 中添加新路由、中间件和错误处理器的扩展指南

- **提供商系统重构**:
  - 删除 `check_model` 属性，改为先从 API 获取模型列表再测试
  - 删除所有提供商中的冗余方法（build_headers, get_models, test_token_limit, test_concurrency, _probe, check_real）
  - 标准提供商从 ~77 行减少到 ~5-8 行
  - 总共减少 ~2,400 行冗余代码
  - 添加 `chat_endpoint` 属性，自动从 `check_endpoint` 推导 chat completions 端点

- **检测逻辑修复**:
  - 修复检测逻辑不再因为 `/v1/models` 返回 200 就立即返回提供商
  - `/v1/models` 只用于获取模型列表，不能判断提供商
  - `/chat/completions` 返回 200 才能判断提供商
  - 添加前缀匹配逻辑，支持共享前缀（如 `sk-`）的提供商检测
  - 免费模型对检测非常重要，检测逻辑会测试所有模型包括免费模型

- **文档更新**:
  - 更新 README.md 的"提供商智能检测"部分，添加详细的检测流程说明
  - 更新 docs/DEVELOPMENT.md 的"检测系统"部分
  - 更新 docs/PROVIDER_REFACTORING_PLAN.md，添加"检测逻辑详解"部分
  - 在 detector.py 中添加详细注释，说明 `/v1/models` 和 `/chat/completions` 的区别

- **Bug 修复与改进**:
  - 修复 SDK 文档方法名与实际不符（`get_keys()` → `keys()` 等）
  - 修复删除密钥时报错 "No passphrase found"，新增自动生成加密密钥功能
  - 修复签名验证报告端点 404 错误，改为动态生成报告
  - 修复添加自定义服务商按钮缺失，新增前端 UI
### v3.2.0 (2026-06-19)

- **模型检测按模型测试**: 支持在模型检测模态框中直接测试Token限制和并发能力
- **提供商管理UI**: 添加前端UI用于添加/管理自定义提供商
- **移除check_model**: 从ProviderBase中移除check_model属性，提供商必须从API获取模型
- **并发测试API改进**: 改进错误处理，提供详细错误信息
- **提供商Token测试修复**: 修复提供商使用错误模型或错误API端点的问题
- **余额API修复**: 修复支持余额查询的服务商的余额检测


### v3.1.0 (2026-06-19)

- **提供商自动发现**: 使用pkgutil自动发现替代47个显式导入
- **提供商元数据属性**: 所有提供商声明display_name、key_prefixes等类属性
- **默认实现**: test_token_limit、test_concurrency等方法现在是具体方法
- **YAML配置支持**: 可在config.yaml中定义自定义提供商
- **提供商Web API**: 添加/api/providers CRUD端点
- **代码精简**: 39个标准提供商从~100行减少到~15行


### v3.0.0 (2026-06-15)

- **精确匹配修复**: 能力检测从子串匹配改为 ^$ 锚定精确匹配，修复 o1 误匹配 o1-mini 等问题
- **API Bug 修复**: 修复 /api/stats/chart 运行时崩溃、/api/keys 和 /api/keys/export 丢失 key 字段
- **测试补充**: 新增 8 个端点的测试（test/token, test/concurrency, models/capabilities, models/check, progress/stream 等）
- **前端缓存优化**: 模型类型切换使用本地缓存过滤，不再重复请求 API
- **Cherry Studio 同步工作流修复**: 修复 GitHub Actions 路径错误和验证脚本
- **Autofill 样式修复**: 修复浏览器 autofill 导致输入框底色变白

### v2.2.2 (2026-06-14)

- **模型检测二级窗口**: 新增模型检测弹窗，支持搜索、类型筛选、全选、批量检测
- **模型类型图标**: 显示模型能力图标（视觉/推理/联网/工具/嵌入/重排/免费）
- **模型能力数据更新**: 从 Cherry Studio 同步最新模型能力数据（3184 条记录）
- **检测逻辑优化**: DeepSeek 欠费密钥正确识别，不再误判为其他服务商
- **指定模型检测**: 自动检测服务商时也支持指定模型检测

### v2.2.1 (2026-06-14)

- **检测逻辑修复**: 自动识别后必须调用 check() 验证 key 有效性
- **签名匹配修复**: 状态码不再硬编码 401，使用实际状态码
- **不可靠服务商**: ppio、nvidia、modelscope 不参与加分
- **模型选择器**: 新增模型下拉框，支持选择具体模型检测
- **Cherry Studio 同步**: 修复 ownedBy 映射（alibaba→dashscope 等）
- **测试补充**: 新增 test_parser_supplement、test_ssrf_supplement、test_validator_supplement
### v2.2.0 (2026-06-14)

- **检测逻辑重构**: 添加三步检测逻辑（/v1/models → 并发测试）
- **URL 修复**: 从 check_endpoint 提取版本路径，修复 OpenCode 等服务商 404 问题
- **并发优化**: /v1/models 和 chat/completions 都并发调用
- **超时控制**: 所有网络请求 5 秒超时
- **模型同步**: 从 Cherry Studio 同步模型数据，支持 ownedBy 映射
- **新增服务商**: OpenCode Go、OpenCode Zen

### v2.1.2 (2026-06-11)

- **Bug 修复**: 修复 `KeyManager.detect_provider()` 调用异步函数未 await 的问题
- **测试扩展**: 提供商合约测试扩展至全部 44 个服务商
- **测试新增**: 新增 `test_proxy.py`、`test_logger.py`、`test_tester.py`、`test_core.py`
- **覆盖率提升**: 测试覆盖率从 74% 提升至 88%

### v2.1.1

- 初始发布版本

## 许可证

MIT License
