Resolve PLAN.md conflict by keeping both plans: Provider management system and Scheduled Tasks enhancement. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
20 KiB
模型配置重构计划 — Provider 管理系统
背景
当前项目的模型配置存在以下问题:
- 模型列表硬编码在
src/server/api/models.ts的AVAILABLE_MODELS数组中 - 不支持自定义 Provider(供应商)
- 不支持自定义 Base URL 和 API Key
- 无法测试模型连通性
- 无法管理多个 Provider 并在它们之间切换
设计原则
- 非侵入性 — 不修改 Claude Code 原生 settings.json 的 schema,通过
env字段注入环境变量 - 简洁 — 不过度设计,只实现核心功能:Provider CRUD、激活切换、连通性测试
- 兼容 — 借鉴 cc-switch 的激活机制,通过写入
settings.json的env字段实现 Provider 切换
核心机制
Claude Code 的 settings.json 支持 env 字段,会在启动时注入到 process.env:
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.example.com",
"ANTHROPIC_AUTH_TOKEN": "sk-xxx"
},
"model": "claude-opus-4-6"
}
激活 Provider = 将其 baseUrl/apiKey/model 写入 settings.json 的 env 字段
数据模型
Provider 类型定义
// src/server/types/provider.ts
interface ProviderModel {
id: string // 模型 ID,如 "claude-opus-4-6"
name: string // 显示名称,如 "Opus 4.6"
description?: string // 简短描述
context?: string // 上下文窗口,如 "200k"
}
interface Provider {
id: string // UUID
name: string // 显示名称,如 "Anthropic 官方"、"OpenRouter"
baseUrl: string // API Base URL
apiKey: string // API Key
models: ProviderModel[] // 该 Provider 支持的模型列表
isActive: boolean // 是否为当前激活的 Provider
createdAt: number // 创建时间戳
updatedAt: number // 更新时间戳
notes?: string // 备注
}
存储格式
文件路径:~/.claude/providers.json
{
"providers": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Anthropic 官方",
"baseUrl": "https://api.anthropic.com",
"apiKey": "sk-ant-xxx",
"models": [
{ "id": "claude-opus-4-6", "name": "Opus 4.6", "description": "Most capable", "context": "200k" },
{ "id": "claude-sonnet-4-6", "name": "Sonnet 4.6", "description": "Most efficient", "context": "200k" },
{ "id": "claude-haiku-4-5", "name": "Haiku 4.5", "description": "Fastest", "context": "200k" }
],
"isActive": true,
"createdAt": 1712476800000,
"updatedAt": 1712476800000
},
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "OpenRouter",
"baseUrl": "https://openrouter.ai/api/v1",
"apiKey": "sk-or-xxx",
"models": [
{ "id": "anthropic/claude-opus-4-6", "name": "Claude Opus 4.6", "context": "200k" }
],
"isActive": false,
"createdAt": 1712476800000,
"updatedAt": 1712476800000
}
],
"activeModel": "claude-opus-4-6",
"version": 1
}
实现步骤
Step 1: Provider 服务层 (src/server/services/providerService.ts)
创建 ProviderService 类,负责:
listProviders()— 读取~/.claude/providers.json并返回 provider 列表getProvider(id)— 获取单个 providergetActiveProvider()— 获取当前激活的 provideraddProvider(data)— 添加新 provider(自动生成 UUID)updateProvider(id, data)— 更新 provider 信息deleteProvider(id)— 删除 provider(不允许删除激活中的 provider)activateProvider(id, modelId)— 激活 provider 并选择模型- 将旧 provider 设为
isActive: false - 将新 provider 设为
isActive: true - 写入
~/.claude/settings.json的env字段:{ "env": { "ANTHROPIC_BASE_URL": "<provider.baseUrl>", "ANTHROPIC_AUTH_TOKEN": "<provider.apiKey>" }, "model": "<modelId>" }
- 将旧 provider 设为
testProvider(id)/testProviderConfig(baseUrl, apiKey, modelId)— 连通性测试- 向
baseUrl/v1/messages发送一个最小请求(max_tokens=1, "Hi") - 返回
{ success, latencyMs, error?, modelUsed? }
- 向
Step 2: Provider REST API (src/server/api/providers.ts)
| 方法 | 路径 | 描述 |
|---|---|---|
| GET | /api/providers |
获取 provider 列表 |
| GET | /api/providers/:id |
获取单个 provider |
| POST | /api/providers |
添加 provider |
| PUT | /api/providers/:id |
更新 provider |
| DELETE | /api/providers/:id |
删除 provider |
| POST | /api/providers/:id/activate |
激活 provider 并选择模型 |
| POST | /api/providers/:id/test |
测试已保存 provider 的连通性 |
| POST | /api/providers/test |
测试未保存配置的连通性(用于添加时预检) |
Step 3: 注册路由 (src/server/router.ts)
在 router 中添加 providers 路由:
case 'providers':
return handleProvidersApi(req, url, segments)
Step 4: 重构 Models API (src/server/api/models.ts)
修改现有的 /api/models 端点:
- GET
/api/models— 不再返回硬编码列表,而是从当前激活的 Provider 读取模型列表 - GET
/api/models/current— 从 providers.json 的activeModel读取 - PUT
/api/models/current— 更新activeModel并同步到 settings.json
保留 Effort Level 相关 API 不变。
Step 5: Provider 类型定义 (src/server/types/provider.ts)
独立的类型文件,包含:
Provider接口ProviderModel接口ProviderTestResult接口ProvidersConfig接口(providers.json 的根类型)- Zod schema 验证
文件变更清单
| 操作 | 文件路径 | 说明 |
|---|---|---|
| 新建 | src/server/types/provider.ts |
Provider 类型定义和 Zod schema |
| 新建 | src/server/services/providerService.ts |
Provider 服务层(CRUD + 激活 + 测试) |
| 新建 | src/server/api/providers.ts |
Provider REST API 路由处理 |
| 修改 | src/server/router.ts |
注册 /api/providers 路由 |
| 修改 | src/server/api/models.ts |
从 Provider 动态读取模型列表 |
总计: 3 个新文件 + 2 个修改文件
激活流程图
用户选择 Provider "OpenRouter" + 模型 "claude-opus-4-6"
│
├─ 1. 更新 providers.json
│ - 旧 provider: isActive = false
│ - 新 provider: isActive = true
│ - activeModel = "claude-opus-4-6"
│
├─ 2. 读取当前 ~/.claude/settings.json
│
├─ 3. 合并写入 settings.json
│ {
│ ...existingSettings,
│ "env": {
│ ...existingEnv,
│ "ANTHROPIC_BASE_URL": "https://openrouter.ai/api/v1",
│ "ANTHROPIC_AUTH_TOKEN": "sk-or-xxx"
│ },
│ "model": "claude-opus-4-6"
│ }
│
└─ 4. 返回成功响应
连通性测试流程
POST /api/providers/test
Body: { baseUrl, apiKey, modelId }
│
├─ 1. 构造最小请求
│ POST {baseUrl}/v1/messages
│ Headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01" }
│ Body: { model: modelId, max_tokens: 1, messages: [{ role: "user", content: "Hi" }] }
│
├─ 2. 记录开始时间
│
├─ 3. 发送请求(超时 15 秒)
│
└─ 4. 返回结果
成功: { success: true, latencyMs: 850, modelUsed: "claude-opus-4-6" }
失败: { success: false, error: "401 Unauthorized", latencyMs: 200 }
不在本次范围内
- 前端 UI 组件(后续单独实现)
- Provider 图标管理
- API Key 加密存储(V2 考虑)
- 多 API 格式支持(OpenAI 兼容等,V2 考虑)
- Provider 导入/导出
- 自动故障转移(failover)
Scheduled Tasks Enhancement Plan
Overview
改进定时任务功能:支持编辑、新增工作目录选择、复用 sessions 组件。
参考官方 Claude Code 桌面端 APP 的 "New scheduled task" 对话框实现。
Phase 1: Data Model Extension
1.1 扩展 CronTask 类型 (src/utils/cronTasks.ts)
在现有 CronTask 类型中新增以下字段:
type CronTask = {
// ... existing fields ...
/** Human-readable task name (e.g. "daily-code-review") */
name?: string
/** Task description (e.g. "Review yesterday's commits") */
description?: string
/** Working directory for the task execution */
folder?: string
/** Model to use (e.g. "claude-opus-4-6", "claude-sonnet-4-6") */
model?: string
/** Permission mode: "ask" | "auto-accept" | "plan" | "bypass" */
permissionMode?: string
/** Whether to use git worktree for execution */
worktree?: boolean
/** Schedule frequency for UI display: "manual" | "hourly" | "daily" | "weekdays" | "weekly" */
frequency?: string
/** Time string for scheduled execution (e.g. "09:00") - UI helper for daily/weekdays/weekly */
scheduledTime?: string
}
向后兼容:所有新字段均为 optional,旧数据无需迁移。
1.2 更新存储 read/write (src/utils/cronTasks.ts)
readCronTasks(): 在读取时保留新字段(name, description, folder, model, permissionMode, worktree, frequency, scheduledTime)writeCronTasks(): 在写入时包含新字段(仍然 stripdurable和agentId)addCronTask(): 扩展函数签名接收新字段
1.3 新增 updateCronTask() 函数 (src/utils/cronTasks.ts)
export async function updateCronTask(
id: string,
updates: Partial<Omit<CronTask, 'id' | 'createdAt'>>,
dir?: string,
): Promise<boolean>
- 查找并更新内存中(session store)或磁盘上的任务
- 如果 cron 表达式变化,重新验证
- 返回是否找到并更新成功
Phase 2: CronUpdateTool (src/tools/ScheduleCronTool/CronUpdateTool.ts)
2.1 创建新工具文件
参照 CronCreateTool.ts 和 CronDeleteTool.ts 的模式:
const inputSchema = z.strictObject({
id: z.string().describe('Job ID returned by CronCreate.'),
cron: z.string().optional().describe('New cron expression'),
prompt: z.string().optional().describe('New prompt'),
name: z.string().optional().describe('New name'),
description: z.string().optional().describe('New description'),
folder: z.string().optional().describe('New working directory'),
model: z.string().optional().describe('New model'),
permissionMode: z.string().optional().describe('New permission mode'),
worktree: z.boolean().optional().describe('New worktree setting'),
recurring: z.boolean().optional().describe('New recurring setting'),
frequency: z.string().optional().describe('New frequency'),
scheduledTime: z.string().optional().describe('New time'),
})
2.2 更新 prompt.ts
- 新增
CRON_UPDATE_TOOL_NAME = 'CronUpdate' - 添加 description 和 prompt 构建函数
2.3 更新 UI.tsx
- 新增
renderUpdateToolUseMessage()和renderUpdateResultMessage()
Phase 3: UI Components (复用 sessions 组件)
核心原则:所有可复用的组件直接 import 使用,不复制代码。sessions 那边改了,这边自动生效。
3.1 频率到 Cron 表达式映射工具 (src/utils/cronFrequency.ts)
新建工具函数,在 UI 友好的频率设置和 cron 表达式之间互转:
// Frequency → Cron
function frequencyToCron(frequency: string, time?: string): string
// "daily" + "09:00" → "0 9 * * *"
// "hourly" → "0 * * * *"
// "weekdays" + "09:00" → "0 9 * * 1-5"
// "weekly" + "09:00" → "0 9 * * 1"
// Cron → Frequency (best effort)
function cronToFrequency(cron: string): { frequency: string; time?: string }
3.2 定时任务向导 (src/components/scheduled-tasks/ScheduledTaskWizard.tsx)
使用现有 Wizard 框架 (src/components/wizard/),创建多步骤向导:
type ScheduledTaskWizardData = {
name: string
description: string
prompt: string
model?: string
permissionMode?: string
folder?: string
worktree?: boolean
frequency: string // "manual" | "hourly" | "daily" | "weekdays" | "weekly"
scheduledTime?: string // "09:00"
cron?: string // 最终生成的 cron 表达式
}
// 支持 create 和 edit 两种模式
type Props = {
mode: 'create' | 'edit'
initialData?: Partial<ScheduledTaskWizardData> // edit 模式下预填充
taskId?: string // edit 模式下的任务 ID
onComplete: (data: ScheduledTaskWizardData) => void
onCancel: () => void
}
向导步骤(每步复用 sessions 组件):
| Step | 组件 | 复用来源 |
|---|---|---|
| 1. NameStep | TextInput |
src/components/TextInput.tsx |
| 2. DescriptionStep | TextInput + external editor |
src/components/agents/new-agent-creation/wizard-steps/DescriptionStep.tsx 的模式 |
| 3. PromptStep | TextInput + external editor |
src/components/agents/new-agent-creation/wizard-steps/PromptStep.tsx 的模式 |
| 4. ModelStep | ModelSelector |
直接复用 src/components/agents/ModelSelector.tsx |
| 5. PermissionStep | Select |
直接复用 src/components/CustomSelect/select.tsx |
| 6. FolderStep | Select / FuzzyPicker |
复用 src/components/CustomSelect/select.tsx + sessionStorage.ts 的项目发现 |
| 7. ScheduleStep | Select + TextInput |
直接复用 src/components/CustomSelect/select.tsx |
| 8. ConfirmStep | Summary display | 使用 Dialog + Text 显示摘要 |
3.3 各步骤详细设计
Step 1: NameStep
// 直接使用 TextInput,参照 DescriptionStep 模式
function NameStep(): ReactNode {
const { goNext, goBack, updateWizardData, wizardData } = useWizard<ScheduledTaskWizardData>()
// TextInput with validation: name is required
}
Step 4: ModelStep(复用 ModelSelector)
import { ModelSelector } from '../../agents/ModelSelector.js'
function ModelStep(): ReactNode {
const { goNext, updateWizardData, wizardData } = useWizard<ScheduledTaskWizardData>()
return (
<WizardDialogLayout subtitle="Select Model">
<ModelSelector
initialModel={wizardData.model}
onComplete={(model) => {
updateWizardData({ model })
goNext()
}}
onCancel={goBack}
/>
</WizardDialogLayout>
)
}
Step 5: PermissionStep(复用 Select)
import { Select } from '../../CustomSelect/select.js'
const permissionOptions = [
{ label: 'Ask permissions', value: 'ask', description: 'Always ask before making changes' },
{ label: 'Auto accept edits', value: 'auto-accept', description: 'Automatically accept all file edits' },
{ label: 'Plan mode', value: 'plan', description: 'Create a plan before making changes' },
{ label: 'Bypass permissions', value: 'bypass', description: 'Accepts all permissions', disabled: false },
]
Step 6: FolderStep(复用 sessionStorage 项目发现)
import { loadAllProjectsMessageLogs } from '../../utils/sessionStorage.js'
// 或者直接读取 GlobalConfig.projects 获取最近项目列表
function FolderStep(): ReactNode {
// 1. 从 GlobalConfig.projects 获取已知项目路径
// 2. 使用 Select 展示 "Recent" 项目列表
// 3. 最后一项 "Choose a different folder" 允许手动输入路径
}
Step 7: ScheduleStep(频率 + 时间)
const frequencyOptions = [
{ label: 'Manual', value: 'manual' },
{ label: 'Hourly', value: 'hourly' },
{ label: 'Daily', value: 'daily' },
{ label: 'Weekdays', value: 'weekdays' },
{ label: 'Weekly', value: 'weekly' },
]
function ScheduleStep(): ReactNode {
// 1. 选择频率 (Select 组件)
// 2. 如果是 daily/weekdays/weekly,显示时间输入 (TextInput,格式 HH:MM)
// 3. 使用 frequencyToCron() 转换为 cron 表达式
}
Step 8: ConfirmStep
function ConfirmStep(): ReactNode {
// 显示所有配置的摘要
// Enter 确认创建/更新
// Esc 返回上一步
}
Phase 4: Integration
4.1 新增 /schedule-local Skill (src/skills/bundled/scheduleLocal.ts)
或修改现有 /schedule skill,添加本地定时任务的向导流程:
// 用户输入 /schedule 时:
// 1. 如果没参数 → 显示 ScheduledTaskWizard (create 模式)
// 2. 如果参数是 task ID → 显示 ScheduledTaskWizard (edit 模式,预填充)
// 3. 如果参数是 "list" → 调用 CronList
4.2 注册 CronUpdateTool
在工具注册表中添加 CronUpdateTool:
- 更新
src/tools/ScheduleCronTool/导出 - 确保工具在
isKairosCronEnabled()条件下启用
4.3 更新 CronListTool 输出
在列表输出中包含新字段(name, description, folder, model 等),方便用户识别任务。
Phase 5: Testing
5.1 单元测试
cronTasks.ts: 测试 updateCronTask() 的各种场景(内存任务、磁盘任务、不存在的 ID)cronFrequency.ts: 测试频率到 cron 的双向转换CronUpdateTool.ts: 测试验证逻辑(无效 ID、权限检查)
5.2 集成测试
- 验证 wizard 创建的任务能被 scheduler 正确执行
- 验证编辑后的任务能正确更新 nextFireAt
File Changes Summary
| File | Change Type | Description |
|---|---|---|
src/utils/cronTasks.ts |
Modified | 扩展 CronTask 类型,新增 updateCronTask(),更新 read/write/add |
src/utils/cronFrequency.ts |
New | 频率 ↔ Cron 表达式转换工具 |
src/tools/ScheduleCronTool/CronUpdateTool.ts |
New | 编辑定时任务工具 |
src/tools/ScheduleCronTool/prompt.ts |
Modified | 新增 CronUpdate 的 name/description/prompt |
src/tools/ScheduleCronTool/UI.tsx |
Modified | 新增 update 的 render 函数 |
src/components/scheduled-tasks/ScheduledTaskWizard.tsx |
New | 定时任务向导(复用 sessions 组件) |
src/components/scheduled-tasks/steps/NameStep.tsx |
New | 名称输入步骤 |
src/components/scheduled-tasks/steps/DescriptionStep.tsx |
New | 描述输入步骤 |
src/components/scheduled-tasks/steps/PromptStep.tsx |
New | Prompt 输入步骤 |
src/components/scheduled-tasks/steps/ModelStep.tsx |
New | 模型选择步骤(复用 ModelSelector) |
src/components/scheduled-tasks/steps/PermissionStep.tsx |
New | 权限模式步骤(复用 Select) |
src/components/scheduled-tasks/steps/FolderStep.tsx |
New | 工作目录步骤(复用 Select + 项目发现) |
src/components/scheduled-tasks/steps/ScheduleStep.tsx |
New | 频率+时间步骤(复用 Select) |
src/components/scheduled-tasks/steps/ConfirmStep.tsx |
New | 确认步骤 |
src/skills/bundled/scheduleLocal.ts |
New or Modified | /schedule skill 集成向导 |
src/hooks/useScheduledTasks.ts |
Modified | 支持 folder/model/permissionMode/worktree 在任务执行时生效 |
Key Reuse Points (复用列表)
确保以下组件是直接 import 复用,不是复制代码:
WizardProvider-src/components/wizard/WizardProvider.tsxWizardDialogLayout-src/components/wizard/WizardDialogLayout.tsxuseWizard-src/components/wizard/useWizard.tsWizardNavigationFooter-src/components/wizard/WizardNavigationFooter.tsxModelSelector-src/components/agents/ModelSelector.tsxSelect-src/components/CustomSelect/select.tsxTextInput-src/components/TextInput.tsxDialog-src/components/design-system/Dialog.tsxFuzzyPicker-src/components/design-system/FuzzyPicker.tsx(如 FolderStep 需要搜索)useKeybinding-src/hooks/useKeybinding.ts- Session Project Discovery -
src/utils/sessionStorage.ts(loadAllProjectsMessageLogs) GlobalConfig.projects-src/utils/config.ts(最近项目列表)
Implementation Order
- Phase 1 (Data Model) → 2. Phase 2 (CronUpdateTool) → 3. Phase 3 (UI) → 4. Phase 4 (Integration) → 5. Phase 5 (Testing)
每个 Phase 完成后做一次代码审查。