
Claude Code Router 扩展机制完全指南从加载原理到从零开发 Wrapper Plugin【免费下载链接】claude-code-routerOne local control plane for every AI agent: route across models, fuse new capabilities, orchestrate tools, and stay fully in control.项目地址: https://gitcode.com/GitHub_Trending/cl/claude-code-routerCCRClaude Code Router通过一套双层、声明式与命令式并存的扩展机制把本地 HTTP 路由、代理流量转发、内置浏览器入口、供应商账号用量读取乃至 core gateway 内部能力全部开放给插件作者。本文以 官方中文文档 为骨架结合仓库内插件服务、后端服务等源码实现讲解扩展的类型体系、加载与生命周期、ctx能力面并带你在 10 分钟内创建、安装、调试出第一个可用的本地扩展。扩展的两层体系Wrapper plugin 与 Core gateway pluginCCR 的扩展分为两层二者运行位置不同、能力边界不同新手应从这里开始定位自己的需求类型配置位置运行位置适合做什么Wrapper pluginpluginsCCR Desktop 的 Electron wrapper 进程注册本地 HTTP 路由、启动本地后端、拦截代理流量、添加内置浏览器入口、连接供应商账号用量Core gateway pluginproviderPlugins或plugins[].coreGateway.providerPluginscore gateway runtime扩展上游供应商、认证方式或 core gateway 内部能力绝大多数用户自定义扩展应该从Wrapper plugin起步。它运行在更外层能拿到 CCR 配置、私有数据目录和带前缀的日志对象并通过ctx注册能力。这与源码结构相互印证网关侧有一个全局单例的GatewayPluginService见 service.ts它负责收集、匹配与路由转发扩展注册的资源而 Wrapper 层就是它的宿主。一个扩展包三个运行面plugins[]是扩展包的安装单位一个扩展包可以同时向三个独立运行面暴露能力运行面配置键启用内容Appsurfaces.apps来自apps或ctx.registerApp的内置浏览器入口Gatewaysurfaces.gateway网关路由、代理路由、HTTP 后端、core gateway 配置和虚拟模型配置Providersurfaces.providerCore provider plugins 和 Provider 账号连接器三条设计细节值得注意向后兼容三个运行面默认全部启用。把某个运行面显式设为false可以保留扩展包但禁用对应能力。静态 App 入口只声明apps且不配置module时不需要执行任何 JavaScript这种纯声明式扩展安全面最小。动态 App 入口仍可经 App 运行面由 JavaScript 运行时注册此时必须声明trusted-code权限。在源码层运行面与权限都有明确的受控集合。app.ts 定义了三个 surface IDapps/gateway/provider与 12 个 permission IDtrusted-code、apps、gateway-routes、proxy-routes、http-backends、provider-account-connectors、gateway-request-transforms、core-gateway-config、core-provider-plugins、virtual-model-profiles、sqlite-store、system-launcher。仓库内置的 claude-design/plugin.json 就是一个三运行面声明范本apps/gateway打开、provider关闭。加载机制与扩展生命周期网关启动时CCR 读取配置中的plugins数组按顺序处理每个enabled ! false的扩展完整流程如下按启用运行面应用静态配置App 运行面应用appsGateway 运行面应用proxy.routes、coreGateway.virtualModelProfiles与coreGateway.configProvider 运行面应用coreGateway.providerPlugins。按需加载 JavaScript只要任一启用运行面需要 JS 注册能力就加载扩展模块。module必须解析到明确的本地 JavaScript 文件绝对路径、~/开头路径、或相对 CCR 配置目录的./...路径均合法。强制声明trusted-code任何通过module加载 JS 的扩展都必须显式声明该权限。注意——权限不是操作系统级沙箱它的作用是限制 CCR 插件 API并把执行本地代码这一信任边界显式化。无兜底不配置moduleCCR 不再加载任何内置兜底扩展。模块形态模块可导出函数或导出含setup(ctx)/activate(ctx)的对象。停止反向钩子扩展停止时CCR 反向执行stop、onStop钩子再关闭该扩展注册的 HTTP 后端与 SQLite store。加载失败时服务会自动回滚从 service.ts 可以看到单插件启动异常会被捕获回滚到加载前的快照并输出[plugin:id] Disabled after startup failure: ...避免一个坏插件拖垮整个网关。模块的两种常见导出形态对象形式含可选的stop钩子use strict; module.exports { async setup(ctx) { ctx.logger.info(extension loaded); }, async stop() { // 可选释放扩展自己持有的资源。 } };函数形式直接导出 setup 函数use strict; module.exports async function setup(ctx) { ctx.logger.info(loaded ${ctx.pluginId}); };setup(ctx)/activate(ctx)既可以直接调用ctx.register...方法也可以返回一个注册对象。返回对象支持的字段为apps、gatewayRoutes、proxyRoutes、providerAccountConnectors、coreGateway、virtualModelProfiles、stop和onStop。源码 service.ts 会按返回值逐类校验 surface 与 permission 后完成注册。关于 module 路径解析的硬约束源码resolveLocalModulePath见 service.ts揭示了三条底层规则支持~展开expandHome但必须是本地绝对路径或以.开头的显式相对路径不允许包名 specifier也不允许 URL / 协议前缀。相对路径以 CCR 配置目录为基准解析且必须落在配置目录内部越界会直接抛错。最终解析结果扩展名必须为.cjs、.js或.mjs见assertJavaScriptModulePath。加载时还会做模块级缓存清除与?v...cache-bust 处理loadPluginModule保证网关重启后能拿到扩展的最新代码。ctx 能力参考Wrapper plugin 的全部抓手setup(ctx)收到的ctx提供以下常用字段与方法字段或方法说明ctx.pluginId当前扩展 IDctx.pluginConfigplugins[].config中的自定义配置ctx.config当前 CCR AppConfig 快照ctx.logger带[plugin:id]前缀的debug/info/warn/error日志ctx.paths.configDirCCR 配置目录ctx.paths.dataDirCCR 数据目录ctx.paths.pluginDataDir当前扩展专属数据目录源码实现在path.join(DATADIR, plugins, id)见 service.tsctx.registerGatewayRoute(route)在 CCR 网关上注册本地 HTTP 路由ctx.registerHttpBackend(backend)启动一个本地 HTTP 后端返回{ url, host, port }ctx.registerProxyRoute(route)把代理模式捕获到的某个 host/path 转发到扩展后端或其他 upstreamctx.registerApp(app)在内置浏览器应用列表里添加入口ctx.openSqliteStore(options)在扩展数据目录打开 SQLite storectx.registerProviderAccountConnector(connector)注册供应商账号余额或额度读取器ctx.registerCoreGatewayProviderPlugin(plugin)向 core gateway 注入 provider pluginctx.registerCoreGatewayVirtualModelProfile(profile)向 core gateway 注入虚拟模型配置Provider account connector 的特殊请求通道账号类 connector 的resolve(request)会收到request.fetchProviderAccountJson({ endpoint, method, requestOrigin, credentials, headers, body, timeoutMs })。它经由 CCR Desktop 的内置浏览器会话发起请求因此设置credentials: include就能为同源账号 API 自动带上浏览器 Cookie。这与 account-webcontent 侧的浏览器化抓取通道形成对应使读取供应商账号余额/额度不必自行维护登录态。Gateway route handler 的 helper网关路由处理器额外收到三个 helperHelper说明helpers.readBody(request)读取请求 body返回Bufferhelpers.readJson(request)读取并解析 JSON bodyhelpers.sendJson(response, statusCode, body)返回 JSON 响应底层实现见 service.tsreadJson本质是readBody后JSON.parsesendJson写入application/json头并输出一行 JSON。网关路由的鉴权模型registerGatewayRoute默认auth: gateway。若 CCR 配置了 API Key请求必须带Authorization: Bearer key或x-api-key: key只有调试用路由或本地公开状态页才建议auth: none。此外路由支持path精确匹配与pathPrefix前缀匹配两种形态源码matchGatewayRoute会先按方法过滤再匹配路径service.ts。从零创建第一个扩展hello-extension新建目录~/ccr-extensions/hello-extension结构如下hello-extension/ plugin.json index.cjsplugin.json让本地扩展选择器认识你plugin.json供 CCR 的本地扩展选择器识别扩展 ID、名称与入口文件{ id: hello-extension, name: Hello Extension, module: index.cjs, surfaces: [apps, gateway], permissions: [trusted-code, apps, gateway-routes, http-backends, proxy-routes], apps: [ { id: hello-status, name: Hello Status, url: http://127.0.0.1:3456/plugins/hello } ] }其中permissions数组声明的每一项都必须来自上面列出的 12 个 permission IDsurfaces也可写成对象形式如{ apps: true, gateway: true, provider: false }静态声明的apps会在 Gateway 启动时由registerConfiguredApps注入内置浏览器列表。index.cjs注册状态路由 echo 后端 代理转发use strict; module.exports { async setup(ctx) { ctx.registerGatewayRoute({ auth: none, id: hello-status, method: GET, path: /plugins/hello, handler(_request, response, helpers) { helpers.sendJson(response, 200, { ok: true, plugin: ctx.pluginId, message: ctx.pluginConfig?.message || hello from CCR }); } }); const backend await ctx.registerHttpBackend({ id: hello-echo, async handler(request, response, helpers) { const body request.method POST ? (await helpers.readBody(request)).toString(utf8) : ; helpers.sendJson(response, 200, { method: request.method, path: request.url, body }); } }); ctx.registerProxyRoute({ host: api.example.local, id: hello-example-api, paths: [/v1], preserveHost: true, upstream: backend.url }); ctx.logger.info(hello backend listening at ${backend.url}); } };这个扩展最终暴露三样东西GET /plugins/hello直接挂在 CCR 网关上用于验证扩展是否加载成功一个本地 echo 后端CCR 自动分配空闲端口registerHttpBackend会返回含url的注册信息宿主实现在 backend-service.ts不传port时以0让系统自动分配一个代理规则当代理模式捕获到api.example.local/v1...的流量时转发到 echo 后端。后端服务层backend-service.ts是理解registerHttpBackend与openSqliteStore的关键所有后端与 SQLite store 都按ownerId即插件 ID登记网关停止或插件失败回滚时通过stopOwner一次性回收 HTTP server 与数据库连接backend-service.ts。SQLite store 默认文件名为pluginId.sqlite落在ctx.paths.pluginDataDir下。安装扩展推荐通过桌面 UI 安装本地扩展打开Extensions页面点击添加扩展选择本地扩展目录选择刚创建的hello-extension目录保存配置打开Server页面重启网关。CCR 的运行配置存储在 SQLite中。扩展请通过 UI 添加旧版 JSON 配置文件仅作参考。保存扩展配置后需要重启网关配置数据库位置见 配置数据库位置。扩展条目在配置中的结构形态如下{ plugins: [ { id: hello-extension, enabled: true, module: /Users/you/ccr-extensions/hello-extension/index.cjs, surfaces: { apps: true, gateway: true, provider: false }, permissions: [trusted-code, apps, gateway-routes, http-backends, proxy-routes], config: { message: hello from my config } } ] }注意module这里配置为机器上的绝对路径配置完成后在setup(ctx)里通过ctx.pluginConfig.message即可读到自定义配置。本地目录选择器的入口识别顺序UI 的本地目录选择器按顺序识别以下入口信息plugin.jsonccr-plugin.json.ccr-plugin/plugin.json.codex-plugin/plugin.jsonpackage.json中的main、ccr.module或ccrPlugin.module若没有任何显式入口文件CCR 会依次尝试目录下的index.cjs、index.mjs、index.js、plugin.cjs、plugin.mjs、plugin.js。这一发现机制也兼容了其他 Agent 工具链如.codex-plugin/plugin.json的约定方便迁移既有插件。调试扩展从语法到行为的分步验证第 1 步先做语法检查CommonJS 扩展直接运行 Node 语法检查node --check ~/ccr-extensions/hello-extension/index.cjs如果扩展依赖 npm 包先在扩展目录安装依赖并确认入口文件能被 Node 正常解析。第 2 步用源码模式启动 CCR在 CCR 仓库根目录运行npm install npm run dev扩展里的ctx.logger.info/warn/error会打印在启动 CCR 的终端中前缀类似[plugin:hello-extension]createPluginLogger即如此构造前缀见 service.ts。若加载失败终端里会看到[plugin:hello-extension] Disabled after startup failure: ...。第 3 步验证 Gateway route启动网关后直接请求状态路由curl http://127.0.0.1:3456/plugins/hello若路由使用默认的auth: gateway且 CCR 已配置 API Key则需要带鉴权头两种方式均可curl -H Authorization: Bearer CCR_API_KEY http://127.0.0.1:3456/plugins/hellocurl -H x-api-key: CCR_API_KEY http://127.0.0.1:3456/plugins/hello第 4 步验证 HTTP 后端与代理规则registerHttpBackend返回的backend.url已由示例写入日志。先直接请求该地址确认后端工作正常再开启代理模式验证目标 host/path 是否命中registerProxyRoute的转发。代理规则匹配逻辑对应源码matchesHost/matchProxyRoute/buildPluginProxyUpstreamUrl见 service.tshost必须匹配目标 hostname支持精确 host、.example.com后缀与*.example.com通配三种形态paths为空时匹配该 host 的所有路径多个 path 同时命中时CCR 选择最长的 path prefixstripPathPrefix会从转发路径中移除匹配前缀传true表示移除已匹配的前缀也可以传字符串指定要剥掉的前缀rewritePathPrefix会把匹配前缀替换成指定前缀后转发。第 5 步常见问题排查现象排查方向扩展没有加载检查plugins[].enabled、plugins[].module路径和终端里的[plugin:id]报错GET /plugins/hello返回 404确认网关已重启路由path或pathPrefix是否以/开头源码normalizeRoutePath会为缺失/的路由自动补全但注册前务必写对返回 401路由默认需要 gateway API Key调试路由可显式设置auth: none修改代码不生效Wrapper plugin 会在网关重启时重新加载只有进程卡住时才需要重启 CCR端口被占用registerHttpBackend不传port会自动分配端口固定端口冲突时改回自动分配代理规则不命中检查代理模式是否开启、证书是否安装、host 是否匹配真实请求的 hostname与仓库内置扩展对照claude-design 是最好范本如果希望看到生产级扩展的完整写法仓库里自带两个随 CCR Desktop 分发的捆绑插件。以 claude-design/plugin.json 为例它声明了appsgateway两个运行面、provider: falsepermissions恰好覆盖trusted-code、apps、gateway-routes、proxy-routes、http-backends、sqlite-store六项——与你在本文中学会的权限模型完全一致其 index.cjs 开头通过process.env.CCR_*读取可调参数之后注册模型发现、路由转发与本地资源服务是理解Gateway 路由 代理 内置 App三合一能力的绝佳真实样本。另外值得知道的是内核为已知扩展维护了默认权限/运行面表见 app.ts内置的claude-design、claude-ship、cursor-proxy即使未显式声明也能获得合理默认自定义扩展则必须显式声明。安全建议只有状态页、健康检查或本机调试路由才使用auth: none不要在日志里打印 API Key、OAuth token、Cookie 或完整请求头扩展写文件时优先使用ctx.paths.pluginDataDir它已按插件 ID 做了目录隔离sanitizeFileSegment会过滤不安全字符见 service.ts对readJson得到的外部输入做类型校验它只做JSON.parse不校验结构代理转发到外部 upstream 时明确处理 header 白名单避免把本地鉴权信息转发到不可信服务。小结一次完整的扩展开发循环通常是这样闭合的写plugin.json声明 ID、surface、module 与 permissions → 在index.cjs的setup(ctx)里注册网关路由、后端与代理规则 → 通过 UI 添加本地目录并重启网关 → 用curl与终端日志逐层验证。理解plugins[]安装单位、三个运行面能力分组与 12 个权限 ID能力边界这组模型后你就能用 CCR 的内置浏览器、网关路由与代理管道拼装出属于自己的 AI Agent 控制面能力。【免费下载链接】claude-code-routerOne local control plane for every AI agent: route across models, fuse new capabilities, orchestrate tools, and stay fully in control.项目地址: https://gitcode.com/GitHub_Trending/cl/claude-code-router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考