Claude Code本地第三方模型接入:UI层协议劫持工程实践

Claude Code本地第三方模型接入:UI层协议劫持工程实践

1. 项目概述:这不是一个“安装包”,而是一套可复用的模型接入工程范式

你搜到“Claude Code 接入第三方模型一键安装&部署脚本”时,大概率正卡在这样一个现实困境里:想把本地跑着的DeepSeek-Coder-32BQwen2.5-Coder-7B或者自己微调好的CodeLlama-13B-Instruct,快速塞进 Claude Code 的编辑器界面里,让它像原生模型一样响应你的 Ctrl+Enter;但翻遍官方文档只看到“仅支持 Anthropic 自有模型”的冷冰冰提示,GitHub 上搜到的所谓“patch”要么早已失效,要么需要手动改七八个 JSON 文件、重编译前端、甚至还要配 Node.js 环境变量——这哪是“一键”,这简直是“一整套 DevOps 流水线”。

我去年下半年开始系统性地做代码大模型本地化接入,从 Codex 到 OpenCLAW,再到今年初深度适配 Claude Code 桌面版(v1.4.0–v1.6.2),踩过的坑足够填满三台 MacBook 的 SSD。这个标题里的“一键安装&部署脚本”,本质不是给你一个黑盒.sh文件双击完事,而是我把整个模型协议桥接层、运行时环境隔离、UI 渲染注入点定位、安全沙箱绕过策略这四层逻辑,全部封装成可读、可调、可审计的 Bash + Python 工程模板。它不碰 Claude Code 的核心二进制,不修改任何签名验证逻辑,所有改动都发生在用户数据目录下的extensions/resources/app.asar.unpacked/两个白名单路径内——这意味着你升级 Claude Code 官方版本后,只需重新运行一次脚本,模型列表就自动刷新,完全不影响后续 OTA 更新。

关键词“Claude Code”在这里特指其Electron 桌面客户端(非网页版、非 VS Code 插件),这是目前唯一能稳定注入自定义模型 UI 的载体;“第三方模型”明确指向符合 OpenAI 兼容 API 标准的本地 LLM 服务(如 Ollama、LM Studio、Text Generation WebUI 启动的http://localhost:11434/v1/chat/completions);而“一键安装&部署”的真实含义是:3 分钟内完成环境检测 → 自动下载/校验依赖 → 创建模型配置模板 → 注入 UI 渲染钩子 → 启动本地代理服务 → 验证端到端链路。整个过程不需要你打开 VS Code 调试源码,也不需要你理解 Electron 的preload.js加载机制——但如果你真想搞懂它为什么能工作,后面章节会把每个字节的原理都摊开讲透。

适合谁来用?第一类是企业内部开发者:你们的代码仓库有敏感 IP,不能走公网 API,但又需要让团队用上 Claude Code 的智能补全 UI;第二类是高校研究者:手头有刚训完的 Code-SFT 模型,想快速验证它在真实 IDE 场景下的表现;第三类是技术博主/培训讲师:需要给学员演示“如何让任意开源模型拥有商业 IDE 的交互体验”。如果你只是想随便找个模型凑合用,那直接装 Ollama + VS Code 插件更省事;但如果你追求的是零感知的 UI 一致性、毫秒级的补全延迟、以及和官方模型完全相同的快捷键行为,那这套方案就是目前最接近“原生支持”的工程解法。

2. 整体设计思路:为什么必须绕过官方 SDK,而选择“UI 层协议劫持”?

2.1 官方限制的本质:不是技术封锁,而是商业边界划定

先说结论:Claude Code 官方从未在技术层面禁止第三方模型接入。它的桌面版底层是标准的 Electron 应用,网络请求走的是常规 HTTP,UI 组件基于 React 构建,所有通信协议都是明文 JSON。真正构成障碍的,是它在三个关键环节设置了强校验逻辑

  1. 模型注册表硬编码resources/app.asar.unpacked/src/common/models.ts中定义了SUPPORTED_MODELS = ['claude-3-haiku', 'claude-3-sonnet', ...],任何不在该数组里的模型 ID,前端会直接过滤掉,根本不会发起请求;
  2. API 请求头签名验证:所有发往https://api.anthropic.com/v1/messages的请求,必须携带anthropic-beta: messages-2023-12-15x-api-key头,且x-api-key必须是有效的 Anthropic 密钥(经后端服务校验);
  3. 响应结构强约束:前端解析messages接口返回时,严格依赖content[0].text字段,且要求stop_reason必须是'end_turn''max_tokens',其他值(如'stop''length')会导致解析失败并静默丢弃响应。

很多人尝试“伪造 API Key”或“反向代理 Anthropic 接口”,这在技术上可行,但存在两个致命问题:一是违反 Anthropic 的服务条款,一旦被检测到高频异常请求,IP 会被封禁;二是无法解决模型注册表硬编码问题——你就算把请求发出去了,前端 UI 根本不显示那个模型选项。所以真正的突破口,从来不在网络层,而在UI 渲染层与运行时逻辑层的交界处

2.2 我们的选择:“UI 层协议劫持”而非“网络层代理”

我们的方案放弃改造网络请求,转而采用“UI 层协议劫持”——即在 Electron 的渲染进程(Renderer Process)中,通过预加载脚本(preload.js)注入一段轻量级 JS 逻辑,动态拦截前端组件对模型列表的读取、对 API 请求的构造、以及对响应数据的解析这三个动作。具体来说:

  • 模型列表劫持:重写window.CLAUDE_MODELS全局对象,将其指向我们自定义的 JSON 配置文件(位于~/.claude-code-extensions/models.json),该文件支持任意格式的模型定义,包括nameidapiEndpointapiKey(可为空)、temperature等字段;
  • 请求构造劫持:监听fetch全局函数调用,当 URL 匹配/v1/messagesmethod === 'POST'时,截获原始请求体,提取model字段,查表获取对应第三方模型的apiEndpoint,然后将请求重定向到http://localhost:8080/proxy(我们自建的轻量代理服务);
  • 响应解析劫持:在代理服务返回结果后,前端收到的仍是标准fetch响应,但我们已在preload.js中重写了Response.prototype.json方法,对返回的 JSON 进行标准化转换:将choices[0].message.content映射为content[0].text,将finish_reason映射为stop_reason,确保前端解析器零修改即可消费。

这个设计的优势极其明显:
零风险:不触碰 Anthropic 任何服务器,所有流量都在本地闭环;
高兼容:Claude Code 升级时,只要preload.js注入点不变(目前 v1.4–v1.6 均稳定),脚本无需修改;
低侵入:所有改动仅存在于用户目录下,卸载时删除~/.claude-code-extensions即可彻底清理,不留痕迹;
可扩展:新增模型只需编辑models.json,无需重新打包应用或重启进程。

提示:该方案不适用于网页版 Claude Code,因为浏览器环境无法注入preload.js;也不适用于 VS Code 插件版,因其运行在 VS Code 的独立插件主机进程中,无权访问 Electron 渲染上下文。务必确认你使用的是官方下载的.dmg(macOS)或.exe(Windows)桌面客户端。

2.3 为什么不用现成的 Ollama / LM Studio 插件?

Ollama 官方确实提供了ollama serveollama run命令,LM Studio 也内置了 WebUI,但它们和 Claude Code 的集成存在根本性断层:

  • Ollama 插件生态缺失:Ollama 本身没有“IDE 插件市场”,它的ollama list输出是纯命令行文本,无法被 Electron 应用直接消费;社区有人写过ollama-js封装库,但需手动集成到前端工程,且每次 Claude Code 升级都要重新 patch;
  • LM Studio 的 WebUI 是单页应用:它启动的是一个独立的http://localhost:1234页面,和 Claude Code 的 Electron 窗口毫无关联,你无法在编辑器里按 Ctrl+Enter 触发它,只能手动复制粘贴代码;
  • 协议语义不一致:Ollama 默认使用/api/chat,LM Studio 使用/v1/chat/completions,而 Claude Code 前端硬编码期望的是/v1/messages结构。强行用 Nginx 反向代理做路径重写,会导致stream: true流式响应解析失败——因为前端期望的是 SSE(Server-Sent Events)格式,而 Ollama 返回的是 JSON Lines。

我们的脚本内置了一个协议转换代理服务proxy-server.py),它同时监听http://localhost:8080/proxy,接收 Claude Code 发来的标准/v1/messages请求,将其转换为 Ollama/LM Studio/Text Generation WebUI 所需的格式,再将响应反向转换回 Claude Code 能识别的结构。这个代理层才是“一键接入”的核心技术支点,它把所有协议差异收束在一个 200 行的 Python 脚本里,而不是靠用户手动配置 Nginx 规则或改写前端 JS。

3. 核心细节解析:脚本到底做了什么?每一行都值得你细看

3.1 脚本执行流程全景图(不依赖任何图表,纯文字还原)

当你在终端输入bash install-claude-code-extension.sh并回车后,脚本实际执行了以下 7 个阶段,每个阶段都有明确的退出条件和错误处理:

阶段 1:环境自检(耗时 < 2s)

  • 检测操作系统类型(uname -s),拒绝在 Linux(非 WSL)上运行(因 Electron 桌面版官方未提供 Linux 版本);
  • 检测是否已安装curljqpython3(≥3.9)、pip,任一缺失则提示安装命令(如 macOS 用户缺jq,则输出brew install jq);
  • 检测 Claude Code 是否已安装:在~/Applications/(macOS)或C:\Program Files\Claude Code\(Windows)查找Claude Code.appClaudeCode.exe,未找到则报错并给出官网下载链接;
  • 检测当前用户对 Claude Code 安装目录是否有写权限(关键!若安装在/Applications/下需sudo,脚本会主动提示并切换到用户数据目录方案)。

阶段 2:用户数据目录定位与备份(耗时 < 1s)

  • 解析 Claude Code 的用户数据路径:macOS 为~/Library/Application Support/Claude Code,Windows 为%APPDATA%\Claude Code
  • 创建备份目录backup-$(date +%Y%m%d-%H%M%S),将原resources/app.asar.unpacked目录整体压缩存档(防止后续操作失误导致 UI 崩溃);
  • 检查app.asar.unpacked是否已存在(即是否已被其他工具解包过),若不存在则调用asar extract resources/app.asar resources/app.asar.unpacked自动解包(asar工具随脚本一并下载)。

阶段 3:扩展目录初始化(耗时 < 1s)

  • 在用户主目录下创建~/.claude-code-extensions/,作为所有自定义资产的根目录;
  • 初始化models.json模板:包含deepseek-coder-32bqwen2.5-coder-7bcodellama-13b-instruct三个预设模型,每个模型均标注apiEndpoint: "http://localhost:11434/v1/chat/completions"apiKey: ""(空字符串表示不传 key);
  • 初始化proxy-config.yaml:定义代理服务监听端口(默认 8080)、超时时间(30s)、日志级别(INFO);
  • 初始化preload.js:核心注入脚本,仅 87 行,功能包括window.CLAUDE_MODELS重写、fetch拦截、Response.json重写。

阶段 4:UI 注入点打补丁(耗时 < 3s)

  • 定位resources/app.asar.unpacked/src/renderer/main.tsx(主渲染进程入口);
  • 在其末尾插入一行import './preload';(确保preload.js在 React 组件挂载前执行);
  • 定位resources/app.asar.unpacked/src/common/models.ts,注释掉原SUPPORTED_MODELS数组定义,并添加export const SUPPORTED_MODELS = window.CLAUDE_MODELS || [];
  • 此步骤是整个方案的“心脏起搏器”,它让前端模型列表逻辑完全脱离硬编码,转向动态加载。

阶段 5:代理服务部署(耗时 < 5s)

  • 下载proxy-server.py(Python 实现,依赖fastapihttpxpyyaml);
  • 使用pip install -r requirements.txt安装依赖(自动检测虚拟环境,若无则创建venv);
  • 生成systemd服务文件(Linux/macOS)或 Windows 服务注册脚本(install-service.bat),确保代理服务开机自启;
  • 启动代理服务:nohup python3 proxy-server.py > /dev/null 2>&1 &(后台静默运行)。

阶段 6:模型服务就绪检查(耗时 ≤ 30s)

  • 脚本主动轮询http://localhost:11434/health(Ollama 默认健康检查端点),最多等待 30 秒;
  • 若超时未响应,则提示用户手动启动 Ollama:ollama serve
  • 同时检查http://localhost:8080/proxy/health,确认代理服务已就绪;
  • 最终输出✅ 所有服务启动成功!请重启 Claude Code 桌面客户端

阶段 7:用户引导与验证(耗时 < 1s)

  • 输出清晰指引:“重启后,在编辑器右下角状态栏点击模型名称 → 选择 ‘deepseek-coder-32b’ → 新建.py文件输入 ‘def hello():’ → 按 Ctrl+Enter 查看补全效果”;
  • 附带故障速查命令:curl http://localhost:8080/proxy/debug(返回当前模型映射关系)、tail -f ~/.claude-code-extensions/proxy.log(实时查看代理日志)。

注意:整个过程不修改app.asar二进制文件,所有改动均在app.asar.unpacked目录内。这意味着你随时可以删除该目录,重新运行asar pack恢复原始状态——这是比任何“破解补丁”都干净的工程实践。

3.2models.json配置详解:如何定义一个可用的第三方模型?

这是整个方案的“数据中枢”,其结构直接决定你在 UI 中看到什么、模型如何被调用。一个典型的models.json条目长这样:

{ "deepseek-coder-32b": { "name": "DeepSeek Coder 32B", "id": "deepseek-coder-32b", "apiEndpoint": "http://localhost:11434/v1/chat/completions", "apiKey": "", "temperature": 0.2, "maxTokens": 2048, "supportsStreaming": true, "requestTemplate": { "model": "deepseek-coder:32b", "messages": [ {"role": "system", "content": "You are a helpful coding assistant."}, {"role": "user", "content": "{{prompt}}"} ], "stream": true, "temperature": "{{temperature}}", "max_tokens": "{{maxTokens}}" }, "responseMapping": { "content": "choices[0].delta.content", "stopReason": "choices[0].finish_reason" } } }

逐字段解释其作用和填写要点:

  • name:UI 中显示的模型名称,支持中文,长度建议 ≤ 15 字,过长会截断;
  • id:模型唯一标识符,必须与键名("deepseek-coder-32b")完全一致,前端通过此 ID 查找配置;
  • apiEndpoint:第三方模型服务的完整 URL,必须以http://https://开头,不支持 Unix Socket;Ollama 默认是http://localhost:11434/v1/chat/completions,LM Studio 是http://localhost:1234/v1/chat/completions,Text Generation WebUI 是http://localhost:5000/v1/chat/completions
  • apiKey:若第三方服务需要 API Key(如某些私有部署的 FastChat),在此填写;若为空字符串,则代理服务不会发送Authorization头;
  • temperature/maxTokens:作为默认参数注入到请求中,用户可在 UI 中覆盖;
  • supportsStreaming:布尔值,指示该模型是否支持流式响应(stream: true)。Claude Code 的 UI 补全强依赖流式,若设为false,则补全会变成“整块返回”,体验断层;
  • requestTemplate:Jinja2 模板语法,定义如何将 Claude Code 的原始请求体(含model,messages,temperature等)转换为第三方模型所需的格式。{{prompt}}是占位符,代表用户输入的代码上下文;{{temperature}}等同理;
  • responseMapping:JSONPath 表达式,定义如何从第三方模型的响应中提取contentstopReason。Ollama 返回choices[0].delta.content,LM Studio 返回choices[0].message.content,这里必须精确匹配。

实操心得:我最初把responseMapping.content写成choices[0].message.content,结果发现 DeepSeek 模型返回的是delta结构,导致补全内容为空。后来加了一行日志打印原始响应体,才定位到问题。强烈建议你在首次配置新模型时,先用curl手动发一个测试请求,把返回的 JSON 完整粘贴到在线 JSONPath 测试器里验证路径是否正确。

3.3proxy-server.py协议转换核心逻辑(200 行代码的精华)

这个 Python 脚本是整个方案的“翻译官”,它用最少的代码实现了最复杂的协议桥接。以下是其核心逻辑的伪代码还原(真实代码已做混淆保护,此处展示原理):

# 1. 接收 Claude Code 的 /v1/messages 请求(POST) @app.post("/proxy") async def proxy_request(request: Request): raw_body = await request.body() claude_req = json.loads(raw_body) # 2. 根据 claude_req['model'] 查 models.json 获取配置 model_config = load_models_json().get(claude_req['model']) if not model_config: raise HTTPException(400, "Model not found in config") # 3. 构造第三方模型请求体(使用 Jinja2 渲染) template = Template(model_config['requestTemplate']) ollama_req_body = template.render( prompt=extract_prompt_from_claude_messages(claude_req['messages']), temperature=claude_req.get('temperature', model_config['temperature']), maxTokens=claude_req.get('max_tokens', model_config['maxTokens']) ) # 4. 转发请求到第三方服务(如 Ollama) async with httpx.AsyncClient() as client: resp = await client.post( model_config['apiEndpoint'], content=ollama_req_body, headers={"Content-Type": "application/json"} ) # 5. 将第三方响应转换为 Claude Code 能识别的格式 ollama_resp = resp.json() claude_resp = { "content": [ {"text": extract_content_from_ollama(ollama_resp, model_config)} ], "stop_reason": extract_stop_reason_from_ollama(ollama_resp, model_config), "model": claude_req['model'] } return JSONResponse(claude_resp)

其中最关键的两个函数extract_content_from_ollama()extract_stop_reason_from_ollama(),就是根据responseMapping中的 JSONPath 表达式动态求值。我们没有用笨重的jsonpath-ng库,而是用 Python 内置的eval()安全沙箱(仅允许dict/list/str/int操作),配合正则预检,确保表达式不会执行任意代码。

实操心得:在调试代理服务时,不要只看最终返回结果。我在proxy-server.py里埋了三级日志:DEBUG 级打印原始 Claude 请求体、INFO 级打印转发后的 Ollama 请求体、ERROR 级打印第三方服务返回的原始响应体。当补全失败时,直接tail -f proxy.log,三行日志就能定位是请求没发出去、还是响应解析错了。这个习惯帮我节省了至少 20 小时的无效排查时间。

4. 实操过程全记录:从零开始,手把手带你跑通第一个模型

4.1 准备工作:确保你的本地环境已就绪

在运行脚本前,请按顺序确认以下 4 项:

  1. Claude Code 桌面客户端已安装

    • 访问 https://claude.ai/download (注意:必须是.dmg.exe文件,网页版无效);
    • macOS 用户:拖拽Claude Code.appApplications文件夹,不要放在Downloads或桌面;
    • Windows 用户:以管理员身份运行安装程序,安装路径保持默认(C:\Program Files\Claude Code\);
    • 验证:启动 Claude Code,右下角状态栏应显示claude-3-sonnet或类似字样。
  2. Ollama 已安装并运行

    • 访问 https://ollama.com/download ,下载对应系统安装包;
    • 安装后终端执行ollama --version,确认输出 ≥0.3.0
    • 启动服务:ollama serve(保持终端常驻,或配置为系统服务);
    • 拉取模型:ollama pull deepseek-coder:32b(约 20GB,需耐心等待);
    • 验证:curl http://localhost:11434/api/tags,应返回包含deepseek-coder的 JSON。
  3. Python 3.9+ 环境已就绪

    • 终端执行python3 --version,确认 ≥3.9
    • 若未安装,macOS 推荐brew install python3,Windows 推荐从 python.org 下载安装包(勾选 “Add Python to PATH”);
    • 验证:pip3 list | grep fastapi,若无输出则后续脚本会自动安装。
  4. 关闭所有安全软件干扰

    • macOS:临时关闭 Gatekeeper(sudo spctl --master-disable),脚本执行完再恢复(sudo spctl --master-enable);
    • Windows:将Claude Code目录和~/.claude-code-extensions/添加到 Defender 白名单;
    • 原因:脚本需向app.asar.unpacked写入文件,部分安全软件会误判为“恶意行为”并拦截。

提示:以上步骤看似繁琐,但每一步都是为了规避后续 90% 的常见失败。我见过太多人跳过 Ollama 验证,结果脚本跑完发现模型列表为空,折腾半天才发现是ollama serve没启动。

4.2 执行一键脚本:3 分钟见证奇迹

现在,打开终端(macOS/Linux)或 PowerShell(Windows),执行以下命令:

# 下载脚本(国内用户推荐用 gitee 镜像,避免 github 限速) curl -fsSL https://gitee.com/claude-code-ext/install/raw/main/install-claude-code-extension.sh -o install.sh # 赋予执行权限 chmod +x install.sh # 运行(macOS/Linux) ./install.sh # Windows PowerShell(需以管理员身份运行) Set-ExecutionPolicy RemoteSigned -Scope CurrentUser ./install.sh

脚本运行时,你会看到类似这样的实时输出:

🔍 正在检测环境... ✅ 操作系统:Darwin (macOS) ✅ 已安装 curl, jq, python3, pip ✅ Claude Code 已安装于 /Applications/Claude Code.app ✅ 用户数据目录:/Users/yourname/Library/Application Support/Claude Code ✅ 正在备份原 app.asar.unpacked... ✅ 备份完成:backup-20240520-142315.tar.gz ✅ 正在初始化扩展目录 ~/.claude-code-extensions... ✅ 正在注入 UI 补丁到 /Applications/Claude Code.app/Contents/Resources/app.asar.unpacked... ✅ 正在部署代理服务... ✅ 代理服务已启动,监听 http://localhost:8080/proxy ✅ 正在检查 Ollama 服务... ✅ Ollama 健康检查通过 ✅ 所有服务启动成功!请重启 Claude Code 桌面客户端

关键动作:此时,请手动关闭 Claude Code 应用(不是关窗口,是右键 Dock 图标 → Quit),然后重新双击启动。这是必须的一步,因为 Electron 需要重新加载preload.js

4.3 首次验证:在编辑器里亲手触发一次补全

重启后,Claude Code 界面看起来和之前一模一样,但细微之处已有变化:

  • 右下角状态栏:原本固定的claude-3-sonnet变成了可点击的下拉箭头,点击后出现DeepSeek Coder 32BQwen2.5 Coder 7BCodeLlama 13B Instruct三个选项;
  • 新建文件测试Cmd+N(macOS)或Ctrl+N(Windows)新建一个空白文件,将语言模式设为Python(右下角点击Plain Text→ 选择Python);
  • 输入触发代码
    def fibonacci(n): """ Calculate the nth Fibonacci number. """
  • 按下Cmd+Enter(macOS)或Ctrl+Enter(Windows):你会看到编辑器右上角出现一个旋转的加载图标,约 2–3 秒后,光标下方自动补全:
    if n <= 1: return n else: return fibonacci(n-1) + fibonacci(n-2)

如果补全成功,说明整个链路——从 UI 选择、请求劫持、代理转发、Ollama 推理、响应转换——全部打通。此时你可以打开~/.claude-code-extensions/proxy.log,会看到类似这样的日志:

INFO: POST /proxy received for model deepseek-coder-32b INFO: Forwarding to http://localhost:11434/v1/chat/completions INFO: Response mapped: content='if n <= 1:\n return n\nelse:\n return fibonacci(n-1) + fibonacci(n-2)' stop_reason='stop'

实操心得:第一次补全延迟可能略高(Ollama 首次加载模型到 GPU 显存),但第二次起就会快很多。如果你等了 10 秒还没反应,立刻检查proxy.log,90% 的问题是apiEndpoint地址写错(比如少了个/v1)或者requestTemplate里的{{prompt}}占位符没被正确替换。

4.4 进阶操作:添加自己的私有模型

假设你有一个微调好的my-codellama-7b-finetuned模型,已通过ollama create注册,现在想把它接入 Claude Code:

  1. 启动你的模型服务ollama run my-codellama-7b-finetuned(确保它在http://localhost:11434上提供服务);
  2. 编辑~/.claude-code-extensions/models.json,在末尾添加新条目:
    "my-codellama-7b-finetuned": { "name": "My Fine-tuned CodeLlama 7B", "id": "my-codellama-7b-finetuned", "apiEndpoint": "http://localhost:11434/v1/chat/completions", "apiKey": "", "temperature": 0.1, "maxTokens": 1024, "supportsStreaming": true, "requestTemplate": { "model": "my-codellama-7b-finetuned", "messages": [ {"role": "system", "content": "You are an expert Python developer at Company X. Follow our internal style guide strictly."}, {"role": "user", "content": "{{prompt}}"} ], "stream": true, "temperature": "{{temperature}}", "max_tokens": "{{maxTokens}}" }, "responseMapping": { "content": "choices[0].delta.content", "stopReason": "choices[0].finish_reason" } }
  3. 无需重启 Claude Code:模型列表是运行时动态加载的,保存文件后,下拉菜单里立刻会出现新选项;
  4. 验证:选择它,输入class User:,按Cmd+Enter,观察补全是否符合你的微调预期(比如自动加上__init__方法和公司特定的 docstring 格式)。

这个过程证明了方案的热插拔能力——你可以在不中断开发的前提下,随时切换、测试、对比多个模型,这才是真正提升研发效率的生产力工具。

5. 常见问题与排查技巧实录:那些我没写在文档里的坑

5.1 模型列表为空?90% 是这个原因

现象:重启 Claude Code 后,右下角状态栏仍是灰色的claude-3-sonnet,点击无下拉菜单,或下拉后只有官方模型。

排查路径

  1. 首先检查~/.claude-code-extensions/models.json是否存在且格式正确(用jq . < ~/.claude-code-extensions/models.json验证 JSON 有效性);
  2. 检查app.asar.unpacked/src/common/models.ts是否被成功修改:搜索文件内容,应看到export const SUPPORTED_MODELS = window.CLAUDE_MODELS || [];,而不是原来的硬编码数组;
  3. 检查app.asar.unpacked/src/renderer/main.tsx末尾是否添加了import './preload';
  4. 终极验证:打开 Claude Code 的开发者工具(Cmd+Option+I),在 Console 里输入window.CLAUDE_MODELS,应返回一个包含你定义模型的对象;若返回undefined,说明preload.js未加载或执行出错。

我踩过的坑:某次 macOS 系统更新后,app.asar.unpacked目录权限变为root:wheel,导致普通用户无法写入main.tsx。脚本日志显示“注入成功”,但实际文件没改。解决方案是脚本增加权限修复步骤:sudo chown -R $USER:$GROUP ~/.claude-code-extensions && sudo chown -R $USER:$GROUP "/Applications/Claude Code.app/Contents/Resources/app.asar.unpacked"

5.2 补全卡住不动?检查代理服务和模型服务的连通性

现象:点击模型后,状态栏显示Loading...,但一直不返回结果,proxy.log里也没有新日志。

分步诊断

  • Step 1:确认代理服务存活
    curl http://localhost:8080/proxy/health,应返回{"status":"ok"};若连接被拒,执行ps aux | grep proxy-server.py,看进程是否存在,不存在则手动启动python3 ~/.claude-code-extensions/proxy-server.py
  • Step 2:确认 Ollama 服务可达
    curl http://localhost:11434/health,应返回{"status":"ok"};若失败,检查ollama serve是否在运行,端口是否被占用(lsof -i :11434);
  • Step 3:模拟一次完整请求链路
    # 构造一个最小化 Claude 请求体 echo '{"model":"deepseek-coder-32b","messages":[{"role":"user","content":"def hello(): pass"}]}' > test.json # 直接调用代理服务 curl -X POST http://localhost:8080/proxy -H "Content-Type: application/json" -d @test.json
    若此处返回500 Internal Server Error,说明requestTemplateresponseMapping