ezllm 0.0.35 → 0.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -11
- package/dist/cpa.js +15 -0
- package/dist/daemon.js +8 -8
- package/dist/server.js +84 -78
- package/dist/web-ui/assets/DashboardView-BzKKC4LZ.js +1 -0
- package/dist/web-ui/assets/{LayoutView-Fp0wV2sP.js → LayoutView-BzBSYjz8.js} +1 -1
- package/dist/web-ui/assets/{LoginView-Cp1ax76n.js → LoginView-Bzngu7qF.js} +1 -1
- package/dist/web-ui/assets/LogsView-C2cZgB5T.js +5 -0
- package/dist/web-ui/assets/LogsView-u-0B5Xpc.css +1 -0
- package/dist/web-ui/assets/{ManagedServicesView-DOLSalxH.js → ManagedServicesView-5QT_g-5a.js} +2 -2
- package/dist/web-ui/assets/ProvidersView-BPOzev6x.js +14 -0
- package/dist/web-ui/assets/{ServiceStatusView-C0AeLX4Y.js → ServiceStatusView--D1wfNAZ.js} +2 -2
- package/dist/web-ui/assets/SettingsView-DygfiGG8.css +1 -0
- package/dist/web-ui/assets/SettingsView-XXHJh2h8.js +1 -0
- package/dist/web-ui/assets/SetupView-CSBKlfcJ.css +1 -0
- package/dist/web-ui/assets/SetupView-DcThD5bd.js +1 -0
- package/dist/web-ui/assets/{config-BxgqmKV-.js → config-B8sfBKCc.js} +1 -1
- package/dist/web-ui/assets/format-CO5kjbrg.js +1 -0
- package/dist/web-ui/assets/{index-D8oQwfo2.js → index-CZeCUjra.js} +1 -1
- package/dist/web-ui/assets/{index-wQvjidwN.css → index-s_h6GEbv.css} +1 -1
- package/dist/web-ui/assets/{message-BjuSIfYg.js → message-sILMvZIb.js} +1 -1
- package/dist/web-ui/assets/{providers-_RIyi_BK.js → providers-dvoMyAle.js} +1 -1
- package/dist/web-ui/assets/{router-BvcZ7vIS.js → router-BUuAm5No.js} +2 -2
- package/dist/web-ui/assets/useManagerApi-XPGaswMw.js +1 -0
- package/dist/web-ui/assets/{useQuickRangeFilter-DWNXdVRp.js → useQuickRangeFilter-Bg2q_vMO.js} +1 -1
- package/dist/web-ui/assets/useToast-BscapoHF.js +1 -0
- package/dist/web-ui/index.html +4 -4
- package/package.json +2 -2
- package/dist/web-ui/assets/DashboardView-DAO_cq5T.js +0 -1
- package/dist/web-ui/assets/LogsView-CGqXceTR.js +0 -5
- package/dist/web-ui/assets/LogsView-D7FTeaYQ.css +0 -1
- package/dist/web-ui/assets/ProvidersView-DJ3UZ6mQ.js +0 -14
- package/dist/web-ui/assets/SettingsView-D-LAM-Z6.js +0 -1
- package/dist/web-ui/assets/SettingsView-PV2z7fD7.css +0 -1
- package/dist/web-ui/assets/SetupView-BLl-ZFOA.css +0 -1
- package/dist/web-ui/assets/SetupView-D8qgF9IQ.js +0 -1
- package/dist/web-ui/assets/format-Z1cpFfkW.js +0 -1
- package/dist/web-ui/assets/useCopyToast-Bcy_kRd6.js +0 -1
- package/dist/web-ui/assets/useManagerApi-BjlLSy7P.js +0 -1
- package/dist/web-ui/assets/useToast-BlwcxEqd.js +0 -1
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# ezllm
|
|
2
2
|
|
|
3
|
-
**轻量、安全的本地 LLM
|
|
3
|
+
**轻量、安全的本地 LLM 网关**——为个人本地使用设计:一条命令安装,一条命令启动,把你的 OpenAI / Anthropic 协议供应商和 API Key 统一收口为一个本地入口。
|
|
4
4
|
|
|
5
|
-
- **部署成本极低**:单进程、单端口(默认 29770
|
|
6
|
-
- **快速启动**:`
|
|
7
|
-
-
|
|
5
|
+
- **部署成本极低**:单进程、单端口(默认 29770),常驻内存约 50–60 MB,你的日常电脑就能跑,无需服务器
|
|
6
|
+
- **快速启动**:`npx ezllm install` 一条命令装好并自启动——自带托管 Node 运行时、自动注册 `ezllm` 命令,首次启动自动创建示例配置,网页向导两分钟完成接入
|
|
7
|
+
- **特别安全**:无遥测、无上报、无云端组件——详见「安全与隐私」
|
|
8
8
|
|
|
9
9
|
## 安全与隐私
|
|
10
10
|
|
|
@@ -15,16 +15,18 @@
|
|
|
15
15
|
## 功能特性
|
|
16
16
|
|
|
17
17
|
- **协议互转**:客户端与上游可各用各的协议(OpenAI / Anthropic),网关自动转换
|
|
18
|
-
- **多供应商路由**:多家供应商 / 多个 API Key 统一管理,按模型路由、网页在线切换;内置 deepseek、kimi、zhipu、ark、openai 等 10 个供应商模板
|
|
19
|
-
- **管理界面**:供应商管理、连通性测试、用量统计、请求日志,开箱即用零学习成本
|
|
18
|
+
- **多供应商路由**:多家供应商 / 多个 API Key 统一管理,按模型路由、网页在线切换;内置 deepseek、kimi、zhipu、ark、dashscope、openai 等 10 个供应商模板
|
|
20
19
|
- **一键接入 AI 客户端**:网页上点一下,把 Claude Code、Codex、DeepSeek Harness(dsh)的流量指向本机网关(自动备份原配置、可还原)
|
|
20
|
+
- **额度快照**:内置多家供应商的额度查询(DeepSeek、智谱、火山方舟、CPA 等),余量一目了然
|
|
21
|
+
- **管理界面**:供应商管理、连通性测试、用量统计、请求日志,开箱即用零学习成本
|
|
22
|
+
- **配置即文件**:所有功能设置(供应商、路由、密钥、端口、界面与安全)集中在一个可读的 `ezllm.config.json`(应用目录内)——管理界面就是它的编辑器,改动内存热更新、无需重启;CLI 只管进程生命周期,没有散落在命令行的隐藏开关。配置可备份、可整份迁移,排障时直接看文件即可
|
|
21
23
|
- **开机自启**:三平台支持,登录即启动,免管理员权限
|
|
22
|
-
-
|
|
24
|
+
- **自动更新**(可选):发现新版本一键更新(`ezllm update`);安装失败或安装后版本与目标不符时**不自动回退**——清掉待更新标记、把原因记入 `lastApply.error`,服务以旧版本继续运行(日志里会给出回退用的命令)。自建 registry / 镜像源可用 `ELLM_UPDATE_REGISTRY` 指定
|
|
23
25
|
|
|
24
26
|
## 安装
|
|
25
27
|
|
|
26
28
|
```bash
|
|
27
|
-
npx ezllm install # 推荐:装配自带 Node
|
|
29
|
+
npx ezllm install # 推荐:装配自带 Node 运行时的环境(装完自动启动;命令常驻可用,不受本机 node 版本影响)
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
也可以直接全局安装(命令可用性依赖本机 node 环境):
|
|
@@ -33,7 +35,9 @@ npx ezllm install # 推荐:装配自带 Node 运行时的环境(命令常
|
|
|
33
35
|
npm i -g ezllm
|
|
34
36
|
```
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
**为什么不用管 Node 版本**:服务启动**总是**用托管运行时(应用目录下的 `node/`,缺失时自动下载一次并缓存,之后复用),与本机已装的 node 无关;`ezllm install` 还会把包装配到用户目录(默认 `~/.ezllm`)并把 `ezllm` 命令注册进用户 PATH。
|
|
39
|
+
|
|
40
|
+
**国内网络友好**:运行时下载自带镜像回退(npmmirror → 官方源),装不上会自动换源重试;服务更新可用 `ELLM_UPDATE_REGISTRY` 指定自建 registry。
|
|
37
41
|
|
|
38
42
|
## 使用方法
|
|
39
43
|
|
|
@@ -43,7 +47,7 @@ ezllm start # 后台常驻启动(首次自动创建示例配置)
|
|
|
43
47
|
|
|
44
48
|
1. 打开管理界面 `http://127.0.0.1:29770/`,首次访问进入**首启向导**:选择部署形态(本机使用 / 局域网公开),设置主密钥与登录账号(本机使用可全部跳过)
|
|
45
49
|
2. 在「供应商」页添加你的供应商(选内置模板,填 API Key 即可)
|
|
46
|
-
3.
|
|
50
|
+
3. 接入客户端:推荐在「设置 → 代理开关」**一键接入**(Claude Code / Codex / dsh,自动备份原配置、可还原);手动接入则是 Claude Code 指向 `http://127.0.0.1:29770`、OpenAI SDK 指向 `http://127.0.0.1:29770/v1`
|
|
47
51
|
|
|
48
52
|
常用命令:
|
|
49
53
|
|
|
@@ -51,6 +55,7 @@ ezllm start # 后台常驻启动(首次自动创建示例配置)
|
|
|
51
55
|
ezllm # 显示当前运行状态(服务地址、pid;只读,不启动服务)
|
|
52
56
|
ezllm stop # 停止
|
|
53
57
|
ezllm restart # 重启
|
|
58
|
+
ezllm update # 检查并安装新版本
|
|
54
59
|
ezllm install # 装配/修复自带 Node 运行时的环境(幂等;自动迁移旧数据目录)
|
|
55
60
|
```
|
|
56
61
|
|
|
@@ -66,7 +71,7 @@ npx ezllm install --force # 重装(清理应用目录后重新装配)
|
|
|
66
71
|
|
|
67
72
|
如果某个客户端每轮都在请求里塞大量固定内容(工具描述里内嵌的 API 手册、注入的记忆索引等),可以在网关侧裁掉,从根上省 token。
|
|
68
73
|
|
|
69
|
-
把发布包里的
|
|
74
|
+
把发布包里的 **`hook.example.cjs`** 复制到应用目录(默认 `~/.ezllm`)即可,无需改名。按注释改里面的规则,然后 `ezllm restart`。
|
|
70
75
|
|
|
71
76
|
- **候选文件名**:`hook.cjs` → `hook.mjs` → `hook.js`,取第一个存在的。**推荐 `hook.cjs`**——`.js` 的解析模式(CJS 还是 ESM)取决于它祖先目录里最近的 `package.json`,不受你控制(比如家目录若有一份 `"type": "module"` 的 package.json,`hook.js` 里的 CJS 写法就会失效);`.cjs` 强制按 CJS 解析,任何环境都稳。
|
|
72
77
|
- **只有请求侧改写**一个钩子(`onModelRequest`):在发往上游前改请求体/请求头,**改不了响应**。
|
package/dist/cpa.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{join as e,resolve as t}from"node:path";import{pathToFileURL as n}from"node:url";import{appendFileSync as r,existsSync as i,readFileSync as a,readdirSync as o,statSync as s}from"node:fs";import{randomBytes as c,randomUUID as l}from"node:crypto";import{homedir as u}from"node:os";import{rename as d,writeFile as f}from"node:fs/promises";import{createServer as p}from"node:http";function m(e,...t){console.log(e,...t)}function h(e,...t){console.warn(e,...t)}function g(e,...t){console.error(e,...t)}var _=class extends Error{status;detail;constructor(e,t,n){super(t),this.name=`CpaHttpError`,this.status=e,this.detail=n}},v=class extends Error{code=`BODY_TOO_LARGE`;constructor(){super(`request body too large`),this.name=`BodyTooLargeError`}};function y(e,t){return{error:{message:e,type:t}}}function b(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function x(e){let t=e.search(/[?#]/);return t>=0?e.slice(0,t):e}function S(e,t,n){if(e.headersSent){try{e.end()}catch{}return}e.writeHead(t,{"Content-Type":`application/json`}),e.end(JSON.stringify(n))}function C(e,t,n){S(e,t,{detail:n})}function w(e,t){C(e,400,y(t,`invalid_request_error`))}function T(e=process.env){return Number(e.PROXY_MAX_BODY||33554432)}function ee(e,t){return new Promise((n,r)=>{let i=[],a=0;e.on(`data`,n=>{if(a+=n.length,a>t){e.destroy(),r(new v);return}i.push(n)}),e.on(`end`,()=>{n(Buffer.concat(i))}),e.on(`error`,r)})}function te(e){return e.length===0?{}:JSON.parse(e.toString(`utf8`))}async function*E(e){if(!e)return;let t=e.getReader();try{for(;;){let{done:e,value:n}=await t.read();if(e)break;n&&(yield Buffer.isBuffer(n)?n:Buffer.from(n))}}finally{try{t.releaseLock()}catch{}}}function D(){let e=new Date,t=e=>String(e).padStart(2,`0`);return`${String(e.getFullYear())}-${t(e.getMonth()+1)}-${t(e.getDate())} ${t(e.getHours())}:${t(e.getMinutes())}:${t(e.getSeconds())}`}var O=class{#e;constructor(e){this.#e=e}get enabled(){return this.#e!==null}log(e){let t=this.#e;if(t!==null)try{r(t,`[${D()}] ${e}\n`,`utf8`)}catch{}}};function k(e,t=80){let n=String(e??``).replace(/\n/g,` `).trim();return n.length>t?`${n.slice(0,t)}…`:n}function ne(e){try{return JSON.stringify(String(e))}catch{return String(e)}}function A(e,t=2){return JSON.stringify(e,null,t)}function j(e){return`[${e.map(e=>typeof e==`string`?`'${e}'`:String(e)).join(`, `)}]`}var M=class{options;constructor(e){this.options=e}init(){return Promise.resolve({ok:!0})}assertChatReady(){}transformBody(e){return{body:e}}usage(){return Promise.resolve(null)}};function N(...e){return e.find(e=>e!==void 0&&e!==``)??null}function P(e){return e.toLowerCase()===`jetbrains`?`jetbrains`:`auto`}function F(e,t=process.env){let n={host:t.PROXY_HOST||`127.0.0.1`,port:Number(t.PROXY_PORT||8787),apiKey:t.CODEBUDDY2OPENAI_KEY||``,log:null,desensitize:!1,noCompact:!1,noReasoning:!1,skipCheck:!1,credSource:t.CODEBUDDY_CRED_SOURCE||``,provider:t.CPA_PROVIDER||``,help:!1};for(let t=0;t<e.length;t+=1){let r=e[t]??``,i=r.indexOf(`=`),a=r.startsWith(`--`)&&i>=0?r.slice(0,i):r,o=r.startsWith(`--`)&&i>=0?r.slice(i+1):void 0,s=()=>o===void 0?e[++t]:o;switch(a){case`--host`:n.host=s()||n.host;break;case`--port`:n.port=Number(s());break;case`--api-key`:n.apiKey=s()||``;break;case`--log`:n.log=s()||null;break;case`--desensitize`:n.desensitize=!0;break;case`--no-compact`:n.noCompact=!0;break;case`--no-reasoning`:n.noReasoning=!0;break;case`--cred-source`:n.credSource=s()||``;break;case`--provider`:n.provider=s()||n.provider;break;case`--skip-check`:n.skipCheck=!0;break;case`--help`:case`-h`:n.help=!0}}return{help:n.help,options:{host:n.host,port:n.port,apiKey:n.apiKey,logPath:N(n.log??void 0,t.CODEBUDDY2OPENAI_LOG,t.PROXY_LOG),desensitize:n.desensitize,noCompact:n.noCompact,noReasoning:n.noReasoning,credSource:P(n.credSource),skipCheck:n.skipCheck,providerId:n.provider||`codebuddy`}}}function re(e=[`codebuddy`]){return[`cpa(CodeBuddy/WorkBuddy 订阅代理,OpenAI 兼容)`,``,`用法: cpa [选项]`,` --host HOST 监听地址(默认 127.0.0.1)`,` --port PORT 监听端口(默认 8787)`,` --api-key KEY 要求客户端携带的 API key(默认不校验)`,` --log PATH 开启数据日志并写到该文件(默认不记)`,` --desensitize 启用脱敏(零宽空格打断敏感词)`,` --no-compact 配合 --desensitize:仅零宽脱敏、不做压缩摘要`,` --no-reasoning 不透传思考内容(客户端不显示思考过程)`,` --cred-source SRC 凭据来源偏好:jetbrains=优先 IDE 登录态(等价 CODEBUDDY_CRED_SOURCE)`,` --provider ID 供应商实现(默认 codebuddy;可用:${e.join(`、`)})`,` --skip-check 跳过启动预检`,``,`环境变量: PROXY_HOST / PROXY_PORT / PROXY_UPSTREAM / CODEBUDDY_BASE_URL /`,` PROXY_LOG / CODEBUDDY2OPENAI_LOG / CODEBUDDY2OPENAI_KEY / CODEBUDDY_AUTH_DIR /`,` CODEBUDDY_CRED_SOURCE / CPA_PROVIDER`,``].join(`
|
|
3
|
+
`)+`
|
|
4
|
+
`}var ie=`https://copilot.tencent.com`;function I(e=process.env){return(e.PROXY_UPSTREAM||e.CODEBUDDY_BASE_URL||ie).replace(/\/+$/,``)}function ae(e=process.env){return{pluginVersion:e.CODEBUDDY_PLUGIN_VERSION||`4.12.38401955`,ideName:`JetBrainsIntelliJIdea`,ideType:`JetBrains`,ideVersion:e.CODEBUDDY_IDE_VERSION||`IU-262.10315.125`,product:`SaaS`,envId:`production`,agentIntent:`craft`}}var oe=256,se=6e4,ce=`/v2/plugin/auth/token/refresh`;function L(e){let t=String(e||``).replace(/-/g,``).toLowerCase();return/^[0-9a-f]{32}$/.test(t)?t:c(16).toString(`hex`)}function le(){let e=Promise.resolve();return function(t){let n=e.then(()=>t());return e=n.then(()=>void 0,()=>void 0),n}}var ue=class{#e;#t;#n;#r;#i;#a;#o=null;#s=null;#c=new Map;#l;constructor(e,t={}){this.#e=e,this.#i=!!e.readonly,this.#t=t.backend??I(t.env),this.#n=t.domain??`www.codebuddy.cn`,this.#r=t.profile??ae(t.env),this.#a=t.allowRefresh??!this.#i,this.#l=le()}#u(){let e;try{e=this.#e.fingerprint()}catch{return}(this.#o===null||e!==this.#s)&&(this.#o=this.#e.read(),this.#s=e)}#d(){if(this.#u(),this.#o===null)throw Error(`无法读取登录态:${this.#e.location}`);return this.#o}isExpired(){let e=this.#d().auth?.expiresAt||0;return Date.now()>=e-se}#f(e,t){let n=this.#r,r=e.domain&&e.domain!==`copilot.tencent.com`?e.domain:this.#n,i={"Content-Type":`application/json;charset=UTF-8`,Accept:`application/json`,Authorization:`Bearer ${e.accessToken||``}`,"X-User-Id":t.uid||``,"X-Domain":r,"User-Agent":`${n.ideName}/${n.ideVersion} CodeBuddy/${n.pluginVersion}`,"X-Requested-With":`XMLHttpRequest`,"X-Env-Id":n.envId,"X-Product":n.product,"X-Product-Version":n.pluginVersion,"X-IDE-Name":n.ideName,"X-IDE-Type":n.ideType,"X-IDE-Version":n.ideVersion};return t.enterpriseId&&(i[`X-Enterprise-Id`]=t.enterpriseId,i[`X-Tenant-Id`]=t.enterpriseId),t.departmentFullName&&(i[`X-Department-Info`]=t.departmentFullName),i}async#p(){if(this.#i||!this.#a)throw Error(`登录态来自只读来源(${this.#e.location}),已拒绝自动刷新:刷新会轮换 refreshToken,可能让 IDE/桌面端那份失效并被登出。请在 CodeBuddy 桌面端或 IDE 插件中重新登录以更新 token。`);let e=this.#d(),t=e.auth??{},n=this.#f(t,e.account??{});n[`X-Refresh-Token`]=t.refreshToken||``,n[`X-Auth-Refresh-Source`]=`plugin`;let r=`${this.#t}${ce}`,i;try{i=await(await fetch(r,{method:`POST`,headers:n,body:`{}`,signal:AbortSignal.timeout(15e3)})).json()}catch(e){throw Error(`刷新 token 网络失败:${e instanceof Error?e.message:String(e)}`)}if(i.code!==0||!i.data)throw Error(`刷新 token 失败:${i.msg??JSON.stringify(i)}`);let a=i.data;a.domain=a.domain||t.domain,a.lastRefreshTime=Date.now(),!a.expiresAt&&a.expiresIn&&(a.expiresAt=Date.now()+a.expiresIn*1e3),!a.refreshExpiresAt&&a.refreshExpiresIn&&(a.refreshExpiresAt=Date.now()+a.refreshExpiresIn*1e3),e.auth=a,await this.#e.write(e),this.#o=e,this.#s=this.#e.fingerprint()}async getHeaders(){return this.#l(async()=>{this.isExpired()&&await this.#p();let e=this.#d();return this.#f(e.auth??{},e.account??{})})}async getChatHeaders(e={}){let t=this.#r,n=await this.getHeaders(),r=L(e.sessionId),i=L(e.messageId),a=L(e.requestId),o=L(e.traceId),s=c(8).toString(`hex`),u=this.#c.get(r);this.#m(r,s),n.Accept=`*/*`,n[`Accept-Language`]=`*`,n[`Sec-Fetch-Mode`]=`cors`,n[`X-Agent-Intent`]=e.agentIntent||t.agentIntent,e.model&&(n[`X-Model-Id`]=e.model),n[`X-Conversation-Id`]=r,n[`X-Conversation-Message-Id`]=i,n[`X-Conversation-Request-Id`]=a,n[`X-Request-Id`]=a,n[`X-Request-Trace-Id`]=l(),n[`X-Trace-Id`]=o,n[`X-B3-TraceId`]=o,n[`X-B3-SpanId`]=s,u&&(n[`X-B3-ParentSpanId`]=u),n[`X-B3-Sampled`]=`1`,n.bo=u?`${o}-${s}-1-${u}`:`${o}-${s}-1`,n[`X-Device-Token-Error`]=`sdk_not_initialized`;let d=Date.now();return n.monitor_httpsendtime=String(d),n.monitor_promptpreparestarttime=String(e.prepareStartTime||d),n}#m(e,t){for(this.#c.delete(e),this.#c.set(e,t);this.#c.size>oe;){let e=this.#c.keys().next().value;if(e===void 0)break;this.#c.delete(e)}}summary(){let e=this.#d(),t=e.auth??{},n=e.account??{};return{uid:n.uid,nickname:n.nickname,enterpriseName:n.enterpriseName,tokenExpiresAt:t.expiresAt||0,tokenExpired:this.isExpired(),source:this.#e.location,readonly:this.#i,refreshable:this.#a&&!this.#i}}},de=[`glm-5.2`,`glm-5.1`,`glm-5v-turbo`,`kimi-k2.7`,`kimi-k2.6`,`kimi-k2.5`,`deepseek-v4-pro`,`deepseek-v4-flash`,`minimax-m3-pay`,`hy3-preview-agent`,`auto`],fe=new Set([`text-to-image`,`image-to-image`,`text-to-video`]);function pe(t){let n=u(),r=[];if(process.platform===`darwin`)r.push(`/Applications/WorkBuddy.app/Contents/Resources/app.asar.unpacked/cli/product.json`);else if(process.platform===`win32`){let n=t.LOCALAPPDATA||``;r.push(e(n,`Programs/WorkBuddy/resources/app.asar.unpacked/cli/product.json`),`C:/Program Files/WorkBuddy/resources/app.asar.unpacked/cli/product.json`)}else r.push(`/opt/WorkBuddy/resources/app.asar.unpacked/cli/product.json`,e(n,`.local/share/WorkBuddy/cli/product.json`));for(let e of r)try{if(i(e)&&s(e).isFile())return e}catch{}return null}function me(e){let t=pe(e);if(t===null)return[];try{let e=JSON.parse(a(t,`utf8`)).models??[],n=[];for(let t of e){if(t===null)continue;let e=t.id;if(!e||(t.tags??[]).some(e=>fe.has(e))||(t.vendor||``)===`tencent`)continue;let r=String(e).toLowerCase();[`completion`,`rewrite`,`jump`,`codewise`].some(e=>r.includes(e))||n.push(e)}return n}catch(e){return h(`Warning: Failed to load models from WorkBuddy product.json: ${e instanceof Error?e.message:String(e)}`),[]}}function he(e=process.env){let t=me(e);return t.length>0?t:de}function ge(t){let n=t.CODEBUDDY_AUTH_DIR;if(n)return[n];let r=u(),i=[`CodeBuddyExtension`,`Data`,`Public`,`auth`];switch(process.platform){case`darwin`:return[e(r,`Library`,`Application Support`,...i)];case`win32`:{let n=t.LOCALAPPDATA||e(r,`AppData`,`Local`);return[e(n,...i)]}default:{let n=t.XDG_DATA_HOME||e(r,`.local`,`share`);return[e(n,...i)]}}}function R(t=process.env){for(let n of ge(t)){let t;try{t=o(n)}catch{continue}let r=t.filter(e=>e.toLowerCase().endsWith(`.info`)).sort()[0];if(r!==void 0)return e(n,r)}return null}function _e(e=process.env){try{return R(e)||`(未找到)`}catch{}return`(未找到)`}var ve=/\.new\.accessToken$/i;function ye(t){let n=u();switch(process.platform){case`darwin`:return[e(n,`Library`,`Application Support`,`JetBrains`)];case`win32`:return[e(t.APPDATA||e(n,`AppData`,`Roaming`),`JetBrains`)];default:return[e(n,`.config`,`JetBrains`),e(n,`.local`,`share`,`JetBrains`)]}}function be(t){let n=[];for(let r of ye(t)){let t;try{t=o(r,{withFileTypes:!0})}catch{continue}for(let i of t){if(!i.isDirectory())continue;let t=e(r,i.name,`options`,`secret-storage.xml`);try{s(t).isFile()&&n.push(t)}catch{continue}}}return n}function xe(e){return String(e).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`).replace(/'/g,`'`).replace(/&#(\d+);/g,(e,t)=>String.fromCharCode(Number(t))).replace(/&/g,`&`)}function Se(e){let t=a(e,`utf8`),n=/<Entry\s+key="([^"]*)"\s+value="([^"]*)"\s*\/?>/g,r=[],i;for(;(i=n.exec(t))!==null;){let e=i[1]??``,t=i[2]??``;ve.test(e)&&r.push({key:e,value:t})}if(r.length===0)return null;r.sort((e,t)=>t.value.length-e.value.length);let o=r[0];return o===void 0?null:{key:o.key,session:JSON.parse(xe(o.value))}}var Ce=class{path;readonly=!1;constructor(e){this.path=e}get location(){return this.path}fingerprint(){return String(s(this.path).mtimeMs)}read(){return JSON.parse(a(this.path,`utf8`))}async write(e){let t=`${this.path}.tmp`;await f(t,JSON.stringify(e,null,2),`utf8`),await d(t,this.path)}},we=class{path;readonly=!0;#e=null;constructor(e){this.path=e,this.read()}get location(){return this.#e?`${this.path} :: ${this.#e}`:this.path}fingerprint(){let e=s(this.path);return`${String(e.mtimeMs)}:${String(e.size)}`}read(){let e=Se(this.path);if(!e)throw Error(`未在 ${this.path} 中找到 *.new.accessToken 凭据条目`);return this.#e=e.key,e.session}write(e){throw Error(`JetBrains secret-storage.xml 为只读来源,拒绝回写`)}};function Te(e,t=process.env){let n=()=>{for(let e of be(t))try{return new we(e)}catch{continue}return null};if(e===`jetbrains`){let e=n();if(e)return e}let r=R(t);return r?new Ce(r):n()}var Ee=``,De=`DoS,DDoS,exploit,credential testing,credential stuffing,supply chain compromise,supply-chain compromise,detection evasion,C2 frameworks,C2 framework,command and control,malicious purposes,malicious intent,mass targeting,brute force,brute-force,privilege escalation,reverse shell,remote code execution,SQL injection,XSS,CSRF,phishing,malware,ransomware,keylogger,rootkit,backdoor,botnet,zero-day,0day,vulnerability,vulnerabilities,red teaming,red-teaming,sandbox,sandboxing,sandboxed,unsandboxed,escalated privileges,escalated,escalation,destructive action,destructive command,destructive,attack,attacks,cybersecurity,security review,exploit development,hacking,penetration testing,penetration test,injection,weaponize,weaponized,harmful,dangerous,abuse,abusive,illegal,terrorist,terrorism,bomb,weapon,weapons,drug,drugs,narcotic,suicide,self-harm,murder,kill,violence,violent,Claude Code,Claude Opus,Claude Sonnet,Claude Haiku,Claude Fable,Anthropic,Co-Authored-By,noreply@anthropic.com`.split(`,`);function z(e){return String(e).replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}var Oe=new RegExp(De.slice().sort((e,t)=>t.length-e.length).map(z).join(`|`),`gi`),ke=[`# AGENTS.md instructions`,`<environment_context>`,`<permissions instructions>`,`<collaboration_mode>`,`<skills_instructions>`,`<system-reminder>`,`# claudeMd`],B=[`You are a coding agent running in the Codex CLI`,`Within this context, Codex refers to`,`# How you work`,`You are Claude Code`],Ae=[`<permissions instructions>`,`Filesystem sandboxing defines which files can be read or written.`,`## How to request escalation`],je=[`<skills_instructions>`,`### Available skills`,`### How to use skills`],Me=[[`<environment_context>`,`</environment_context>`,`Environment context is provided by the harness.`],[`<permissions instructions>`,`</permissions instructions>`,`Runtime permissions apply: filesystem access may be sandboxed, network may be restricted, and some commands may require user approval.`],[`<collaboration_mode>`,`</collaboration_mode>`,`Collaboration mode instructions are provided by the harness.`],[`<skills_instructions>`,`</skills_instructions>`,`Runtime skill metadata is available. Use relevant skills only when explicitly requested or clearly applicable.`],[`<plugins_instructions>`,`</plugins_instructions>`,`Runtime plugin metadata is available when relevant.`],[`<system-reminder>`,`</system-reminder>`,`Runtime reminder context is provided by the harness.`]],Ne=[`The following deferred tools are now available via ToolSearch.`,`Available agent types for the Agent tool:`,`The following skills are available for use with the Skill tool:`,`## MCP Server Instructions`],Pe=`Runtime tool, agent, skill, and MCP metadata is available separately.`,Fe=`You are a coding assistant in Codex CLI. Be precise, helpful, concise, and safe. Inspect the repository, use available tools when needed, follow repository instructions, and keep the user informed with concise progress updates.`;function Ie(e){return e.length<=1?e:e.charAt(0)+Ee+e.slice(1)}function V(e){return e&&String(e).replace(Oe,e=>Ie(e))}function H(e){return typeof e==`object`&&!!e&&e.type===`text`}function U(e){let t=typeof e==`string`?e:``;if(Array.isArray(e)){let n=[];for(let t of e)H(t)&&n.push(String(t.text||``));t=n.join(``)}return t}function W(e){let t=U(e);return ke.some(e=>t.includes(e))}var Le=/\n{3,}/g;function G(e,t){if(!t)return t;let n=t;for(let[e,t,r]of Me){let i=RegExp(`\\s*`+z(e)+`[\\s\\S]*?`+z(t)+`\\s*`,`g`);n=n.replace(i,`
|
|
5
|
+
|
|
6
|
+
`+r+`
|
|
7
|
+
|
|
8
|
+
`)}let r=Ne.filter(e=>n.includes(e)).map(e=>n.indexOf(e));if(r.length){let e=Math.min(...r.filter(e=>e>=0)),t=n.slice(0,e).replace(/\s+$/,``);n=t?t+`
|
|
9
|
+
|
|
10
|
+
Runtime tool, agent, skill, and MCP metadata is available separately.`:Pe}if(e===`system`&&B.some(e=>n.includes(e))){let e=[],t=RegExp(`^[\\s\\S]*?(?=\\n# AGENTS\\.md spec|\\n## Responsiveness|\\n## Planning|\\n## Task execution|$)`).exec(n);if(t){let n=(t[0]??``).trim();n&&e.push(n)}for(let t of[`## Personality`,`# AGENTS.md spec`]){let r=RegExp(z(t)+`[\\s\\S]*?(?=\\n## |\\n# |$)`).exec(n);if(r){let t=(r[0]??``).trim();t&&e.push(t)}}n=e.length?e.join(`
|
|
11
|
+
|
|
12
|
+
`):Fe}return e===`user`&&W(n)&&(n.includes(`# AGENTS.md instructions`)||t.includes(`<environment_context>`)||t.includes(`<skills_instructions>`))?`Repository instructions and durable user context are provided. Follow repository guidance while answering the user’s actual request.`:(n=n.replace(Le,`
|
|
13
|
+
|
|
14
|
+
`).trim(),n)}function Re(e,t){let n=U(t);return n?e===`system`&&B.some(e=>n.includes(e))?n.includes(`You are Claude Code`)?`You are a coding assistant. Be precise, helpful, concise, and safe. Use available tools when needed, follow repository instructions, and keep the user informed.`:`You are a coding assistant in Codex CLI. Be precise, helpful, concise, and safe. Use available tools when needed, follow repository instructions, and keep the user informed.`:Ae.some(e=>n.includes(e))?`Runtime permissions apply: filesystem access may be sandboxed, network may be restricted, and some commands may require user approval.`:je.some(e=>n.includes(e))?`Runtime skill metadata is available. Use relevant skills only when explicitly requested or clearly applicable.`:e===`user`&&W(t)?`Repository instructions and environment context are provided. Follow repository guidance while answering the user’s actual request.`:null:null}function K(e,t=!1){if(Array.isArray(e))return e.map(e=>K(e,t));if(typeof e==`object`&&e){let n={};for(let[r,i]of Object.entries(e))if((r===`description`||r===`title`)&&typeof i==`string`){if(t)continue;n[r]=V(i)}else n[r]=K(i,t);return n}return e}function ze(e,t=[`system`],n={}){let{desensitizeHarnessUser:r=!1,compactHarness:i=!1}=n,a=Array.isArray(e)?e:[],o=[];for(let e of a){if(!e||typeof e!=`object`){o.push(e);continue}let n=e,a=n.role,s=typeof a==`string`&&t.includes(a);a===`user`&&r&&(s=W(n.content));let c={...n};if(s){let e=n.content,t=i?Re(a,e):null;if(t!==null)c.content=V(t);else if(typeof e==`string`)c.content=V(G(a,e));else if(Array.isArray(e)){let t=[];for(let n of e)if(H(n)){let e={...n};e.text=V(G(a,String(n.text||``))),t.push(e)}else t.push(n);c.content=t}}o.push(c)}return o}function Be(e,t={}){let{roles:n=[`system`],desensitizeHarnessUser:r=!1,desensitizeTools:i=!1,compactHarness:a=!1,stripToolMetadata:o=!1}=t,s=!1,c={...e};return e.messages&&(c.messages=ze(e.messages,n,{desensitizeHarnessUser:r,compactHarness:a}),s=!0),i&&e.tools&&(c.tools=K(e.tools,o),s=!0),s?c:e}var Ve=[[/\(you will usually use this for PRs\)/gi,`(you will typically use this for PRs)`],[/x-anthropic-/gi,`x-cc-`],[/You are Claude Code\s*,\s*Anthropic['’]s/gi,`You are Claude Code, from Anthropic's`]];function He(e){let t=e;for(let[e,n]of Ve)t=t.replace(e,n);return t}function Ue(e){let t=new WeakSet,n=0,r=e=>{if(typeof e==`string`)return He(e);if(!e||typeof e!=`object`||t.has(e))return e;t.add(e);let i=Array.isArray(e)?[]:{};for(let t of Object.keys(e)){let a=e[t],o=r(a);i[t]=o,o!==a&&(n+=1)}return i};return[r(e),n]}function We(e,t){return t.enabled?Be(e,{roles:[`system`,`developer`],desensitizeHarnessUser:!0,desensitizeTools:!0,compactHarness:t.forceCompact||!t.noCompact,stripToolMetadata:!0}):e}var Ge=`/billing/meter/get-user-resource-summary`,Ke=15e3,qe=`credits`;function q(e){if(typeof e==`number`)return Number.isFinite(e)?e:null;if(typeof e==`string`&&e.trim()!==``){let t=Number(e);if(Number.isFinite(t))return t}return null}function Je(e){if(!b(e))return null;let t=q(e.CycleTotalCapacity),n=q(e.CycleRemainCapacity);if(t===null||n===null)return null;let r=q(e.CycleUsedCapacity)??Math.max(0,t-n);return{packageCode:typeof e.PackageCode==`string`?e.PackageCode:``,total:t,remain:n,used:r}}function Ye(e){if(!b(e))throw Error(`上游未返回额度数据(响应缺少 data)`);let t=Array.isArray(e.Packages)?e.Packages:[],n=t.map(Je).filter(e=>e!==null);if(n.length===0)throw Error(`上游未返回有效资源包(Packages 为空或各包的容量字段不可解析)`);let r=n.reduce((e,t)=>e+t.total,0),i=n.reduce((e,t)=>e+t.remain,0),a=n.reduce((e,t)=>e+t.used,0),o=t.map(e=>b(e)?e.CapacityUnit:void 0).find(e=>typeof e==`string`&&e!==``);return{unit:typeof o==`string`?o:qe,total:r,remain:i,used:a,packages:n,subscriptionName:typeof e.SubscriptionPackageName==`string`?e.SubscriptionPackageName:``,isPaidUser:e.IsPaidUser===!0}}async function Xe(e,t){let n;try{n=await fetch(`${e}${Ge}`,{method:`POST`,headers:{...t,"Content-Type":`application/json;charset=UTF-8`},body:`{}`,signal:AbortSignal.timeout(Ke)})}catch(e){let t=e instanceof Error?e.message:String(e);throw new _(502,`额度查询网络失败:${t}`,y(`额度查询网络失败:${t}`,`upstream_error`))}if(!n.ok){let e=await n.text().then(e=>e.replace(/\s+/g,` `).trim().slice(0,200)).catch(()=>``);throw new _(502,`额度查询失败:HTTP ${String(n.status)}${e===``?``:` ${e}`}`,y(e===``?`upstream HTTP ${String(n.status)}`:e,`upstream_error`))}let r;try{r=await n.json()}catch{throw new _(502,`额度查询失败:响应不是 JSON`,y(`响应不是 JSON`,`upstream_error`))}if(!b(r)||r.code!==0){let e=b(r)&&typeof r.msg==`string`?r.msg:JSON.stringify(r);throw new _(502,`额度查询被拒绝:${e}`,y(e,`upstream_error`))}return Ye(r.data)}var Ze=class extends M{id=`codebuddy`;label=`CodeBuddy/WorkBuddy 订阅代理`;#e;#t=null;#n=null;constructor(e){super(e),this.#e=I()}async init(){try{let e=Te(this.options.credSource);return e===null?(this.#n=`未找到登录凭据,请先在桌面端登录 CodeBuddy/WorkBuddy`,{ok:!1,reason:this.#n}):(this.#t=new ue(e,{backend:this.#e}),{ok:!0})}catch(e){return this.#n=`加载登录态失败:${e instanceof Error?e.message:String(e)}`,{ok:!1,reason:this.#n}}}assertChatReady(){this.#r()}chatUrl(){return`${this.#e}/v2/chat/completions`}buildHeaders(e){return this.#r().getChatHeaders(e)}transformBody(e){let{desensitize:t,noCompact:n}=this.options,[r,i]=Ue(We(e,{enabled:t,noCompact:n}));return{body:r,changed:i}}async listModels(){return he()}async usage(){let e=await this.#r().getHeaders();return Xe(this.#e,e)}healthDetails(){let e={auth_file:_e(),upstream:this.#e};if(this.#t!==null)try{e.credential=this.#t.summary()}catch(t){e.credential_error=t instanceof Error?t.message:String(t)}else this.#n!==null&&(e.credential_error=this.#n);return e}#r(){if(this.#t!==null)return this.#t;let e=this.#n??`未找到登录凭据,请先在桌面端登录 CodeBuddy/WorkBuddy`;throw new _(503,e,y(e,`auth_error`))}},J={codebuddy:e=>new Ze(e)};function Y(){return Object.keys(J)}function Qe(e,t){let n=J[e];if(n===void 0)throw Error(`未知供应商 "${e}"(可用:${Y().join(`、`)})`);return n(t)}async function X(e,t,n,r){let i={method:`POST`,headers:t,body:JSON.stringify(n)};return r!==null&&(i.signal=AbortSignal.timeout(r)),fetch(e.chatUrl,i)}function $e(e){for(let t=e.length-1;t>=0;--t){let n=e[t];if(typeof n!=`object`||!n)continue;let r=n;if(r.role!==`user`)continue;let i=r.content??``;if(Array.isArray(i)){for(let e of i)if(typeof e==`object`&&e&&e.type===`text`)return String(e.text??``);return``}return String(i)}return``}async function*et(e){for(let t of e.toString(`utf8`).split(/\r?\n/))yield t}function tt(e,t,n,r,i=``){let a=(Date.now()-n)/1e3,o=i?`[${i}] `:``,s=r.choices[0]??{index:0,message:{},finish_reason:``},c=s.finish_reason,l=s.message,u=Array.isArray(l.tool_calls)?l.tool_calls:[],d=r.usage??{},f=c===`content-filter`?` ⚠️内容审核拦截`:``,p=u.map(e=>e.function?.name);e.logger.log(`${o}◀ RESPONSE ${t} | ${a.toFixed(1)}s | finish=${String(c)}${f}`+(p.length?` | tool_calls=${j(p)}`:``)+` | tokens=${d.total_tokens===void 0?`?`:String(d.total_tokens)}`),e.logger.enabled&&e.logger.log(`${o}── RESPONSE BODY ──\n${A(r)}`)}async function nt(e){let t=[],n=new Map,r=null,i=null,a=null;for await(let o of e){let e=String(o).trim();if(!e||!e.startsWith(`data:`))continue;let s=e.slice(5).trim();if(s===`[DONE]`)break;let c;try{c=JSON.parse(s)}catch{continue}r=c.model??r,c.usage&&(a=c.usage);for(let e of c.choices??[]){e.finish_reason&&(i=e.finish_reason);let r=e.delta??{};r.content&&t.push(r.content);for(let e of r.tool_calls??[]){let t=e.index===void 0||e.index===null?0:e.index,r=n.get(t);r===void 0&&(r={id:null,name:null,arguments:``},n.set(t,r)),e.id&&(r.id=e.id);let i=e.function??{};i.name&&(r.name=i.name),i.arguments&&(r.arguments+=i.arguments)}}}let o=null;n.size>0&&(o=[...n.entries()].sort((e,t)=>e[0]-t[0]).map(([,e])=>({id:e.id,type:`function`,function:{name:e.name,arguments:e.arguments}})),i??=`tool_calls`);let s={role:`assistant`,content:t.join(``)||null};return o&&(s.tool_calls=o),{id:`chatcmpl-${c(12).toString(`hex`)}`,object:`chat.completion`,created:Math.floor(Date.now()/1e3),model:r??`unknown`,choices:[{index:0,message:s,finish_reason:i??`stop`}],usage:a??{prompt_tokens:0,completion_tokens:0,total_tokens:0}}}function rt(e,t){try{return JSON.parse(e.toString(`utf8`))}catch{return{error:{message:e.toString(`utf8`).slice(0,500),type:`upstream_error`,code:t}}}}function Z(e,t){let n={error:{message:e.toString(`utf8`).slice(0,500),type:`upstream_error`,code:t}};return`data: ${JSON.stringify(n)}\n\n`}async function it(e,t,n,r,i=`?`,a=0,o=``){let s=Number(process.env.PROXY_RC_MAX_CHARS||8e3),l=t.dropReasoning,u=t.logger,d=null,f=[],p={},m=!1,h=0,g=0,_=0,v=``,y=null,b=[],x=o?`[${o}] `:``,S=()=>{if(!v)return;let t=y??{},n={id:t.id||`chatcmpl-${c(8).toString(`hex`)}`,object:t.object||`chat.completion.chunk`,created:t.created||Math.floor(Date.now()/1e3),model:t.model||i,choices:[{index:0,delta:{role:`assistant`,content:``,reasoning_content:v,function_call:null,refusal:``,tool_calls:[],extra_fields:null},logprobs:null,finish_reason:``}],usage:null};e.write(`data: ${JSON.stringify(n)}\n\n`),_+=1,v=``},C=t=>{if(t.startsWith(`data:`)){let n=t.slice(5).trim();if(n===`[DONE]`){S(),e.write(`${t}\n`);return}let r=null;try{r=JSON.parse(n)}catch{}if(r!==null){r.usage&&(p={...p,...r.usage}),y??=r;let t=!1;for(let e of r.choices??[]){e.finish_reason&&(d=e.finish_reason);let n=e.delta??{};n.reasoning_content?(g+=1,l?delete n.reasoning_content:(v+=n.reasoning_content,!n.content&&!(n.tool_calls??[]).length&&!n.function_call&&(t=!0))):n.reasoning_content===``&&(delete n.reasoning_content,h+=1);for(let e of n.tool_calls??[]){let t=e.function?.name;t&&f.push(t)}}t&&(r.choices??[]).length===1?v.length>=s&&S():(S(),e.write(`data: ${JSON.stringify(r)}\n`)),(n.includes(`content-filter`)||n.includes(`敏感`)||n.includes(`审核`))&&(m=!0);return}}e.write(`${t}\n`)};try{let a=await X(t,n,r,null);if(a.status!==200){let t=Buffer.from(await a.arrayBuffer());u.log(`${x}✗ HTTP ${String(a.status)} | ${i} | ${k(t.toString(`utf8`),200)}`),u.enabled&&u.log(`${x}── ERROR BODY ──\n${t.toString(`utf8`)}`),e.write(Z(t,a.status)),e.end();return}let o=Buffer.alloc(0);for await(let e of E(a.body))if(e&&e.length)for(u.enabled&&b.push(e),o=Buffer.concat([o,e]);;){let e=o.indexOf(`
|
|
15
|
+
`);if(e<0)break;let t=o.subarray(0,e).toString(`utf8`).replace(/\r$/,``);o=o.subarray(e+1),C(t)}o.length&&C(o.toString(`utf8`)),S(),e.end()}catch(t){let n=t instanceof Error?t.message:String(t);u.log(`${x}✗ 网络错误 | ${i} | ${n}`),e.headersSent||e.writeHead(200,{"Content-Type":`text/event-stream`}),e.write(Z(Buffer.from(n),502)),e.end()}let w=a?(Date.now()-a)/1e3:0,T=m||d===`content-filter`?` ⚠️内容审核拦截`:``;u.log(`${x}◀ RESPONSE ${i} | ${w.toFixed(1)}s | stream finish=${String(d)}${T}`+(f.length?` | tool_calls=${j(f)}`:``)+` | tokens=${p.total_tokens===void 0?`?`:String(p.total_tokens)} | stripped_empty_rc=${String(h)} | rc_agg=${String(g)}→${String(_)}`+(l?` | reasoning=hidden`:``)),u.enabled&&u.log(`${x}── RESPONSE RAW SSE ──\n${Buffer.concat(b).toString(`utf8`)}`)}var at=[`model`,`messages`,`tools`,`tool_choice`,`temperature`,`max_tokens`,`max_completion_tokens`,`top_p`,`stream`,`stream_options`,`stop`,`presence_penalty`,`frequency_penalty`,`n`,`response_format`,`seed`,`user`,`reasoning_effort`,`verbosity`,`reasoning_summary`],ot=3e5,st=6e4;function ct(e){let t={...e,usageQuery:lt(e.provider)};return p((n,r)=>{gt(t,n,r).catch(t=>{e.logger.log(`[fatal] handler error: ${t instanceof Error&&t.stack?t.stack:String(t)}`);try{r.headersSent||r.writeHead(500,{"Content-Type":`application/json`}),r.end(JSON.stringify({detail:y(String(t),`internal_error`)}))}catch{}})})}function lt(e){let t=null;return async()=>{let n=Date.now();if(t!==null&&n-t.at<st)return{usage:t.usage,fetchedAt:t.at,cached:!0};let r=await e.usage();return r===null?null:(t={at:n,usage:r},{usage:r,fetchedAt:n,cached:!1})}}function ut(e){return async()=>{let t=await e.usage();return t===null?null:{usage:t,fetchedAt:Date.now(),cached:!1}}}function Q(e,t){let n=e.apiKey;if(!n)return;let r=t.headers.authorization,i=t.headers[`x-api-key`],a=``;if(r&&r.startsWith(`Bearer `)&&(a=r.slice(7).trim()),!a&&i&&(a=String(i)),a!==n)throw new _(401,`invalid api key`,y(`invalid api key`,`auth_error`))}function dt(e){let t=(...t)=>{for(let n of t){let t=e[n];if(t)return Array.isArray(t)?t[0]:t}};return{sessionId:t(`x-session-id`,`x-zcode-session-id`,`x-conversation-id`),messageId:t(`x-query-id`,`x-conversation-message-id`,`x-message-id`),requestId:t(`x-request-id`,`x-zcode-request-id`),traceId:t(`x-zcode-trace-id`,`x-trace-id`,`x-b3-traceid`)}}function ft(e,t){S(t,200,{status:`ok`,platform:process.platform,node:process.version.replace(/^v/,``),mode:`direct-proxy (native function calling)`,...e.provider.healthDetails()})}async function pt(e,t){let n=await(e.usageQuery??ut(e.provider))();if(n===null){S(t,501,{detail:y(`供应商 ${e.provider.id} 不支持额度查询`,`not_implemented`)});return}S(t,200,{status:`ok`,provider:e.provider.id,fetchedAt:n.fetchedAt,cached:n.cached,usage:n.usage})}async function mt(e,t,n){Q(e.options,t),S(n,200,{object:`list`,data:(await e.provider.listModels()).map(t=>({id:t,object:`model`,created:17e8,owned_by:e.provider.id}))})}async function ht(e,t,n){let{options:r,provider:i,logger:a}=e;Q(r,t),i.assertChatReady();let o=Date.now(),s=await ee(t,T()),l;try{l=te(s)}catch(e){return w(n,`bad json: ${e instanceof Error?e.message:String(e)}`)}let u=b(l)?l:{},d=Array.isArray(u.messages)?u.messages:[];if(d.length===0)return w(n,`messages is required`);let f=!!u.stream,p={};for(let e of at)e in u&&(p[e]=u[e]);`model`in p||(p.model=`auto`),p.stream=!0,`stream_options`in p||(p.stream_options={include_usage:!0}),Array.isArray(p.messages)&&(p.messages=p.messages.map(e=>b(e)&&e.role===`developer`?{...e,role:`system`}:e));let m=i.transformBody(p),h=m.body,g=u.model,_=typeof g==`string`&&g!==``?g:`auto`,v=(Array.isArray(u.tools)?u.tools:[]).filter(b).map(e=>b(e.function)?e.function.name:void 0),x=$e(d),E=c(4).toString(`hex`);a.log(`[${E}] ▶ REQUEST ${_} | stream=${String(f)} | msgs=${String(d.length)}`+(v.length?` | tools=${j(v)}`:``)+(m.changed?` | fingerprint_rewrites=${String(m.changed)}`:``)+(x?` | last_user=${ne(k(x,60))}`:``)),a.enabled&&a.log(`[${E}] ── REQUEST BODY (发往后端) ──\n${A(h)}`);let D=await i.buildHeaders({model:_,...dt(t.headers),prepareStartTime:o});a.log(`[${E}] ── 后端会话 ── conv=${String(D[`X-Conversation-Id`])} req=${String(D[`X-Request-Id`])}`);let O=Date.now(),M={chatUrl:i.chatUrl(),logger:a,dropReasoning:r.noReasoning};if(f){n.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,"X-Accel-Buffering":`no`}),await it(n,M,D,h,_,O,E);return}let N,P;try{let e=await X(M,D,h,ot);N=e.status,P=Buffer.from(await e.arrayBuffer())}catch(e){let t=e instanceof Error?e.message:String(e);return a.log(`[${E}] ✗ 网络错误 | ${_} | ${t}`),C(n,502,y(`upstream error: ${t}`,`upstream_error`))}if(N!==200)return a.log(`[${E}] ✗ HTTP ${String(N)} | ${_} | ${k(P.toString(`utf8`),200)}`),a.enabled&&a.log(`[${E}] ── ERROR BODY ──\n${P.toString(`utf8`)}`),C(n,N,rt(P,N));let F=await nt(et(P));tt(M,_,O,F,E),S(n,200,F)}async function gt(e,t,n){let r=x(t.url??`/`),i=(t.method??`GET`).toUpperCase();!r.startsWith(`/v1/`)&&(r===`/chat/completions`||r===`/models`)&&(r=`/v1${r}`);try{if(i===`GET`&&r===`/health`){ft(e,n);return}if(i===`GET`&&r===`/usage`){await pt(e,n);return}if(i===`GET`&&r===`/v1/models`){await mt(e,t,n);return}if(i===`POST`&&r===`/v1/chat/completions`){await ht(e,t,n);return}S(n,404,{detail:y(`未找到:${i} ${r}`,`not_found`)})}catch(t){if(t instanceof _){e.logger.log(`✗ ${i} ${r} → ${String(t.status)} ${t.message}`),S(n,t.status,{detail:t.detail});return}e.logger.log(`[fatal] ${i} ${r}: ${t instanceof Error&&t.stack?t.stack:String(t)}`),S(n,500,{detail:y(t instanceof Error?t.message:String(t),`internal_error`)})}}function _t(e){m(`==== 预检 ====`),m(`平台 : ${process.platform}`),m(`Node : ${process.version}`),m(`上游 : ${e.chatUrl()}(直连,原生 function calling)`);let t=e.healthDetails(),n=t.auth_file;m(`登录文件 : ${typeof n==`string`&&n!==``?n:`(未找到)`}`);let r=t.credential;if(b(r)){let e=typeof r.nickname==`string`?r.nickname:``,t=r.enterpriseName;m(`账号 : ${typeof t==`string`&&t!==``?`${e} / ${t}`:e||`(未知)`}`),m(`token过期 : ${r.tokenExpired===!0?`是(将自动刷新)`:`否`}`)}else h(``),h(typeof t.credential_error==`string`?`[警告] ${t.credential_error}`:`[警告] 未找到登录凭据。请先在桌面端完成登录(CodeBuddy/WorkBuddy)。`);m(`================`)}async function $(e=process.argv.slice(2),t=process.env){let{options:n,help:r}=F(e,t);if(r)return process.stdout.write(re(Y())),null;let i=Qe(n.providerId,n),a=new O(n.logPath),o=await i.init();o.ok||h(`[cpa] ${o.reason??`初始化未完成`}`),n.skipCheck||_t(i),m(`[cpa] ✅ 监听 http://${n.host}:${String(n.port)}(${i.label})`),m(`[cpa] GET /v1/models`),m(`[cpa] POST /v1/chat/completions (原生 tools/tool_calls,支持流式)`),m(`[cpa] GET /health`),m(`[cpa] GET /usage (账号额度快照)`),n.apiKey&&m(`[cpa] 鉴权已启用(API key 已设置)`),a.enabled&&m(`[cpa] 日志 : ${n.logPath??``}`),n.desensitize&&m(`[cpa] 脱敏 : 已启用(${n.noCompact?`零宽脱敏 + 保留全文`:`零宽脱敏 + 压缩摘要`})`),n.noReasoning&&m(`[cpa] 思考 : 已隐藏(不透传 reasoning_content)`),m(`[cpa] 按 Ctrl+C 退出。`),a.log(`==== converter 启动 ====`);let s=ct({options:n,provider:i,logger:a});return await new Promise((e,t)=>{s.once(`error`,t),s.listen(n.port,n.host,()=>{s.off(`error`,t),e()})}),m(`[cpa] listening on ${n.host}:${String(n.port)}`),{server:s,provider:i,options:n}}var vt=process.argv[1];vt!==void 0&&import.meta.url===n(t(vt)).href&&$().catch(e=>{g(`[cpa] 启动失败:${e instanceof Error?e.message:String(e)}`),process.exitCode=1});export{$ as main};
|