👤 用户
想到一个新的改进,也就是在网页直联模式下,因为每个大模型厂家对API接口的要求是不一样的,尤其是Deepseek有自己独立的API调用的接口要求,能不能在切换大模型的时候,给每个大模型配一套自己独立的API调用方式,用哪个模型就调用哪套大模型的API调用方法,而不是共享一套API调用方法,你把这想法实现
🤖 DeepSeek 助手
```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"
  }
]
```