```plan
[
{
"id": 1,
"tool": "bash_run",
"args": {
"command": "grep -n \\"useProxy\\\\|baseUrl\\\\|apiKey\\" /home/administrator/CmdCode-V0.5/public/cmdcode-web.html | head -20",
"timeout": 5
},
"description": "定位直连模式下构造请求的变量位置",
"expected_outcome": "找到发送消息时使用的 baseUrl、apiKey 及直连标志相关行号"
},
{
"id": 2,
"tool": "file_read",
"args": {
"path": "/home/administrator/CmdCode-V0.5/public/cmdcode-web.html",
"offset": 1620,
"limit": 80
},
"description": "读取 sendMessage 函数中构造请求和调用大模型 API 的部分,了解当前请求构建逻辑",
"expected_outcome": "识别出 fetch(url, {method, headers, body}) 的具体格式"
},
{
"id": 3,
"tool": "file_write",
"args": {
"path": "src/adapters.ts",
"content": "export interface ModelAdapter {\\n id: string;\\n name: string;\\n /** 构建请求 URL,baseUrl 是模型配置里的 api 地址,modelId 是模型名称 */\\n buildUrl(baseUrl: string, modelId: string): string;\\n /** 构建请求头,接收用户 API Key */\\n buildHeaders(apiKey: string): Record<string, string>;\\n /** 构建请求体,messages 是 OpenAI 格式的消息数组,modelId 是模型名称 */\\n buildBody(messages: any[], modelId: string): any;\\n /** 是否支持流式 (stream) */\\n supportsStream: boolean;\\n}\\n\\n// 默认 OpenAI 兼容适配器\\nexport const openaiAdapter: ModelAdapter = {\\n id: 'openai',\\n name: 'OpenAI Compatible',\\n buildUrl: (base, model) => `${base.replace(/\\\\/+$/, '')}/chat/completions`,\\n buildHeaders: (key) => ({\\n 'Content-Type': 'application/json',\\n 'Authorization': `Bearer ${key}`\\n }),\\n buildBody: (msgs, model) => ({\\n model,\\n messages: msgs,\\n temperature: 0.7,\\n max_tokens: 4096,\\n stream: true\\n }),\\n supportsStream: true\\n};\\n\\n// DeepSeek 适配器(必须使用 stream: false,且 model 名称需要严格匹配)\\nexport const deepseekAdapter: ModelAdapter = {\\n id: 'deepseek',\\n name: 'DeepSeek',\\n buildUrl: (base, model) => `${base.replace(/\\\\/+$/, '')}/chat/completions`,\\n buildHeaders: (key) => ({\\n 'Content-Type': 'application/json',\\n 'Authorization': `Bearer ${key}`\\n }),\\n buildBody: (msgs, model) => ({\\n model,\\n messages: msgs,\\n stream: false\\n }),\\n supportsStream: false\\n};\\n\\n// 智谱 GLM 适配器\\nexport const zhipuAdapter: ModelAdapter = {\\n id: 'zhipu',\\n name: 'ZhipuAI',\\n buildUrl: (base, model) => `${base.replace(/\\\\/+$/, '')}/chat/completions`,\\n buildHeaders: (key) => ({\\n 'Content-Type': 'application/json',\\n 'Authorization': `Bearer ${key}`\\n }),\\n buildBody: (msgs, model) => ({\\n model,\\n messages: msgs,\\n stream: false\\n }),\\n supportsStream: false\\n};\\n\\n// 内置适配器表\\nexport const BUILTIN_ADAPTERS: Record<string, ModelAdapter> = {\\n 'deepseek-v4-flash': deepseekAdapter,\\n 'deepseek-v4-pro': deepseekAdapter,\\n 'glm-4.7-flash': zhipuAdapter,\\n // MiniMax、Hunyuan 等使用默认适配器\\n};\\n\\n/** 根据模型 ID 获取适配器,没有则回退到 OpenAI 默认 */\\nexport function getAdapter(modelId: string): ModelAdapter {\\n return BUILTIN_ADAPTERS[modelId] || openaiAdapter;\\n}"
},
"description": "创建适配器定义文件,内置常见厂商的 API 调用差异",
"expected_outcome": "src/adapters.ts 文件存在,导出了统一的适配器接口和内置适配器"
},
{
"id": 4,
"tool": "file_read",
"args": {
"path": "src/web.ts",
"offset": 1,
"limit": 30
},
"description": "检查 web.ts 头部导入,准备添加适配器相关路由",
"expected_outcome": "确认现有导入结构"
},
{
"id": 5,
"tool": "file_edit",
"args": {
"path": "src/web.ts",
"new_text": "import { getAdapter, BUILTIN_ADAPTERS } from './adapters.js';",
"old_text": "import { executeTool } from './tools.js';"
},
"description": "在 web.ts 中导入适配器模块",
"expected_outcome": "成功导入适配器函数"
},
{
"id": 6,
"tool": "file_edit",
"args": {
"path": "src/web.ts",
"new_text": " // 获取适配器列表\\n if (path === '/api/adapters' && method === 'GET') {\\n const list = Object.entries(BUILTIN_ADAPTERS).map(([id, adp]) => ({ id, name: adp.name }));\\n return json({ adapters: list });\\n }\\n\\n // 为模型指定适配器\\n if (path === '/api/models/adapter' && method === 'POST') {\\n const { modelId, adapterId } = await req.json();\\n // 将适配器信息存储到 secrets.enc 的 modelAdapters 映射中\\n const config = loadSecrets();\\n if (!config.modelAdapters) config.modelAdapters = {};\\n config.modelAdapters[modelId] = adapterId;\\n saveSecrets(config);\\n return json({ ok: true });\\n }",
"old_text": " // 模型列表\\n if (path === '/api/models' && method === 'GET') {"
},
"description": "新增 API:获取适配器列表和设置模型适配器",
"expected_outcome": "可通过 /api/adapters 获取可用适配器,/api/models/adapter 关联模型与适配器"
},
{
"id": 7,
"tool": "file_edit",
"args": {
"path": "src/web.ts",
"new_text": " // 模型列表(附带适配器信息)\\n if (path === '/api/models' && method === 'GET') {\\n const { BUILTIN_PROVIDERS } = await import('./models');\\n const userModels = listUserModels('default') || [];\\n const config = loadSecrets();\\n const adapterMap = config.modelAdapters || {};\\n const allModels = [\\n ...BUILTIN_PROVIDERS.map(p => ({\\n id: p.id,\\n name: p.name,\\n vendor: p.vendor,\\n url: p.url,\\n custom: false,\\n adapter: adapterMap[p.id] || null\\n })),\\n ...userModels.map(m => ({\\n id: m.id,\\n name: m.name,\\n vendor: m.vendor || '自定义',\\n url: m.baseUrl,\\n custom: true,\\n customId: m.id,\\n adapter: adapterMap[m.id] || null\\n }))\\n ];\\n return json({ models: allModels });\\n }",
"old_text": " // 模型列表\\n if (path === '/api/models' && method === 'GET') {\\n const { BUILTIN_PROVIDERS } = await import('./models');\\n return json({ models: BUILTIN_PROVIDERS });\\n }"
},
"description": "增强 GET /api/models 返回的数据,包含每个模型的适配器 ID",
"expected_outcome": "模型列表中的每个模型都有 adapter 字段,前端可用以确定调用方式"
},
{
"id": 8,
"tool": "file_read",
"args": {
"path": "/home/administrator/CmdCode-V0.5/public/cmdcode-web.html",
"offset": 1310,
"limit": 40
},
"description": "读取 loadModelsIntoPanel 函数,确认模型缓存 modelsCache 的填充方式",
"expected_outcome": "找到 modelsCache 赋值的位置,以便添加 adapter 字段"
},
{
"id": 9,
"tool": "file_edit",
"args": {
"path": "/home/administrator/CmdCode-V0.5/public/cmdcode-web.html",
"new_text": " modelsCache = (data.models || []).map(m => ({\\n id: m.id,\\n name: m.name,\\n vendor: m.vendor,\\n url: m.url || '',\\n custom: m.custom || false,\\n customId: m.customId || m.id,\\n adapter: m.adapter || null\\n }));",
"old_text": " modelsCache = (data.models || []).map(m => ({\\n id: m.id,\\n name: m.name,\\n vendor: m.vendor,\\n url: m.url || '',\\n custom: m.custom || false\\n }));"
},
"description": "让模型缓存包含后端返回的适配器 ID,便于前端选择调用逻辑",
"expected_outcome": "modelsCache 中每个模型都有 adapter 属性"
},
{
"id": 10,
"tool": "file_read",
"args": {
"path": "/home/administrator/CmdCode-V0.5/public/cmdcode-web.html",
"offset": 1670,
"limit": 50
},
"description": "再次读取 sendMessage 中构建请求的变量定义区域,准备修改为利用适配器",
"expected_outcome": "获取当前直连模式请求组装代码"
},
{
"id": 11,
"tool": "file_edit",
"args": {
"path": "/home/administrator/CmdCode-V0.5/public/cmdcode-web.html",
"new_text": " // 直连模式:根据当前模型自动选择适配器\\n const currentModelCfg = modelsCache.find(m => m.id === currentModel);\\n const adapterId = currentModelCfg?.adapter || 'openai';\\n const directApiKey = localStorage.getItem('cmdcode_apikey') || '';\\n if (directApiKey) {\\n useProxy = false;\\n // 动态导入适配器(通过全局内置的适配器函数或从缓存读取)\\n const adapter = getAdapterById(adapterId);\\n const modelId = currentModel;\\n const baseUrl = currentModelCfg?.url || localStorage.getItem('cmdcode_baseurl') || 'https://api.lkeap.cloud.tencent.com/coding/v3';\\n // 构建请求\\n requestUrl = adapter.buildUrl(baseUrl, modelId);\\n requestHeaders = adapter.buildHeaders(directApiKey);\\n requestBody = adapter.buildBody(messages, modelId);\\n supportsStream = adapter.supportsStream;\\n }",
"old_text": " const directApiKey = localStorage.getItem('cmdcode_apikey') || '';\\n if (directApiKey) {\\n useProxy = false;\\n baseUrl = currentModelCfg?.url || localStorage.getItem('cmdcode_baseurl') || 'https://api.lkeap.cloud.tencent.com/coding/v3';\\n apiKey = directApiKey;\\n }"
},
"description": "重构直连模式请求组装,使用模型对应的适配器来生成 URL、headers、body",
"expected_outcome": "发送请求时自动应用该模型定制的 API 调用方式"
},
{
"id": 12,
"tool": "bash_run",
"args": {
"command": "cd /home/administrator/CmdCode-V0.5 && bun run src/cli.ts --version",
"timeout": 10
},
"description": "验证后端修改后编译成功",
"expected_outcome": "输出 CmdCode V0.5.0"
},
{
"id": 13,
"tool": "bash_run",
"args": {
"command": "curl -s http://localhost:3010/api/adapters",
"timeout": 8
},
"description": "测试新增的 /api/adapters 端点",
"expected_outcome": "返回内置适配器列表 JSON"
}
]
```