Files
claude-code/docs/jira/MULTI-AUTH-DESIGN.md
unraid 8945f08708 feat: integrate fork work onto upstream main (squashed)
Squash-merge of feat/autofix-pr-test (69 commits) onto upstream/main
with -X ours strategy (upstream as authoritative for content conflicts).

Key features brought in from fork:
- LocalMemoryRecall + VaultHttpFetch tools (end-to-end wired)
- /local-memory, /local-vault, /memory-stores, /skill-store interactive panels
- /agents-platform, /schedule, /vault command scaffolding
- /login: switch / replace / remove of workspace API key
- statusline refactor (built-in status row, /statusline as info command)
- autofix-pr command + workflow

Conflict resolutions (upstream-wins):
- 10 .js command stubs kept from upstream (alongside fork's .ts implementations)
- src/components/BuiltinStatusLine.tsx accepted upstream's deletion
  (fork's wire-up references in StatusLine.tsx will be cleaned up next)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:58:26 +08:00

312 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 多 Auth 模式设计Workspace API key + 第三方 + 订阅 OAuth
**日期**2026-05-04
**目标**:让被隐藏的 `/agents-platform` `/vault` `/memory-stores` 命令在用户**配置 workspace API key** 后启用;同时让 fork 支持**第三方 API provider**(如 Cerebras / Groq / 阿里通义 / 自建 OpenAI 兼容 endpoint通过同一选择器接入。
---
## 1. Fork 现状盘点(不要从零起)
### 已有基础设施
| 模块 | 路径 | 功能 |
|---|---|---|
| 7 个 provider 流适配器 | `src/services/api/{claude,bedrockClient,gemini,grok,openai,...}.ts` | firstParty / bedrock / vertex / foundry / openai / gemini / grokCLAUDE.md 已记录)|
| Provider 选择器 | `src/utils/model/providers.ts` | 优先级modelType > 环境变量 > 默认 firstParty |
| API key auth 识别 | `src/cli/handlers/auth.ts:239` | 已读 `ANTHROPIC_API_KEY` env var + `apiKeySource` 字段 |
| OAuth subscription auth | `src/utils/teleport/api.ts:181` `prepareApiRequest()` | 拿 OAuth token + orgUUID已 work for /v1/code/triggers |
| Workspace API client | — | **没实现**4 个 P2 clientvault/agents/memory-stores/skill-store当前只走 OAuth |
| 第三方 API key env vars | CLAUDE.md 列了 `OPENAI_API_KEY` `GEMINI_API_KEY` `GROK_API_KEY` `OPENAI_BASE_URL` 等 | 用于聊天 endpoint 不是管理 endpoint |
| `/login` 命令 | `src/commands/login/*` | 已支持切 OAuth / API key 模式 |
### 不可逾越的约束
1. **第三方 provider 永远没有 vault/agents/memory_stores 等价端点** — 这是 Anthropic 私有功能OpenAI/Gemini/Grok/Bedrock 没等价。所以"第三方支持"指的是**聊天/推理 endpoint**,不是管理 endpoint。
2. **workspace API key 只能调 Anthropic api.anthropic.com**,与第三方 host 不通。
3. **订阅 OAuth ≠ workspace API key**,必须双轨并存(不强制用户选一个)。
---
## 2. 三层 auth plane 设计
```
┌─────────────────────────────────────┐
User CLI 用户输入 / 命令派发 │
└────────┬────────────────────────────┘
┌───────────────┼─────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ 推理 endpoint│ │ 订阅 endpoint│ │ workspace endpoint│
│ (聊天/补全) │ │ /v1/code/* │ │ /v1/agents │
│ │ │ /v1/sessions │ │ /v1/vaults │
│ │ │ ultrareview │ │ /v1/memory_stores│
│ │ │ /schedule │ │ /v1/skills │
└──────┬───────┘ └──────┬───────┘ └────────┬─────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌──────────────┐ ┌────────────────────┐
│ Provider 选择器 │ │ Subscription │ │ Workspace API key │
│ ─────────────── │ │ OAuth bearer │ │ ────────────────── │
│ firstParty (默)│ │ /login 拿到 │ │ ANTHROPIC_API_KEY │
│ bedrock │ │ prepareApiReq│ │ (sk-ant-api03-*) │
│ vertex │ │ │ │ console.anthropic │
│ foundry │ │ │ │ │
│ openai (compat)│ │ │ │ │
│ gemini │ │ │ │ │
│ grok │ │ │ │ │
│ 第三方: │ │ │ │ 第三方 workspace: │
│ - Cerebras │ │ │ │ 不支持(这些 plane │
│ - Groq │ │ │ │ 是 Anthropic 私有)│
│ - 通义/混元 │ │ │ │ │
│ - 自建 OpenAI │ │ │ │ │
│ 兼容 endpoint│ │ │ │ │
└────────────────┘ └──────────────┘ └────────────────────┘
```
### 3 个 auth plane 互不替换 — 用户可同时拥有
- **推理 endpoint**:每次 API call 都用,按 token 计费API key或包含在订阅
- **订阅 endpoint**:仅 `/login` 拿到 OAuth bearer 后能用,免费包含在订阅
- **workspace endpoint**:管理 agent/vault/memory store 等"组织资源",只接受 workspace API key`sk-ant-api03-*`),独立计费
---
## 3. 实施方案(分 4 个 PR
### PR-1Workspace API key 模式(让隐藏的 3 命令复活)
**目标**:用户设 `ANTHROPIC_API_KEY=sk-ant-api03-*` 后,`/vault` `/agents-platform` `/memory-stores` 启用。
**改动文件**
- `src/utils/teleport/api.ts``prepareWorkspaceApiRequest(): { apiKey: string }`
```ts
export async function prepareWorkspaceApiRequest(): Promise<{ apiKey: string }> {
const apiKey = process.env.ANTHROPIC_API_KEY?.trim()
if (!apiKey) {
throw new Error(
'Workspace API key required. Set ANTHROPIC_API_KEY=sk-ant-api03-* (from https://console.anthropic.com/settings/keys). Subscription OAuth bearer cannot reach workspace endpoints.',
)
}
if (!apiKey.startsWith('sk-ant-api03-')) {
throw new Error('ANTHROPIC_API_KEY must start with sk-ant-api03- (workspace key, not subscription token).')
}
return { apiKey }
}
```
- 4 个 P2 client `buildHeaders()` 改:
```ts
async function buildHeaders(): Promise<Record<string, string>> {
const { apiKey } = await prepareWorkspaceApiRequest()
return {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'anthropic-beta': BETA_HEADER, // 各文件原值
'content-type': 'application/json',
}
}
```
- `vault/vaultsApi.ts` / `memory-stores/memoryStoresApi.ts` / `agents-platform/agentsApi.ts` / `skill-store/skillsApi.ts`
- 注意:**不再需要** `x-organization-uuid`API key 自带 org 路由)
- 4 个 `index.ts` 改 `isHidden` 为动态:
```ts
isHidden: !process.env.ANTHROPIC_API_KEY, // 有 key 自动显示,无 key 隐藏
```
- 4 个 `__tests__/api.test.ts` 改 mockmock `prepareWorkspaceApiRequest` 而非 prepareApiRequest断言 `x-api-key` header 而非 `Authorization`
**测试**:每个 client 加 1 测试确认 `x-api-key` header 被传 + 1 测试确认无 key 时抛清晰错。
**估算**500 行含测试1 个 PR。
---
### PR-2第三方 API provider 注册框架
**目标**:让用户接 Cerebras / Groq / 通义 / 自建 OpenAI-compatible endpoint扩展现有 7-provider 列表为可注册。
**关键观察**fork 已有 `CLAUDE_CODE_USE_OPENAI` `OPENAI_BASE_URL` `OPENAI_MODEL` 模式(文档化),可直接接任何 OpenAI 兼容 endpoint含 Cerebras `https://api.cerebras.ai/v1` 和 Groq `https://api.groq.com/openai/v1`)。**无需新代码** — 已 work。
**真正缺的**
1. 配置文件 `~/.claude/providers.json` 让用户存多个 provider 切换:
```json
{
"providers": [
{ "id": "cerebras", "kind": "openai-compat", "baseUrl": "https://api.cerebras.ai/v1", "apiKeyEnv": "CEREBRAS_API_KEY", "defaultModel": "llama-3.3-70b" },
{ "id": "groq", "kind": "openai-compat", "baseUrl": "https://api.groq.com/openai/v1", "apiKeyEnv": "GROQ_API_KEY", "defaultModel": "llama-3.3-70b-versatile" },
{ "id": "qwen", "kind": "openai-compat", "baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1", "apiKeyEnv": "DASHSCOPE_API_KEY" },
{ "id": "deepseek", "kind": "openai-compat", "baseUrl": "https://api.deepseek.com/v1", "apiKeyEnv": "DEEPSEEK_API_KEY" }
],
"default": "cerebras"
}
```
2. `/provider` 命令切换:`/provider use cerebras` → 设 `CLAUDE_CODE_USE_OPENAI=1` `OPENAI_BASE_URL=https://api.cerebras.ai/v1` 然后重启。
**改动文件**
- 新建 `src/services/providerRegistry/` 含 `loader.ts`、`switcher.ts`、`__tests__/`
- 新建 `src/commands/provider/index.ts` + `launchProvider.tsx`Ink picker 列 providerEnter 选)
- 注册到主 `COMMANDS`
**估算**800 行1 个 PR。**前提**PR-1 先合(保持 commit 顺序)。
---
### PR-3本地等价物无 workspace key 用户的兜底)
**目标**:没 workspace API key 的订阅用户也能用 vault/memory-stores 的核心功能(管 secret / 跨 session 持久化),通过 fork 本地实现。
- `/local-vault`aliases `/lv` `/local-secret`
- 用 OS keychain`@napi-rs/keyring`)存 secretfallback `~/.claude/local-vault.enc.json` AES-256-GCM
- 子命令:`list / set <key> <value> / get <key> / delete <key>`
- 命令名独立 — 与 `/vault`workspace不冲突
- `/local-memory`aliases `/lm`
- 复用 fork 已有 `src/services/SessionMemory/`,扩展为多 store
- 子命令:`list / create <name> / store <name> <key> <value> / fetch <name> <key>`
**估算**1000 行1 个 PR。**P3 优先级**(用户没明确要本地版,可跳过)。
---
### PR-4`/login` UX 升级
**目标**:让 `/login` 让用户看清 3 个 auth plane 各自状态 + 一键配置。
UI 大约:
```
Anthropic auth status:
☑ Subscription (claude.ai) pro plan
☐ Workspace API key not set
To enable /vault /agents-platform /memory-stores:
1. Open https://console.anthropic.com/settings/keys
2. Create a key (sk-ant-api03-*)
3. Set ANTHROPIC_API_KEY=<paste>
4. Restart Claude Code
Third-party providers:
✓ cerebras (CEREBRAS_API_KEY set, 5 models)
☐ groq (GROQ_API_KEY not set)
☐ qwen (DASHSCOPE_API_KEY not set)
Press 1 to switch active provider, 2 to add a third-party, q to quit.
```
**估算**400 行1 个 PR。
---
## 4. 安全设计(每 PR 都要满足)
| 风险 | 缓解 |
|---|---|
| API key 写到日志 | `sanitizeErrorMessage()` 已实现mask `sk-ant-*` `sk-*` 等)— 4 个 P2 client 的 catch 块都已 reuse |
| API key 误传到第三方 endpoint | switcher.ts 严格验证 `apiKeyEnv` 与 `baseUrl` 配对,配置文件加 schema 校验 |
| OS keychain 不可用环境headless / CI | local-vault 自动 fallback AES-256-GCM 加密文件,密码从 `~/.claude/local-vault.passphrase`gitignore读 |
| 用户误把订阅 OAuth 当 workspace key 配 | `prepareWorkspaceApiRequest()` 检查 `apiKey.startsWith('sk-ant-api03-')`,不是的话明确报错 |
---
## 5. 实施顺序 + 测试
| Step | PR | 工作量 | 测试 | 依赖 |
|---|---|---|---|---|
| 1 | PR-1 workspace API key | ~500 行 | mock prepareWorkspaceApiRequest + 4 client 各 5 测试 + 1 集成 | 无 |
| 2 | PR-2 provider registry | ~800 行 | loader.ts schema test + switcher.ts 4 测试 + provider 命令 8 测试 | PR-1 |
| 3 | PR-4 /login UI | ~400 行 | Ink render test 5 测试 | PR-1 + PR-2 |
| 4 | PR-3 local-vault / local-memory | ~1000 行 | keyring mock + crypto test 12 测试 | 无(独立可做) |
**总**:约 2700 行 + 60 测试4 个 PR。
---
## 6. 推荐先做哪个
**最小 viable** = **PR-1** 单做。
- 让 `/vault` `/agents-platform` `/memory-stores` 在用户配 workspace API key 后立即启用
- 零破坏(无 key 时仍隐藏)
- ~500 行可周末完成
- 高优先级:直接解决用户当前痛点
**P2 = PR-2**(第三方 provider 切换)—— 第三方推理 endpoint 已 workCLAUDE.md缺的是注册管理 UI。
**P3 = PR-4**`/login` UI 升级)—— nice-to-have等前 2 个稳定后做。
**P4 = PR-3**(本地 vault/memory—— 用户没明确要,可跳。
---
## 7. 反向问题
1. **workspace API key 是否有 spending cap** 用户配后会不会被恶意 prompt 大量调用?
→ fork 应在每次调用前 log 一次 estimated cost超阈值如 $1/call警告
2. **订阅用户配 API key 后调聊天会优先用哪个?**
→ 现有 `prepareApiRequest()` 优先 OAuthworkspace API key 仅用于 P2 管理 endpoint。需要在文档明确不混用
3. **Cerebras / Groq 等只能 OpenAI-compat 吗?还是 Anthropic-compat**
→ 调研:截至 2026-05主要是 OpenAI Chat Completions 兼容Anthropic-compat 只有 Anthropic 自己 + Bedrock + Vertex
4. **本地 vault 如何处理 git rotate**
→ AES key 不进 git`~/.claude/.local-vault-rotate-log` 记录最近 rotation
---
**报告作者**Claude Opus 4.7
**Codex 验证**:完成 2026-05-04codex CLI v0.125.0
---
## 8. Codex 反馈合入
### Q1 → CONFIRM
PR-1 header shape **正确**。引用 `https://platform.claude.com/docs/en/api/beta/agents/create` + API Overview官方 `/v1/agents` 请求只需 `Content-Type / anthropic-version / anthropic-beta: managed-agents-2026-04-01 / X-Api-Key`**不**含 `x-organization-uuid`org 由 server 在 response 里通过 `anthropic-organization-id` 返回)。**采纳4 P2 client 删 x-organization-uuid 行**。
### Q2 → EXPANDPR-2 兼容性风险)
PR-2 不只是 config UI。第三方"OpenAI 兼容"实际有差异,需要 per-provider 回归测试:
| Provider | 已知差异 |
|---|---|
| **DeepSeek** | `reasoning_content` 跨模式行为不一致thinking-only / thinking+tools / 普通fork 当前"always preserve reasoning_content"对 DeepSeek 需针对性测试 |
| **严格"兼容"endpoint** | 可能拒绝 `stream_options: { include_usage: true }` 和额外 `thinking` 字段 — 需要 graceful drop |
| **Groq / Cerebras** | 主流 streaming + tool_calls 应该 OKfork 已支持),但要测试新模型名(如 Groq llama-3.3-70b-versatile |
**采纳PR-2 加一个 `providerCompatMatrix.ts`,每个 provider 配置允许传的 fields**whitelist 模式而非 dump 全部)。
### Q3 → EXPANDroute/header coupling 守卫)
**主漏点不是 plane 共存,是 route/header 错配**。Codex 验证:
- ✓ 订阅 bearer **不会**到 Cerebras`getOpenAIClient()` 只读 `OPENAI_*` env
- ⚠️ **workspace key 可达 `/v1/messages`** — 技术合法但 billing intent 惊喜用户以为只用订阅workspace key 也扣钱)
**采纳:必加 3 个硬边界守卫**
```ts
// src/services/auth/hostGuard.ts (新建)
export function assertWorkspaceHost(url: string): void {
if (!url.startsWith('https://api.anthropic.com')) {
throw new Error(`Workspace API key only callable to api.anthropic.com, got ${new URL(url).host}`)
}
}
export function assertNoAnthropicEnvForOpenAI(): void {
// OpenAI-compat client should never read ANTHROPIC_* — guard at construct time
const leaked = Object.keys(process.env).filter(k => k.startsWith('ANTHROPIC_') && process.env[k])
if (leaked.length > 0) {
// not throw — just warn (user may still legit have workspace key)
console.warn(`[OpenAI client] ANTHROPIC_* env vars present (${leaked.join(',')}) — these are NOT used by this provider; check intent`)
}
}
export function assertSubscriptionBaseUrl(url: string): void {
if (!url.startsWith('https://api.anthropic.com')) {
throw new Error(`Subscription OAuth helpers must not use arbitrary base URL, got ${url}`)
}
}
```
3 个 client 工厂调用入口处 invoke 这些 guard。
### 综合采纳总结
| Codex 反馈 | 设计调整 |
|---|---|
| header shape CONFIRM | 直接采用,不改设计 |
| PR-2 compat | 新增 `providerCompatMatrix.ts` + per-provider 测试套 |
| host guard | 新增 `src/services/auth/hostGuard.ts` 三方法PR-1 立即用 |