ezllm 0.0.39 → 0.0.41

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.
Files changed (36) hide show
  1. package/README.md +8 -9
  2. package/dist/cli.js +23 -24
  3. package/dist/daemon.js +6 -10
  4. package/dist/plugin-host.js +3 -3
  5. package/dist/server.js +73 -83
  6. package/dist/web-ui/assets/{DashboardView-STI4Bkxw.js → DashboardView-DRckeG7f.js} +1 -1
  7. package/dist/web-ui/assets/LayoutView-4x7mM1Nd.js +1 -0
  8. package/dist/web-ui/assets/{LayoutView-CQJN31vh.css → LayoutView-D84C9Iqp.css} +1 -1
  9. package/dist/web-ui/assets/{LoginView-CwH6eISd.js → LoginView-D55GMnh2.js} +1 -1
  10. package/dist/web-ui/assets/{LogsView-CGgzVbGZ.js → LogsView-Ck8cUn2K.js} +1 -1
  11. package/dist/web-ui/assets/{ManagedServicesView-BvzeGhcb.js → ManagedServicesView-aU7KD-uK.js} +1 -1
  12. package/dist/web-ui/assets/PluginsView-XtlAtfkK.js +2 -0
  13. package/dist/web-ui/assets/PluginsView-ggYmqCJ8.css +1 -0
  14. package/dist/web-ui/assets/{ProvidersView-BpBOF4eX.css → ProvidersView-BSFjmX9y.css} +1 -1
  15. package/dist/web-ui/assets/ProvidersView-BuCzzvhA.js +14 -0
  16. package/dist/web-ui/assets/{ServiceStatusView-BvuFCgT-.js → ServiceStatusView-BfEtiG3k.js} +1 -1
  17. package/dist/web-ui/assets/SettingsView-C3lfSDLN.js +1 -0
  18. package/dist/web-ui/assets/{SettingsView-CB_w0Y2E.css → SettingsView-NXkxZUgr.css} +1 -1
  19. package/dist/web-ui/assets/{SetupView-D5KYolFQ.js → SetupView-BB5YHhmw.js} +1 -1
  20. package/dist/web-ui/assets/{config-CX31TfJ4.js → config-B3YP6GMt.js} +1 -1
  21. package/dist/web-ui/assets/{index-D6ydfwRk.js → index-DVkpGX_l.js} +1 -1
  22. package/dist/web-ui/assets/{message-B022-tc2.js → message-JXw_u_ps.js} +1 -1
  23. package/dist/web-ui/assets/{providers-C6QKdfzy.js → providers-CbsDigh3.js} +1 -1
  24. package/dist/web-ui/assets/{router-Daxct41S.js → router-DxY62FPf.js} +2 -2
  25. package/dist/web-ui/assets/{useCopyToast-0dz9T4-H.js → useCopyToast-W6vLjLdg.js} +1 -1
  26. package/dist/web-ui/assets/{useManagerApi-DdgACgsq.js → useManagerApi-7t9qNB2C.js} +1 -1
  27. package/dist/web-ui/assets/{useQuickRangeFilter-D8oTOMUp.js → useQuickRangeFilter-BWtDACYs.js} +1 -1
  28. package/dist/web-ui/assets/useToast-DgnKZwIF.js +1 -0
  29. package/dist/web-ui/index.html +3 -3
  30. package/package.json +3 -2
  31. package/dist/web-ui/assets/LayoutView-DHXc1jcz.js +0 -1
  32. package/dist/web-ui/assets/PluginsView-CNDVeVj8.js +0 -2
  33. package/dist/web-ui/assets/PluginsView-zyTILSF1.css +0 -1
  34. package/dist/web-ui/assets/ProvidersView-DTIx2VSi.js +0 -14
  35. package/dist/web-ui/assets/SettingsView-W9bjQ9ly.js +0 -1
  36. package/dist/web-ui/assets/useToast-nQZ8k6t7.js +0 -1
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{dirname as e,join as t,resolve as n}from"node:path";import{fileURLToPath as r,pathToFileURL as i}from"node:url";import{cwd as a}from"node:process";import{existsSync as o,mkdirSync as s,readFileSync as c,readdirSync as l,renameSync as u,rmSync as d,statSync as f,writeFileSync as p}from"node:fs";import{homedir as ee}from"node:os";import{createHash as te,randomBytes as ne}from"node:crypto";import{spawn as re}from"node:child_process";import{createServer as ie}from"node:http";function m(e,...t){console.log(e,...t)}function h(e,...t){console.warn(e,...t)}function ae(e,...t){console.error(e,...t)}function g(e){if(e===void 0)return;let t=e.trim();return t===``?void 0:t}function _(e){let t=`--${e}`,n=`${t}=`,r=process.argv;for(let e=0;e<r.length;e+=1){let i=r[e]??``;if(i.startsWith(n))return g(i.slice(n.length));if(i===t){let t=r[e+1]??``;return t.startsWith(`-`)?void 0:g(t)}}}function v(e,t){return _(e)??g(process.env[t])}var oe=65535;function se(e){let t=g(e);if(t===void 0)return;let n=Number(t);return Number.isInteger(n)&&n>0&&n<=oe?n:void 0}var ce=(()=>{let e=v(`app-dir`,`ELLM_APP_DIR`),t=e===void 0?void 0:n(a(),e),r=_(`port`),i=se(r??g(process.env.ELLM_PORT));r!==void 0&&i===void 0&&h(`--port=${r} 无效(需为 1-65535 的整数),已忽略:本次不生效、也不会写入配置文件。`);let o=v(`node-mirror`,`ELLM_NODE_MIRROR`),s=v(`node-bin`,`ELLM_NODE_BIN`);return t!==void 0&&(process.env.ELLM_APP_DIR=t),o!==void 0&&(process.env.ELLM_NODE_MIRROR=o),s!==void 0&&(process.env.ELLM_NODE_BIN=s),{appDir:t,port:i,nodeMirror:o,nodeBin:s}})(),y=e(r(import.meta.url)),b;function le(){return t(ee(),`.ezllm`)}function x(){return b===void 0&&(b=ce.appDir??le()),b}function ue(){return n(x(),`plugins`)}function de(){return n(x(),`plugin-auth.json`)}function fe(e){try{let n=c(t(e,`package.json`),`utf-8`).replace(/^\uFEFF/,``);return JSON.parse(n)}catch{return null}}function S(t,n){let r=y;for(let i=0;i<n;i++){if(fe(r)?.name===t)return r;r=e(r)}return null}S(`ezllm`,5)??n(y,`..`),S(`ellm`,6);function pe(e,t){let n=`${e}.${`${process.pid}.${ne(6).toString(`hex`)}`}.tmp`;try{p(n,JSON.stringify(t,null,2)+`
3
- `,`utf-8`),u(n,e)}finally{try{d(n,{force:!0})}catch{}}}function C(e){try{let t=JSON.parse(c(e,`utf-8`));if(typeof t!=`object`||!t||Array.isArray(t))return{};let n={};for(let[e,r]of Object.entries(t))typeof r==`object`&&r&&!Array.isArray(r)&&(n[e]=r);return n}catch{return{}}}function w(e,t){pe(e,t)}var me=class{#e;#t;constructor(e){this.#e=e,this.#t=C(e)}reload(){this.#t=C(this.#e)}get(e){let t=this.#t[e];return t===void 0?void 0:{...t}}has(e){return e in this.#t}providers(){return Object.keys(this.#t)}save(e,t){let n={...this.#t,[e]:{...t}};w(this.#e,n),this.#t=n}remove(e){if(!(e in this.#t))return;let t={...this.#t};delete t[e],w(this.#e,t),this.#t=t}},he=/^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(@[a-zA-Z0-9.^~<>=*|xX -]+)?$/,T=3e5,ge=65536,_e=`npm_config_`;function E(e){let t=e.trim();if(t===``||!he.test(t))return null;let n=t.lastIndexOf(`@`);return n<=0?{name:t,version:``}:{name:t.slice(0,n),version:t.slice(n+1)}}function ve(e,t=e=>e){let n=e.trim();if(n===``)return null;if(n.startsWith(`./`)||n.startsWith(`../`)||n.startsWith(`file:`)||n.startsWith(`/`)||/^[a-zA-Z]:[\\/]/.test(n)||n.startsWith(`\\\\`)){let e=n.startsWith(`file:`)?n.slice(5):n;return e.trim()===``?null:{kind:`path`,path:t(e.trim())}}let r=E(n);return r===null?null:{kind:`npm`,name:r.name,version:r.version}}function ye(e){return o(e)?f(e).isDirectory()?o(t(e,`package.json`))?null:`目录里没有 package.json:${e}`:`不是目录:${e}`:`目录不存在:${e}`}function D(e){return E(e)?.name??null}function be(e,n){return t(e,`node_modules`,...n.split(`/`))}function xe(n,r){let i=e=>e.endsWith(`npm-cli.js`)&&o(e),a=e=>{for(let n of[[`node_modules`,`npm`,`bin`,`npm-cli.js`],[`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`],[`bin`,`npm-cli.js`]]){let r=t(e,...n);if(i(r))return r}return null};if(n!==``){let e=a(n);if(e!==null)return e;if(i(n))return n;let r=t(n,`npm`,`bin`,`npm-cli.js`);if(i(r))return r}let s=t(e(process.execPath),`node_modules`,`npm`,`bin`,`npm-cli.js`);if(i(s))return s;let c=t(e(process.execPath),`..`,`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`);if(i(c))return c;if(r!==null){let e=t(r,`node`),n=a(e);if(n!==null)return n;try{for(let n of l(e)){let r=a(t(e,n));if(r!==null)return r}}catch{}}return null}function O(e,t,n){return new Promise(r=>{let i=re(e,[...t],{shell:n.shell,cwd:n.cwd,windowsHide:!0,...n.env===void 0?{}:{env:n.env}}),a=``,o=e=>{a.length<ge&&(a+=e.toString(`utf8`))};i.stdout.on(`data`,o),i.stderr.on(`data`,o);let s=setTimeout(()=>{a+=`\n[npm 超时 ${String(T/1e3)}s,已终止]`,i.kill()},T);i.on(`error`,e=>{clearTimeout(s),r({code:null,output:`${a}\n${e.message}`})}),i.on(`close`,e=>{clearTimeout(s),r({code:e,output:a})})})}function Se(e){let t={};for(let[n,r]of Object.entries(e))r!==void 0&&(n.toLowerCase().startsWith(_e)||(t[n]=r));return t}async function k(e,t,n,r,i){let a={...r??{}};i!==void 0&&(a.NPM_CONFIG_USERCONFIG=i,a.NPM_CONFIG_GLOBALCONFIG=`${i}.global-empty`);let o={...Se(process.env),...a};return e===null?O(process.platform===`win32`?`npm.cmd`:`npm`,t,{shell:process.platform===`win32`,cwd:n,env:o}):O(process.execPath,[e,...t],{shell:!1,cwd:n,env:o})}function Ce(e){s(e,{recursive:!0});let n=t(e,`package.json`);o(n)||p(n,`${JSON.stringify({name:`ezllm-plugins`,private:!0,version:`0.0.0`},null,2)}\n`,`utf8`)}async function A(e,t){let r=ve(t,e=>n(e));if(r===null)return{ok:!1,message:`不是合法的 npm 包规格,也不是本机目录:${t}`};if(r.kind===`path`){let e=ye(r.path);if(e!==null)return{ok:!1,message:e}}Ce(e.pluginsDir);let i=r.kind===`npm`?r.version===``?r.name:`${r.name}@${r.version}`:`file:${r.path}`,a=[`install`,`--no-audit`,`--no-fund`,`--save`,i];e.log(`[plugin-host] npm install ${i}`);let o=await k(e.npmCli,a,e.pluginsDir,e.env,e.userconfig);return o.code===0?{ok:!0,output:o.output.trim()}:{ok:!1,message:`npm install 失败(退出码 ${String(o.code)}):\n${o.output.trim().slice(-2e3)}`}}async function we(e,t){let n=D(t);if(n===null)return{ok:!1,message:`不是合法的 npm 包名:${t}`};let r=be(e.pluginsDir,n);if(!o(r))return{ok:!1,message:`未安装:${n}`};e.log(`[plugin-host] npm uninstall ${n}`);let i=await k(e.npmCli,[`uninstall`,`--no-audit`,`--no-fund`,n],e.pluginsDir,e.env,e.userconfig);if(o(r))try{d(r,{recursive:!0,force:!0}),e.log(i.code===0?`[plugin-host] npm 未清理 ${n} 的目录,已手动删除`:`[plugin-host] npm uninstall 退出码 ${String(i.code)},已手动删除 ${n} 的目录`)}catch(e){return{ok:!1,message:`卸载 ${n} 未完成:npm 退出码 ${String(i.code)},且目录残留删除失败(${e instanceof Error?e.message:String(e)})\n${i.output.trim().slice(-1500)}`}}return i.code!==0&&!o(r)?{ok:!0,output:`npm 退出码 ${String(i.code)}(目录已删除)\n${i.output.trim().slice(-1500)}`}:i.code===0?{ok:!0,output:i.output.trim()}:{ok:!1,message:`npm uninstall 失败(退出码 ${String(i.code)}):\n${i.output.trim().slice(-2e3)}`}}function Te(e){for(let n of[`README.md`,`readme.md`,`README.MD`,`README`]){let r=t(e,n);if(o(r))try{return c(r,`utf8`).slice(0,204800)}catch{return null}}return null}var Ee=[{npm:`@ai-sdk/openai-compatible`,protocol:`openai`,path:`chat/completions`},{npm:`@ai-sdk/anthropic`,protocol:`anthropic`,path:`messages`},{npm:`@ai-sdk/openai`,protocol:`openai-responses`,path:`responses`}];function De(e){return typeof e!=`string`||e===``?null:Ee.find(t=>t.npm===e)??null}function j(e){return new Proxy(async()=>({data:void 0}),{get(t,n){if(n!==`then`)return j(`${e}.${String(n)}`)},apply(){return Promise.resolve({data:void 0})}})}function Oe(e){let t={auth:{set:async t=>{let n=t?.path?.id??t?.id??t?.providerID,r=typeof n==`string`&&n!==``?n:e.scope?.key,i=t?.body??t?.auth;return typeof r==`string`&&r!==``&&typeof i==`object`&&i&&await e.saveAuth(r,i),{data:!0}},remove:async()=>({data:!0})},tui:{showToast:async()=>({data:!0})},app:{log:async()=>({data:!0})},config:{get:async()=>({data:{provider:{}}})}},n=(e,t)=>new Proxy(e,{get(e,r){if(typeof r==`symbol`)return Reflect.get(e,r);if(r in e){let i=e[r];return typeof i==`object`&&i?n(i,`${t}.${r}`):i}if(r!==`then`)return j(`${t}.${r}`)}});return n(t,`client`)}function ke(e){return{client:Oe(e),directory:e.directory,worktree:e.directory,project:{id:`ezllm`,worktree:e.directory},serverUrl:new URL(`http://127.0.0.1:4096`),$:()=>{throw Error("插件请求了 shell(`$`),但 ezllm 的插件宿主不提供:当前插件生态没有任何插件使用它;若某个插件真的需要,请提交 issue 说明用途(提供 shell 等于给第三方代码任意命令执行能力)")}}}var M=`@magpie-community`;function N(e){let n=t(e,`node_modules`,M);if(!o(n))return[];let r=[];for(let e of l(n)){let i=t(n,e),a=t(i,`package.json`);if(o(a))try{let t=JSON.parse(c(a,`utf-8`)),n=typeof t.name==`string`&&t.name!==``?t.name:`${M}/${e}`,o=t.exports,s=typeof o==`string`?o:typeof o==`object`&&o?o[`.`]:void 0,l=typeof t.main==`string`?t.main:s;if(typeof l!=`string`||l===``)continue;let u=t.repository,d=typeof u==`string`?u:typeof u==`object`&&u&&typeof u.url==`string`?String(u.url):``;r.push({name:n,version:typeof t.version==`string`?t.version:`0.0.0`,main:l,description:typeof t.description==`string`?t.description:``,license:typeof t.license==`string`?t.license:``,repository:d,dir:i})}catch{}}return r}function Ae(e,n){let r=n.name.startsWith(`${M}/`)?n.name.slice(18):n.name;return t(e,`node_modules`,M,r,n.main)}function P(e,t){return N(e).find(e=>e.name===t)}function je(e){let t=[];for(let[n,r]of Object.entries(e))n==="default"||n.startsWith(`_`)||typeof r==`function`&&t.push([n,r]);return t}function Me(e,t,n){let r=[],i=e=>{let t=n.find(t=>t.auth?.provider===e)??n.find(t=>t.provider?.id===e);return t===void 0||t.auth===void 0?null:{hooks:t,auth:t.auth}};for(let[n,a]of Object.entries(t.provider??{})){let t=a??{},o=i(n),s=De(typeof t.npm==`string`?t.npm:void 0);r.push({id:n,name:typeof t.name==`string`&&t.name!==``?t.name:n,source:{package:e.name,version:e.version},protocol:s?.protocol??null,path:s?.path??``,models:Object.keys(t.models??{}),config:t,hooks:o,capabilities:{models:typeof o?.hooks.provider?.models==`function`,usage:typeof o?.auth.usage==`function`,checkin:typeof o?.auth.checkin==`function`,refresh:typeof o?.auth.refresh==`function`,chatHeaders:typeof o?.hooks[`chat.headers`]==`function`}})}return r}async function Ne(e,t){let n=[],r=[];for(let a of N(e)){let s=Ae(e,a);if(!o(s)){r.push(`${a.name}: 入口不存在(${s})`);continue}let c;try{c=await import(i(s).href)}catch(e){r.push(`${a.name}: 导入失败(${e instanceof Error?e.message:String(e)})`);continue}let l=je(c);if(l.length===0){r.push(`${a.name}: 没有导出插件工厂函数`);continue}let u=[],d={provider:{}};for(let[e,n]of l)try{let e=await n(ke(t));u.push(e),typeof e.config==`function`&&await e.config(d)}catch(t){r.push(`${a.name}#${e}: 工厂/config 失败(${t instanceof Error?t.message:String(t)})`)}if(u.length===0)continue;let f=Me(a,d,u);n.push({package:a.name,version:a.version,providers:f,hooks:u})}return{plugins:n,errors:r}}function F(e){let t=new Set,n=[],r=[...e.errors];for(let i of e.plugins)for(let e of i.providers){if(t.has(e.id)){r.push(`${e.id}: provider id 重复(${e.source.package}),已忽略后一个`);continue}t.add(e.id),n.push(e)}return{providers:n,errors:r}}function Pe(e,t=process.env){let n={host:`127.0.0.1`,port:Number(t.PORT||29772),pluginsDir:t.PLUGIN_DIR||``,authFile:``,apiKey:t.PLUGIN_HOST_KEY||``,nodeDir:t.NODE_DIR||``,skipCheck:!1,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`--plugins-dir`:n.pluginsDir=s()||n.pluginsDir;break;case`--auth-file`:n.authFile=s()||n.authFile;break;case`--api-key`:n.apiKey=s()||``;break;case`--node-dir`:n.nodeDir=s()||``;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,pluginsDir:n.pluginsDir,authFile:n.authFile,apiKey:n.apiKey,nodeDir:n.nodeDir,skipCheck:n.skipCheck}}}function Fe(){return[`plugin-host(OpenCode 插件服务:加载订阅插件并暴露成 OpenAI 兼容端点)`,``,`用法: plugin-host [选项]`,` --host HOST 监听地址(默认 127.0.0.1)`,` --port PORT 监听端口(默认 29772;托管形态由 daemon 注入 PORT = 主端口+2)`,` --plugins-dir DIR 插件安装目录(默认 <APP_DIR>/plugins;托管形态由 daemon 注入 PLUGIN_DIR)`,` --auth-file PATH 凭据文件(默认 <APP_DIR>/plugin-auth.json)`,` --api-key KEY 要求客户端携带的 API key(默认不校验;安装面是写操作,建议设)`,` --node-dir DIR 托管 Node 目录(安装面用其中的 node+npm;默认自动探测)`,` --skip-check 跳过启动预检`,``,`端点: GET /health · GET /plugins · GET / · GET /packages · GET /packages/<name>`,` POST /packages({package, version?})· DELETE /packages/<name>`,` GET /p/<id>/v1/models · POST /p/<id>/v1/{chat/completions|messages|responses}`,` GET /login/<id>(登录方式与状态)· POST /login/<id>(起登录,{method?})`,``,`环境变量(托管形态注入): PLUGIN_DIR · PORT · NODE_DIR`,``].join(`
2
+ import{dirname as e,join as t,resolve as n}from"node:path";import{fileURLToPath as r,pathToFileURL as i}from"node:url";import{cwd as a}from"node:process";import{existsSync as o,mkdirSync as s,readFileSync as c,readdirSync as l,renameSync as u,rmSync as d,statSync as f,writeFileSync as p}from"node:fs";import{homedir as m}from"node:os";import{createHash as h,randomBytes as g}from"node:crypto";import{spawn as ee}from"node:child_process";import{createServer as te}from"node:http";function _(e,...t){console.log(e,...t)}function v(e,...t){console.warn(e,...t)}function ne(e,...t){console.error(e,...t)}function y(e){if(e===void 0)return;let t=e.trim();return t===``?void 0:t}function b(e){let t=`--${e}`,n=`${t}=`,r=process.argv;for(let e=0;e<r.length;e+=1){let i=r[e]??``;if(i.startsWith(n))return y(i.slice(n.length));if(i===t){let t=r[e+1]??``;return t.startsWith(`-`)?void 0:y(t)}}}function x(e,t){return b(e)??y(process.env[t])}var re=65535;function ie(e){let t=y(e);if(t===void 0)return;let n=Number(t);return Number.isInteger(n)&&n>0&&n<=re?n:void 0}var ae=(()=>{let e=x(`app-dir`,`ELLM_APP_DIR`),t=e===void 0?void 0:n(a(),e),r=b(`port`),i=ie(r??y(process.env.ELLM_PORT));r!==void 0&&i===void 0&&v(`--port=${r} 无效(需为 1-65535 的整数),已忽略:本次不生效、也不会写入配置文件。`);let o=x(`node-mirror`,`ELLM_NODE_MIRROR`),s=x(`node-bin`,`ELLM_NODE_BIN`);return t!==void 0&&(process.env.ELLM_APP_DIR=t),o!==void 0&&(process.env.ELLM_NODE_MIRROR=o),s!==void 0&&(process.env.ELLM_NODE_BIN=s),{appDir:t,port:i,nodeMirror:o,nodeBin:s}})(),S=e(r(import.meta.url)),C;function oe(){return t(m(),`.ezllm`)}function w(){return C===void 0&&(C=ae.appDir??oe()),C}function se(){return n(w(),`plugins`)}function ce(){return n(w(),`plugin-auth.json`)}function le(e){try{let n=c(t(e,`package.json`),`utf-8`).replace(/^\uFEFF/,``);return JSON.parse(n)}catch{return null}}function ue(t,n){let r=S;for(let i=0;i<n;i++){if(le(r)?.name===t)return r;r=e(r)}return null}ue(`ezllm`,5)??n(S,`..`),ue(`ellm`,6);function de(e,t){let n=`${e}.${`${process.pid}.${g(6).toString(`hex`)}`}.tmp`;try{p(n,JSON.stringify(t,null,2)+`
3
+ `,`utf-8`),u(n,e)}finally{try{d(n,{force:!0})}catch{}}}var fe=`https://registry.npmmirror.com/`,pe=`https://registry.npmjs.org/`;function me(){return[fe,pe]}async function he(e,t=me()){let n;for(let r of t)try{return{value:await e(r),registry:r}}catch(e){n=e}throw n??Error(`没有任何可用的 npm registry`)}function T(e){try{let t=JSON.parse(c(e,`utf-8`));if(typeof t!=`object`||!t||Array.isArray(t))return{};let n={};for(let[e,r]of Object.entries(t))typeof r==`object`&&r&&!Array.isArray(r)&&(n[e]=r);return n}catch{return{}}}function E(e,t){de(e,t)}var ge=class{#e;#t;constructor(e){this.#e=e,this.#t=T(e)}reload(){this.#t=T(this.#e)}get(e){let t=this.#t[e];return t===void 0?void 0:{...t}}has(e){return e in this.#t}providers(){return Object.keys(this.#t)}save(e,t){let n={...this.#t,[e]:{...t}};E(this.#e,n),this.#t=n}remove(e){if(!(e in this.#t))return;let t={...this.#t};delete t[e],E(this.#e,t),this.#t=t}},_e=/^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(@[a-zA-Z0-9.^~<>=*|xX -]+)?$/,D=3e5,ve=65536,ye=`npm_config_`;function O(e){let t=e.trim();if(t===``||!_e.test(t))return null;let n=t.lastIndexOf(`@`);return n<=0?{name:t,version:``}:{name:t.slice(0,n),version:t.slice(n+1)}}function be(e,t=e=>e){let n=e.trim();if(n===``)return null;if(n.startsWith(`./`)||n.startsWith(`../`)||n.startsWith(`file:`)||n.startsWith(`/`)||/^[a-zA-Z]:[\\/]/.test(n)||n.startsWith(`\\\\`)){let e=n.startsWith(`file:`)?n.slice(5):n;return e.trim()===``?null:{kind:`path`,path:t(e.trim())}}let r=O(n);return r===null?null:{kind:`npm`,name:r.name,version:r.version}}function xe(e){return o(e)?f(e).isDirectory()?o(t(e,`package.json`))?null:`目录里没有 package.json:${e}`:`不是目录:${e}`:`目录不存在:${e}`}function k(e){return O(e)?.name??null}function Se(e,n){return t(e,`node_modules`,...n.split(`/`))}function Ce(n,r){let i=e=>e.endsWith(`npm-cli.js`)&&o(e),a=e=>{for(let n of[[`node_modules`,`npm`,`bin`,`npm-cli.js`],[`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`],[`bin`,`npm-cli.js`]]){let r=t(e,...n);if(i(r))return r}return null};if(n!==``){let e=a(n);if(e!==null)return e;if(i(n))return n;let r=t(n,`npm`,`bin`,`npm-cli.js`);if(i(r))return r}let s=t(e(process.execPath),`node_modules`,`npm`,`bin`,`npm-cli.js`);if(i(s))return s;let c=t(e(process.execPath),`..`,`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`);if(i(c))return c;if(r!==null){let e=t(r,`node`),n=a(e);if(n!==null)return n;try{for(let n of l(e)){let r=a(t(e,n));if(r!==null)return r}}catch{}}return null}function A(e,t,n){return new Promise(r=>{let i=ee(e,[...t],{shell:n.shell,cwd:n.cwd,windowsHide:!0,...n.env===void 0?{}:{env:n.env}}),a=``,o=e=>{a.length<ve&&(a+=e.toString(`utf8`))};i.stdout.on(`data`,o),i.stderr.on(`data`,o);let s=setTimeout(()=>{a+=`\n[npm 超时 ${String(D/1e3)}s,已终止]`,i.kill()},D);i.on(`error`,e=>{clearTimeout(s),r({code:null,output:`${a}\n${e.message}`})}),i.on(`close`,e=>{clearTimeout(s),r({code:e,output:a})})})}function we(e){let t={};for(let[n,r]of Object.entries(e))r!==void 0&&(n.toLowerCase().startsWith(ye)||(t[n]=r));return t}async function j(e,t,n,r,i){let a={...r??{}};i!==void 0&&(a.NPM_CONFIG_USERCONFIG=i,a.NPM_CONFIG_GLOBALCONFIG=`${i}.global-empty`);let o={...we(process.env),...a};return e===null?A(process.platform===`win32`?`npm.cmd`:`npm`,t,{shell:process.platform===`win32`,cwd:n,env:o}):A(process.execPath,[e,...t],{shell:!1,cwd:n,env:o})}async function Te(e,t){let n=await he(async n=>{let r=await j(e.npmCli,[`view`,t,`version`,`--json`,`--registry`,n],e.pluginsDir,e.env,e.userconfig),i=r.code===0?Ee(r.output):null;if(i===null)throw Error(`npm view 未能取到 ${t} 的版本(源 ${n})`);return i}).catch(()=>null);return n===null?null:n.value}function Ee(e){let t=e=>{try{let t=JSON.parse(e);return typeof t==`string`&&t!==``?t:null}catch{return null}},n=e.trim(),r=t(n);if(r!==null)return r;let i=n.split(/\r?\n/);for(let e=i.length-1;e>=0;e--){let n=(i[e]??``).trim();if(n===``)continue;let r=t(n);if(r!==null)return r}return null}function De(e){s(e,{recursive:!0});let n=t(e,`package.json`);o(n)||p(n,`${JSON.stringify({name:`ezllm-plugins`,private:!0,version:`0.0.0`},null,2)}\n`,`utf8`)}async function Oe(e,t){let r=be(t,e=>n(e));if(r===null)return{ok:!1,message:`不是合法的 npm 包规格,也不是本机目录:${t}`};if(r.kind===`path`){let e=xe(r.path);if(e!==null)return{ok:!1,message:e}}De(e.pluginsDir);let i=r.kind===`npm`?r.version===``?r.name:`${r.name}@${r.version}`:`file:${r.path}`,a=[`install`,`--no-audit`,`--no-fund`,`--save`,i];return e.log(`[plugin-host] npm install ${i}`),await he(async t=>{let n=await j(e.npmCli,[...a,`--registry`,t],e.pluginsDir,e.env,e.userconfig);if(n.code!==0)throw Error(`npm install 失败(退出码 ${String(n.code)},源 ${t}):\n${n.output.trim().slice(-2e3)}`);return{ok:!0,output:n.output.trim()}}).then(({value:e})=>e).catch(e=>({ok:!1,message:e instanceof Error?e.message:`npm install 失败:${String(e)}`}))}async function ke(e,t){let n=k(t);if(n===null)return{ok:!1,message:`不是合法的 npm 包名:${t}`};let r=Se(e.pluginsDir,n);if(!o(r))return{ok:!1,message:`未安装:${n}`};e.log(`[plugin-host] npm uninstall ${n}`);let i=await j(e.npmCli,[`uninstall`,`--no-audit`,`--no-fund`,n],e.pluginsDir,e.env,e.userconfig);if(o(r))try{d(r,{recursive:!0,force:!0}),e.log(i.code===0?`[plugin-host] npm 未清理 ${n} 的目录,已手动删除`:`[plugin-host] npm uninstall 退出码 ${String(i.code)},已手动删除 ${n} 的目录`)}catch(e){return{ok:!1,message:`卸载 ${n} 未完成:npm 退出码 ${String(i.code)},且目录残留删除失败(${e instanceof Error?e.message:String(e)})\n${i.output.trim().slice(-1500)}`}}return i.code!==0&&!o(r)?{ok:!0,output:`npm 退出码 ${String(i.code)}(目录已删除)\n${i.output.trim().slice(-1500)}`}:i.code===0?{ok:!0,output:i.output.trim()}:{ok:!1,message:`npm uninstall 失败(退出码 ${String(i.code)}):\n${i.output.trim().slice(-2e3)}`}}function Ae(e){for(let n of[`README.md`,`readme.md`,`README.MD`,`README`]){let r=t(e,n);if(o(r))try{return c(r,`utf8`).slice(0,204800)}catch{return null}}return null}var je=[{npm:`@ai-sdk/openai-compatible`,protocol:`openai`,path:`chat/completions`},{npm:`@ai-sdk/anthropic`,protocol:`anthropic`,path:`messages`},{npm:`@ai-sdk/openai`,protocol:`openai-responses`,path:`responses`}];function Me(e){return typeof e!=`string`||e===``?null:je.find(t=>t.npm===e)??null}function M(e){return new Proxy(async()=>({data:void 0}),{get(t,n){if(n!==`then`)return M(`${e}.${String(n)}`)},apply(){return Promise.resolve({data:void 0})}})}function Ne(e){let t={auth:{set:async t=>{let n=t?.path?.id??t?.id??t?.providerID,r=typeof n==`string`&&n!==``?n:e.scope?.key,i=t?.body??t?.auth;return typeof r==`string`&&r!==``&&typeof i==`object`&&i&&await e.saveAuth(r,i),{data:!0}},remove:async()=>({data:!0})},tui:{showToast:async()=>({data:!0})},app:{log:async()=>({data:!0})},config:{get:async()=>({data:{provider:{}}})}},n=(e,t)=>new Proxy(e,{get(e,r){if(typeof r==`symbol`)return Reflect.get(e,r);if(r in e){let i=e[r];return typeof i==`object`&&i?n(i,`${t}.${r}`):i}if(r!==`then`)return M(`${t}.${r}`)}});return n(t,`client`)}function Pe(e){return{client:Ne(e),directory:e.directory,worktree:e.directory,project:{id:`ezllm`,worktree:e.directory},serverUrl:new URL(`http://127.0.0.1:4096`),$:()=>{throw Error("插件请求了 shell(`$`),但 ezllm 的插件宿主不提供:当前插件生态没有任何插件使用它;若某个插件真的需要,请提交 issue 说明用途(提供 shell 等于给第三方代码任意命令执行能力)")}}}var Fe=`@magpie-community`,Ie=`ezllmPlugin`;function Le(e){let t=e.exports,n=typeof t==`string`?t:typeof t==`object`&&t?t[`.`]:void 0,r=typeof e.main==`string`?e.main:n;return typeof r==`string`&&r!==``?r:void 0}function N(e,r){let i=t(e,`package.json`);if(!o(i))return null;let a;try{let e=JSON.parse(c(i,`utf-8`));if(typeof e!=`object`||!e||Array.isArray(e))return null;a=e}catch{return null}let s=typeof a.name==`string`&&a.name!==``?a.name:r;if(!s.startsWith(`${Fe}/`)&&a[Ie]!==!0)return null;let l=Le(a);if(l===void 0)return null;let u=a.repository,d=typeof u==`string`?u:typeof u==`object`&&u&&typeof u.url==`string`?String(u.url):``;return{name:s,version:typeof a.version==`string`?a.version:`0.0.0`,main:l,description:typeof a.description==`string`?a.description:``,license:typeof a.license==`string`?a.license:``,repository:d,dir:n(e)}}function P(e){let n=t(e,`node_modules`);if(!o(n))return[];let r;try{r=l(n,{withFileTypes:!0})}catch{return[]}let i=[];for(let e of r){if(e.name.startsWith(`.`))continue;if(!e.name.startsWith(`@`)){let r=N(t(n,e.name),e.name);r!==null&&i.push(r);continue}let r=t(n,e.name),a;try{a=l(r,{withFileTypes:!0})}catch{continue}for(let n of a){let a=N(t(r,n.name),`${e.name}/${n.name}`);a!==null&&i.push(a)}}return i}function Re(e){return t(e.dir,e.main)}function ze(e,t){return P(e).find(e=>e.name===t)}function Be(e){let t=[];for(let[n,r]of Object.entries(e))n==="default"||n.startsWith(`_`)||typeof r==`function`&&t.push([n,r]);return t}function Ve(e,t,n){let r=[],i=e=>{let t=n.find(t=>t.auth?.provider===e)??n.find(t=>t.provider?.id===e);return t===void 0||t.auth===void 0?null:{hooks:t,auth:t.auth}};for(let[n,a]of Object.entries(t.provider??{})){let t=a??{},o=i(n),s=Me(typeof t.npm==`string`?t.npm:void 0);r.push({id:n,name:typeof t.name==`string`&&t.name!==``?t.name:n,source:{package:e.name,version:e.version},protocol:s?.protocol??null,path:s?.path??``,models:Object.keys(t.models??{}),config:t,hooks:o,capabilities:{models:typeof o?.hooks.provider?.models==`function`,usage:typeof o?.auth.usage==`function`,checkin:typeof o?.auth.checkin==`function`,refresh:typeof o?.auth.refresh==`function`,chatHeaders:typeof o?.hooks[`chat.headers`]==`function`}})}return r}async function He(e,t){let n=[],r=[];for(let a of P(e)){let e=Re(a);if(!o(e)){r.push(`${a.name}: 入口不存在(${e})`);continue}let s;try{s=await import(i(e).href)}catch(e){r.push(`${a.name}: 导入失败(${e instanceof Error?e.message:String(e)})`);continue}let c=Be(s);if(c.length===0){r.push(`${a.name}: 没有导出插件工厂函数`);continue}let l=[],u={provider:{}};for(let[e,n]of c)try{let e=await n(Pe(t));l.push(e),typeof e.config==`function`&&await e.config(u)}catch(t){r.push(`${a.name}#${e}: 工厂/config 失败(${t instanceof Error?t.message:String(t)})`)}if(l.length===0)continue;let d=Ve(a,u,l);n.push({package:a.name,version:a.version,providers:d,hooks:l})}return{plugins:n,errors:r}}function F(e){let t=new Set,n=[],r=[...e.errors];for(let i of e.plugins)for(let e of i.providers){if(t.has(e.id)){r.push(`${e.id}: provider id 重复(${e.source.package}),已忽略后一个`);continue}t.add(e.id),n.push(e)}return{providers:n,errors:r}}function Ue(e,t=process.env){let n={host:`127.0.0.1`,port:Number(t.PORT||29772),pluginsDir:t.PLUGIN_DIR||``,authFile:``,apiKey:t.PLUGIN_HOST_KEY||``,nodeDir:t.NODE_DIR||``,mainPort:Number(t.MAIN_PORT||0),skipCheck:!1,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`--plugins-dir`:n.pluginsDir=s()||n.pluginsDir;break;case`--auth-file`:n.authFile=s()||n.authFile;break;case`--api-key`:n.apiKey=s()||``;break;case`--node-dir`:n.nodeDir=s()||``;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,pluginsDir:n.pluginsDir,authFile:n.authFile,apiKey:n.apiKey,nodeDir:n.nodeDir,mainPort:n.mainPort,skipCheck:n.skipCheck}}}function We(){return[`plugin-host(OpenCode 插件服务:加载订阅插件并暴露成 OpenAI 兼容端点)`,``,`用法: plugin-host [选项]`,` --host HOST 监听地址(默认 127.0.0.1)`,` --port PORT 监听端口(默认 29772;托管形态由 daemon 注入 PORT = 主端口+2)`,` --plugins-dir DIR 插件安装目录(默认 <APP_DIR>/plugins;托管形态由 daemon 注入 PLUGIN_DIR)`,` --auth-file PATH 凭据文件(默认 <APP_DIR>/plugin-auth.json)`,` --api-key KEY 要求客户端携带的 API key(默认不校验;安装面是写操作,建议设)`,` --node-dir DIR 托管 Node 目录(安装面用其中的 node+npm;默认自动探测)`,` --skip-check 跳过启动预检`,``,`端点: GET /health · GET /plugins · GET / · GET /packages · GET /packages/<name>`,` POST /packages({package, version?})· DELETE /packages/<name>`,` GET /p/<id>/v1/models · POST /p/<id>/v1/{chat/completions|messages|responses}`,` GET /login/<id>(登录方式与状态)· POST /login/<id>(起登录,{method?})`,``,`环境变量(托管形态注入): PLUGIN_DIR · PORT · NODE_DIR`,``].join(`
4
4
  `)+`
5
- `}var Ie=3e5,Le=864e5,Re=15e3,ze=3e4,I=new class{renewing=new Map;failed=new Map};function Be(e){let t=`${String(e?.refresh??``)}\0${String(e?.access??``)}`;return te(`sha256`).update(t).digest(`hex`).slice(0,16)}function Ve(e){let t=e.refreshLead;return typeof t==`number`&&Number.isFinite(t)&&t>=0?Math.min(t,Le):Ie}function He(e,t){if(typeof e.refresh!=`function`||t.type!==`oauth`)return 0;let n=Number(t.expires);return Number.isFinite(n)&&n>0?Math.max(1,n-Ve(e)):0}var Ue=class{#e;#t;#n;constructor(e,t,n){this.#e=e,this.#t=t,this.#n=n}current(){return this.#n.store.get(this.#e)}getAuth=async()=>this.current();async ensureFresh(){let e=this.#n.now?.()??Date.now(),t=this.current();if(t===void 0)return;let n=He(this.#t,t);if(!n||e<n)return;let r=this.#n.registry??I,i=`${this.#e}\0${Be(t)}`,a=r.failed.get(i);if(a!==void 0&&e-a<ze)return;let o=r.renewing.get(i);o===void 0&&(o=this.#r(t,i).finally(()=>r.renewing.delete(i)),r.renewing.set(i,o));let s;await Promise.race([o,new Promise(e=>{s=setTimeout(e,Re)})]),clearTimeout(s)}async#r(e,t){let n=this.#n.registry??I,r=this.#t.refresh;if(r!==void 0)try{let i=await r({...e},this.#n.providerInfo);typeof i==`object`&&i&&this.#n.store.save(this.#e,{...e,...i}),n.failed.delete(t),this.#n.log?.(`[plugin-host] 凭据已续期:${this.#e}`)}catch(e){n.failed.set(t,this.#n.now?.()??Date.now()),this.#n.log?.(`[plugin-host] 凭据续期失败:${this.#e}(${e instanceof Error?e.message:String(e)})`)}}},We=class extends Error{status;detail;constructor(e,t,n){super(t),this.name=`HttpError`,this.status=e,this.detail=n}};function L(e,t){return{error:{message:e,type:t}}}function R(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Ge(e){let t=e.search(/[?#]/);return t>=0?e.slice(0,t):e}function z(e,t,n){if(e.headersSent){try{e.end()}catch{}return}e.writeHead(t,{"Content-Type":`application/json`}),e.end(JSON.stringify(n))}function B(e,t,n){z(e,t,{detail:n})}function V(e=process.env){return Number(e.PLUGIN_HOST_MAX_BODY||33554432)}function H(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 We(413,`request body too large`,L(`request body too large`,`invalid_request_error`)));return}i.push(n)}),e.on(`end`,()=>{n(Buffer.concat(i))}),e.on(`error`,r)})}async function*Ke(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{}}}var qe=new Set([`host`,`connection`,`keep-alive`,`proxy-authenticate`,`proxy-authorization`,`te`,`trailer`,`transfer-encoding`,`upgrade`,`content-length`,`authorization`,`x-api-key`,`x-plugin-session`]),Je=`x-plugin-session`;function Ye(e,t){return t===void 0||t===``?{}:e===`anthropic`?{"x-api-key":t}:{authorization:`Bearer ${t}`}}function Xe(e){let t={};for(let[n,r]of Object.entries(e.headers)){let e=n.toLowerCase();qe.has(e)||r!==void 0&&(t[e]=Array.isArray(r)?r.join(`, `):r)}return t}function Ze(e,t){let n=new Headers;for(let[r,i]of Object.entries(Ye(e.provider.protocol,t.apiKey)))n.set(r,i);for(let[e,r]of Object.entries(t.headers??{}))n.set(e,String(r));for(let[t,r]of Object.entries(Xe(e.req)))n.set(t,r);return{headers:n,baseURL:typeof t.baseURL==`string`?t.baseURL:``,fetchFn:typeof t.fetch==`function`?t.fetch:fetch}}async function Qe(e,t,n){let r=t.provider.hooks?.hooks[`chat.headers`];if(typeof r!=`function`)return;let i={headers:{}};try{await r({sessionID:t.sessionId,agent:`build`,model:{id:t.model,providerID:t.provider.id,api:{id:t.model,npm:t.provider.config.npm}},provider:{source:`custom`,info:t.provider.config,options:t.provider.config.options??{}},message:{id:``,sessionID:t.sessionId,role:`user`,time:{created:Date.now()},agent:`build`,model:{providerID:t.provider.id,modelID:t.model}}},i)}catch(e){n(`[plugin-host] ${t.provider.id}: chat.headers 失败(${e instanceof Error?e.message:String(e)})`);return}for(let[t,n]of Object.entries(i.headers))e.set(t,String(n))}async function $e(e,t){return H(e,t)}async function et(e,t){let{provider:n,credentials:r}=e;if(n.hooks===null)return{kind:`error`,status:501,message:`插件 ${n.source.package} 声明了 provider「${n.id}」但没有实现 auth 钩子(无登录/转发能力)`};if(await r.ensureFresh(),r.current()===void 0)return{kind:`error`,status:401,message:`${n.id} 尚未登录:请在管理台完成登录后重试`};let i=n.hooks.auth.loader;if(i===void 0)return{kind:`error`,status:501,message:`插件 ${n.source.package} 未实现 auth.loader,无法转发`};let a;try{a=await i(r.getAuth,n.config)??{}}catch(e){return{kind:`error`,status:502,message:`插件 loader 失败:${e instanceof Error?e.message:String(e)}`}}if(a.baseURL===void 0||a.baseURL===``)return{kind:`error`,status:502,message:`插件 ${n.source.package} 未给出 baseURL(登录态可能已失效)`};let o=Ze(e,a);await Qe(o.headers,e,t);let s=`${o.baseURL.replace(/\/+$/,``)}/${e.path.replace(/^\/+/,``)}`;try{return{kind:`ok`,response:await o.fetchFn(s,{method:e.method,headers:o.headers,body:e.body.length>0?new Uint8Array(e.body):void 0})}}catch(e){return{kind:`error`,status:502,message:`上游请求失败:${e instanceof Error?e.message:String(e)}`}}}async function tt(e,t,n){let r={};e.headers.forEach((e,t)=>{let n=t.toLowerCase();n!==`content-encoding`&&n!==`content-length`&&n!==`transfer-encoding`&&(r[n]=e)}),t(e.status,r);for await(let t of Ke(e.body))n(t)}function nt(e){return(e.headers.get(`content-type`)??``).includes(`text/event-stream`)}function rt(e,t){try{let t=JSON.parse(e.toString(`utf8`));if(R(t)&&typeof t.model==`string`&&t.model!==``)return t.model}catch{}return t.models[0]??``}function it(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 Error(`invalid api key`)}var at=[`127.0.0.1`,`localhost`,`[::1]`,`::1`],U=`拒绝的 `;function ot(e){let t=e.trim();if(t===``)return``;if(t.startsWith(`[`)){let e=t.indexOf(`]`);return e<0?t.toLowerCase():t.slice(0,e+1).toLowerCase()}let n=t.lastIndexOf(`:`);return(n<0?t:t.slice(0,n)).toLowerCase()}function st(e){return e===`0.0.0.0`||e===`::`||e===``}function ct(e,t){if(st(e.host))return;let n=[...at,e.host.trim().toLowerCase()],r=ot(String(t.headers.host??``));if(!n.includes(r))throw Error(`${U}Host:${r===``?`(空)`:r}(只接受本机回环地址)`);let i=t.headers.origin;if(typeof i==`string`&&i!==``&&i!==`null`){let e=(()=>{try{return new URL(i).hostname.toLowerCase()}catch{return i.toLowerCase()}})();if(!n.includes(e))throw Error(`拒绝的 Origin:${e}`)}}function W(e,t){return{id:e.id,name:e.name,package:e.source.package,version:e.source.version,protocol:e.protocol,basePath:e.protocol===null?null:`/${e.id}/v1`,models:e.models,signedIn:t,hooks:e.capabilities}}function lt(e,t){let{providers:n}=F(e.loadResult());z(t,200,{status:`ok`,platform:process.platform,node:process.version.replace(/^v/,``),mode:`plugin-host (forwarding + package install)`,port:e.options.port,pluginsDir:e.options.pluginsDir,authFile:e.options.authFile,providers:n.length})}function ut(e,t){let n=e.loadResult(),{providers:r,errors:i}=F(n);z(t,200,{status:`ok`,pluginsDir:e.options.pluginsDir,packages:n.plugins.map(e=>({package:e.package,version:e.version,providers:e.providers.length})),providers:r.map(t=>W(t,e.auth.has(t.id))),errors:i})}function G(e,t){return F(e.loadResult()).providers.filter(e=>e.source.package===t)}function dt(e,t){let n=N(e.options.pluginsDir).map(t=>{let n=G(e,t.name);return{package:t.name,version:t.version,description:t.description,license:t.license,repository:t.repository,loaded:n.length>0,providers:n.map(t=>({id:t.id,name:t.name,protocol:t.protocol,models:t.models.length,signedIn:e.auth.has(t.id)}))}});z(t,200,{status:`ok`,pluginsDir:e.options.pluginsDir,count:n.length,packages:n,errors:e.loadResult().errors,canInstall:e.install!==void 0&&e.reload!==void 0})}function ft(e,t,n){let r=P(e.options.pluginsDir,t);if(r===void 0)return B(n,404,L(`未安装:${t}`,`not_found`));let i=G(e,r.name);z(n,200,{status:`ok`,package:r.name,version:r.version,description:r.description,license:r.license,repository:r.repository,dir:r.dir,entry:r.main,readme:e.install===void 0?null:Te(r.dir),loaded:i.length>0,providers:i.map(t=>W(t,e.auth.has(t.id))),errors:e.loadResult().errors.filter(e=>e.includes(r.name))})}async function K(e){if(e.reload===void 0)return[];try{return await e.reload()}catch(t){return e.log(`[plugin-host] 重载失败:${t instanceof Error?t.message:String(t)}`),[`重载失败:${t instanceof Error?t.message:String(t)}`]}}async function pt(e,t,n){if(e.install===void 0||e.reload===void 0)return B(n,501,L(`本进程未启用安装能力(未注入 npm 依赖)`,`not_implemented`));let r;try{r=await H(t,V())}catch(e){return B(n,413,L(e instanceof Error?e.message:String(e),`invalid_request_error`))}let i;try{i=r.length===0?{}:JSON.parse(r.toString(`utf8`))}catch{return B(n,400,L(`body 不是合法 JSON`,`invalid_request_error`))}if(!R(i)||typeof i.package!=`string`||i.package.trim()===``)return B(n,400,L(`缺少 package(npm 包名,可带 @版本)`,`invalid_request_error`));let a=i.package.trim(),o=typeof i.version==`string`&&i.version.trim()!==``?i.version.trim():``,s=o===``?a:`${D(a)??a}@${o}`;if(E(s)===null)return B(n,400,L(`不是合法的 npm 包规格:${s}`,`invalid_request_error`));e.log(`[plugin-host] 安装 ${s}`);let c=await A(e.install,s);if(!c.ok)return e.log(`[plugin-host] ✗ 安装失败:${s}`),B(n,502,L(c.message,`install_error`));let l=await K(e),u=D(s)??s,d=P(e.options.pluginsDir,u);e.log(`[plugin-host] ✓ 已安装 ${u}${d?`@${d.version}`:``}`),z(n,200,{status:`ok`,package:u,version:d?.version??``,loaded:G(e,u).length>0,providers:G(e,u).map(e=>({id:e.id,protocol:e.protocol,models:e.models.length})),npmOutput:c.output.slice(-2e3),reloadErrors:l})}async function mt(e,t,n){if(e.install===void 0||e.reload===void 0)return B(n,501,L(`本进程未启用卸载能力(未注入 npm 依赖)`,`not_implemented`));e.log(`[plugin-host] 卸载 ${t}`);let r=await we(e.install,t);if(!r.ok)return B(n,r.message.startsWith(`未安装`)?404:502,L(r.message,r.message.startsWith(`未安装`)?`not_found`:`install_error`));let i=await K(e);e.log(`[plugin-host] ✓ 已卸载 ${t}`),z(n,200,{status:`ok`,package:t,npmOutput:r.output.slice(-2e3),reloadErrors:i})}function q(e){let t=e.hooks?.hooks.auth?.methods;return Array.isArray(t)?t:[]}function J(e,t){let n=e.signIns?.get(t.id);return{provider:t.id,signedIn:e.auth.has(t.id),methods:q(t).map(e=>({type:e.type,label:e.label??``})),state:n?.state??`idle`,...n===void 0?{}:{url:n.url,instructions:n.instructions,...n.error===void 0?{}:{error:n.error},...n.accountId===void 0?{}:{accountId:n.accountId}}}}function ht(e,t,n){z(n,200,J(e,t))}async function gt(e,t,n,r){let i=q(t);if(i.length===0)return B(r,400,L(`${t.id} 未声明登录方式(auth.methods),无法登录;该插件可能不需要登录`,`invalid_request_error`));let a={};try{let e=await H(n,V());if(e.length>0){let t=JSON.parse(e.toString(`utf-8`));a=R(t)?t:{}}}catch{return B(r,400,L(`请求体不是合法 JSON`,`invalid_request_error`))}let o=e.signIns??=new Map;if(o.get(t.id)?.state===`pending`)return z(r,200,J(e,t));let s=typeof a.method==`string`?a.method:``;if(s===``&&i.length>1)return z(r,200,{...J(e,t),needMethod:!0});let c=s===``?i[0]:i.find(e=>e.label===s);if(c===void 0)return B(r,400,L(`未知登录方式:${s}(可选:${i.map(e=>e.label??`(未命名)`).join(`、`)})`,`invalid_request_error`));let l=c.authorize;if(typeof l!=`function`)return B(r,400,L(`登录方式「${c.label??c.type}」缺少 authorize 实现(插件声明与实现不一致)`,`invalid_request_error`));let u;try{u=await l()}catch(e){return B(r,502,L(`起登录流程失败:${e instanceof Error?e.message:String(e)}`,`upstream_error`))}let d=u.callback;if(typeof d!=`function`)return B(r,400,L(`登录流程没有 callback(插件声明与实现不一致)`,`invalid_request_error`));let f={state:`pending`,url:typeof u.url==`string`?u.url:``,instructions:typeof u.instructions==`string`?u.instructions:``};o.set(t.id,f);let p=s===``?c.label??c.type:s;return e.log(`[plugin-host] ▶ ${t.id} 登录已发起(${p}${f.url===``?`,读取本机凭据`:`,等待浏览器完成`})`),(async()=>{try{let n=await d(),r=R(n)?n:{};if(r.type===`success`){e.auth.save(t.id,{...r,type:`oauth`});let n=typeof r.accountId==`string`?r.accountId:``;f.state=`success`,f.accountId=n,e.log(`[plugin-host] ✓ ${t.id} 登录完成${n===``?``:`(${n})`}`)}else{let n=typeof r.error==`string`&&r.error!==``?r.error:`登录未完成`;f.state=`failed`,f.error=n,e.log(`[plugin-host] ✗ ${t.id} 登录失败:${n}`)}}catch(n){f.state=`failed`,f.error=n instanceof Error?n.message:String(n),e.log(`[plugin-host] ✗ ${t.id} 登录异常:${f.error}`)}})(),z(r,200,J(e,t))}function Y(e,t){let{providers:n}=F(e.loadResult());z(t,200,{service:`ezllm-plugin-host`,endpoints:[`GET /health`,`GET /plugins`,`GET /p/<id>/v1/models`,`POST /p/<id>/v1/{chat/completions|messages|responses}`,`GET /packages`,`GET /packages/<name>`,`POST /packages`,`DELETE /packages/<name>`,`GET /login/<id>`,`POST /login/<id>`],providers:n.map(e=>({id:e.id,apiBase:e.protocol===null?null:`/p/${e.id}/v1`,protocol:e.protocol}))})}function _t(e){let t=/^\/p\/([^/]+)\/v1(?:\/(.*))?$/.exec(e);if(t===null)return null;let n=decodeURIComponent(t[1]??``);return n===``?null:{id:n,rest:(t[2]??``).replace(/\/+$/,``)}}function X(e,t){return F(e.loadResult()).providers.find(e=>e.id===t)}function Z(e,t){if(t.hooks===null)throw Error(`provider「${t.id}」没有 auth 钩子(插件声明与实现不一致)`);return new Ue(t.id,t.hooks.auth,{store:e.auth,providerInfo:t.config,log:e.log})}async function vt(e,t,n){let r=t.models,i=!1,a=t.hooks?.hooks.provider?.models;if(typeof a==`function`&&t.hooks!==null&&e.auth.has(t.id)){let n=Z(e,t);await n.ensureFresh();let o=n.current();if(o!==void 0)try{let e=await a(t.config,{auth:o}),n=Object.keys(e??{});n.length>0&&(r=n,i=!0)}catch(n){e.log(`[plugin-host] ${t.id}: provider.models 失败,回落声明表(${n instanceof Error?n.message:String(n)})`)}}z(n,200,{object:`list`,provider:t.id,fromHook:i,data:r.map(e=>({id:e,object:`model`,owned_by:t.id}))})}async function yt(e,t,n,r,i){if(t.protocol===null)return B(i,501,L(`${t.id} 的协议未知(插件未声明可识别的 npm),不可转发`,`not_implemented`));if(n!==t.path)return B(i,404,L(`${t.id} 只提供 /p/${t.id}/v1/${t.path}(插件声明的协议是 ${t.protocol})`,`not_found`));let a;try{a=await $e(r,V())}catch(e){return B(i,413,L(e instanceof Error?e.message:String(e),`invalid_request_error`))}let o=r.headers[Je],s=typeof o==`string`?o:``,c=rt(a,t),l=Date.now();e.log(`[plugin-host] ▶ ${t.id} ${n} model=${c} bytes=${String(a.length)}${s?` session=${s}`:``}`);let u=await et({provider:t,credentials:Z(e,t),model:c,path:n,method:`POST`,req:r,body:a,sessionId:s},e.log);if(u.kind===`error`)return e.log(`[plugin-host] ✗ ${t.id} ${n} ${String(u.status)} ${u.message}`),B(i,u.status,L(u.message,u.status===401?`auth_error`:`upstream_error`));let d=u.response,f=nt(d);e.log(`[plugin-host] ◀ ${t.id} ${n} status=${String(d.status)} ${f?`stream`:`json`} ${String(Date.now()-l)}ms`),await tt(d,(e,t)=>{i.headersSent||i.writeHead(e,t)},e=>{i.write(e)}),i.end()}async function bt(e,t,n){let r=Ge(t.url??`/`),i=(t.method??`GET`).toUpperCase();try{if(ct(e.options,t),it(e.options,t),i===`GET`&&r===`/health`)return lt(e,n);if(i===`GET`&&r===`/plugins`)return ut(e,n);if(i===`GET`&&r===`/`)return Y(e,n);if(r===`/packages`)return i===`GET`?dt(e,n):i===`POST`?await pt(e,t,n):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`));let a=/^\/packages\/(.+)$/.exec(r);if(a!==null){let t=decodeURIComponent(a[1]??``);return t===``?B(n,404,L(`未找到:${i} ${r}`,`not_found`)):i===`GET`?ft(e,t,n):i===`DELETE`?await mt(e,t,n):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`))}let o=/^\/login\/(.+)$/.exec(r);if(o!==null){let a=decodeURIComponent(o[1]??``),s=a===``?void 0:X(e,a);return s===void 0?B(n,404,L(`未加载 provider:${a}`,`not_found`)):i===`GET`?ht(e,s,n):i===`POST`?await gt(e,s,t,n):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`))}let s=_t(r);if(s!==null){let a=X(e,s.id);return a===void 0?B(n,404,L(`未加载 provider:${s.id}`,`not_found`)):i===`GET`&&s.rest===`models`?await vt(e,a,n):i===`POST`?await yt(e,a,s.rest,t,n):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`))}B(n,404,L(`未找到:${i} ${r}`,`not_found`))}catch(e){let t=e instanceof Error?e.message:String(e),r=t===`invalid api key`?401:t.startsWith(U)?403:500;B(n,r,L(t,r===500?`internal_error`:`invalid_request_error`))}}function xt(e){return ie((t,n)=>{bt(e,t,n).catch(t=>{e.log(`[fatal] handler error: ${t instanceof Error&&t.stack?t.stack:String(t)}`);try{n.headersSent||n.writeHead(500,{"Content-Type":`application/json`}),n.end(JSON.stringify({detail:L(String(t),`internal_error`)}))}catch{}})})}function St(e){return{...e,pluginsDir:e.pluginsDir===``?ue():e.pluginsDir,authFile:e.authFile===``?de():e.authFile}}function Ct(e,t,n){let{providers:r,errors:i}=F(t);m(`==== 插件宿主预检 ====`),m(`平台 : ${process.platform}`),m(`Node : ${process.version}`),m(`插件目录 : ${e.pluginsDir}`),m(`凭据文件 : ${e.authFile}`),m(`已装插件 : ${String(t.plugins.length)} 个(provider ${String(r.length)} 个)`);for(let e of r){let t=e.protocol??`未知协议(不可用)`,r=n(e.id)?`已登录`:`未登录`;m(` - ${e.id}(${e.name}): ${t} | ${String(e.models.length)} 模型 | ${r}`)}for(let e of i)h(` ! ${e}`);r.length===0&&h(" 未加载到任何 provider——用 `npm install <plugin> --prefix` 装进插件目录后重启"),m(`======================`)}async function Q(e=process.argv.slice(2),t=process.env){let{options:n,help:r}=Pe(e,t);if(r)return process.stdout.write(Fe()),null;let i=St(n),a=new me(i.authFile),o={plugins:[],errors:[]},s=()=>o,c={directory:i.pluginsDir,saveAuth:(e,t)=>{a.save(e,t),m(`[plugin-host] 凭据已保存:${e}`)}},l=async()=>{let e=await Ne(i.pluginsDir,c);return o=e,e.errors};try{await l()}catch(e){h(`[plugin-host] 加载插件失败:${e instanceof Error?e.message:String(e)}`)}i.skipCheck||Ct(i,s(),e=>a.has(e));let u=xe(i.nodeDir,x()),d={pluginsDir:i.pluginsDir,npmCli:u,log:e=>m(e)};m(`[plugin-host] ✅ 监听 http://${i.host}:${String(i.port)}`),m(`[plugin-host] GET /health`),m(`[plugin-host] GET /plugins`),m(`[plugin-host] GET /packages · POST /packages · DELETE /packages/<name>`),m(`[plugin-host] GET /login/<id> · POST /login/<id>`),m(`[plugin-host] 安装能力: ${u===null?`npm 未找到(仅可运行已装插件)`:`已启用(${u})`}`),i.apiKey&&m(`[plugin-host] 鉴权已启用(API key 已设置)`),m(`[plugin-host] 按 Ctrl+C 退出。`);let f=xt({options:i,auth:a,loadResult:s,reload:l,install:d,log:e=>m(e)});return await new Promise((e,t)=>{f.once(`error`,t),f.listen(i.port,i.host,()=>{f.off(`error`,t),e()})}),m(`[plugin-host] listening on ${i.host}:${String(i.port)}`),{server:f,options:i,auth:a,loadResult:s,reload:l}}var $=process.argv[1];$!==void 0&&import.meta.url===i(n($)).href&&Q().catch(e=>{ae(`[plugin-host] 启动失败:${e instanceof Error?e.message:String(e)}`),process.exitCode=1});export{Q as main};
5
+ `}var Ge=3e5,Ke=864e5,qe=15e3,Je=3e4,I=new class{renewing=new Map;failed=new Map};function Ye(e){let t=`${String(e?.refresh??``)}\0${String(e?.access??``)}`;return h(`sha256`).update(t).digest(`hex`).slice(0,16)}function Xe(e){let t=e.refreshLead;return typeof t==`number`&&Number.isFinite(t)&&t>=0?Math.min(t,Ke):Ge}function Ze(e,t){if(typeof e.refresh!=`function`||t.type!==`oauth`)return 0;let n=Number(t.expires);return Number.isFinite(n)&&n>0?Math.max(1,n-Xe(e)):0}var Qe=class{#e;#t;#n;constructor(e,t,n){this.#e=e,this.#t=t,this.#n=n}current(){return this.#n.store.get(this.#e)}getAuth=async()=>this.current();async ensureFresh(){let e=this.#n.now?.()??Date.now(),t=this.current();if(t===void 0)return;let n=Ze(this.#t,t);if(!n||e<n)return;let r=this.#n.registry??I,i=`${this.#e}\0${Ye(t)}`,a=r.failed.get(i);if(a!==void 0&&e-a<Je)return;let o=r.renewing.get(i);o===void 0&&(o=this.#r(t,i).finally(()=>r.renewing.delete(i)),r.renewing.set(i,o));let s;await Promise.race([o,new Promise(e=>{s=setTimeout(e,qe)})]),clearTimeout(s)}async#r(e,t){let n=this.#n.registry??I,r=this.#t.refresh;if(r!==void 0)try{let i=await r({...e},this.#n.providerInfo);typeof i==`object`&&i&&this.#n.store.save(this.#e,{...e,...i}),n.failed.delete(t),this.#n.log?.(`[plugin-host] 凭据已续期:${this.#e}`)}catch(e){n.failed.set(t,this.#n.now?.()??Date.now()),this.#n.log?.(`[plugin-host] 凭据续期失败:${this.#e}(${e instanceof Error?e.message:String(e)})`)}}},$e=class extends Error{status;detail;constructor(e,t,n){super(t),this.name=`HttpError`,this.status=e,this.detail=n}};function L(e,t){return{error:{message:e,type:t}}}function R(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function et(e){let t=e.search(/[?#]/);return t>=0?e.slice(0,t):e}function z(e,t,n){if(e.headersSent){try{e.end()}catch{}return}e.writeHead(t,{"Content-Type":`application/json`}),e.end(JSON.stringify(n))}function B(e,t,n){z(e,t,{detail:n})}function V(e=process.env){return Number(e.PLUGIN_HOST_MAX_BODY||33554432)}function H(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 $e(413,`request body too large`,L(`request body too large`,`invalid_request_error`)));return}i.push(n)}),e.on(`end`,()=>{n(Buffer.concat(i))}),e.on(`error`,r)})}async function*tt(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{}}}var nt=new Set([`host`,`connection`,`keep-alive`,`proxy-authenticate`,`proxy-authorization`,`te`,`trailer`,`transfer-encoding`,`upgrade`,`content-length`,`authorization`,`x-api-key`,`x-plugin-session`]),rt=`x-plugin-session`;function it(e,t){return t===void 0||t===``?{}:e===`anthropic`?{"x-api-key":t}:{authorization:`Bearer ${t}`}}function at(e){let t={};for(let[n,r]of Object.entries(e.headers)){let e=n.toLowerCase();nt.has(e)||r!==void 0&&(t[e]=Array.isArray(r)?r.join(`, `):r)}return t}function ot(e,t){let n=new Headers;for(let[r,i]of Object.entries(it(e.provider.protocol,t.apiKey)))n.set(r,i);for(let[e,r]of Object.entries(t.headers??{}))n.set(e,String(r));for(let[t,r]of Object.entries(at(e.req)))n.set(t,r);return{headers:n,baseURL:typeof t.baseURL==`string`?t.baseURL:``,fetchFn:typeof t.fetch==`function`?t.fetch:fetch}}async function st(e,t,n){let r=t.provider.hooks?.hooks[`chat.headers`];if(typeof r!=`function`)return;let i={headers:{}};try{await r({sessionID:t.sessionId,agent:`build`,model:{id:t.model,providerID:t.provider.id,api:{id:t.model,npm:t.provider.config.npm}},provider:{source:`custom`,info:t.provider.config,options:t.provider.config.options??{}},message:{id:``,sessionID:t.sessionId,role:`user`,time:{created:Date.now()},agent:`build`,model:{providerID:t.provider.id,modelID:t.model}}},i)}catch(e){n(`[plugin-host] ${t.provider.id}: chat.headers 失败(${e instanceof Error?e.message:String(e)})`);return}for(let[t,n]of Object.entries(i.headers))e.set(t,String(n))}async function ct(e,t){return H(e,t)}async function lt(e,t){let{provider:n,credentials:r}=e;if(n.hooks===null)return{kind:`error`,status:501,message:`插件 ${n.source.package} 声明了 provider「${n.id}」但没有实现 auth 钩子(无登录/转发能力)`};if(await r.ensureFresh(),r.current()===void 0)return{kind:`error`,status:401,message:`${n.id} 尚未登录:请在管理台完成登录后重试`};let i=n.hooks.auth.loader;if(i===void 0)return{kind:`error`,status:501,message:`插件 ${n.source.package} 未实现 auth.loader,无法转发`};let a;try{a=await i(r.getAuth,n.config)??{}}catch(e){return{kind:`error`,status:502,message:`插件 loader 失败:${e instanceof Error?e.message:String(e)}`}}if(a.baseURL===void 0||a.baseURL===``)return{kind:`error`,status:502,message:`插件 ${n.source.package} 未给出 baseURL(登录态可能已失效)`};let o=ot(e,a);await st(o.headers,e,t);let s=`${o.baseURL.replace(/\/+$/,``)}/${e.path.replace(/^\/+/,``)}`;try{return{kind:`ok`,response:await o.fetchFn(s,{method:e.method,headers:o.headers,body:e.body.length>0?new Uint8Array(e.body):void 0})}}catch(e){return{kind:`error`,status:502,message:`上游请求失败:${e instanceof Error?e.message:String(e)}`}}}async function ut(e,t,n){let r={};e.headers.forEach((e,t)=>{let n=t.toLowerCase();n!==`content-encoding`&&n!==`content-length`&&n!==`transfer-encoding`&&(r[n]=e)}),t(e.status,r);for await(let t of tt(e.body))n(t)}function dt(e){return(e.headers.get(`content-type`)??``).includes(`text/event-stream`)}function ft(e,t){try{let t=JSON.parse(e.toString(`utf8`));if(R(t)&&typeof t.model==`string`&&t.model!==``)return t.model}catch{}return t.models[0]??``}function pt(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 Error(`invalid api key`)}var mt=[`127.0.0.1`,`localhost`,`[::1]`,`::1`],U=`拒绝的 `;function ht(e){let t=e.trim();if(t===``)return``;if(t.startsWith(`[`)){let e=t.indexOf(`]`);return e<0?t.toLowerCase():t.slice(0,e+1).toLowerCase()}let n=t.lastIndexOf(`:`);return(n<0?t:t.slice(0,n)).toLowerCase()}function gt(e){return e===`0.0.0.0`||e===`::`||e===``}function _t(e,t){if(gt(e.host))return;let n=[...mt,e.host.trim().toLowerCase()],r=ht(String(t.headers.host??``));if(!n.includes(r))throw Error(`${U}Host:${r===``?`(空)`:r}(只接受本机回环地址)`);let i=t.headers.origin;if(typeof i==`string`&&i!==``&&i!==`null`){let e=(()=>{try{return new URL(i).hostname.toLowerCase()}catch{return i.toLowerCase()}})();if(!n.includes(e))throw Error(`拒绝的 Origin:${e}`)}}function W(e,t){return{id:e.id,name:e.name,package:e.source.package,version:e.source.version,protocol:e.protocol,basePath:e.protocol===null?null:`/${e.id}/v1`,models:e.models,signedIn:t,hooks:e.capabilities}}function vt(e,t){let{providers:n}=F(e.loadResult());z(t,200,{status:`ok`,platform:process.platform,node:process.version.replace(/^v/,``),mode:`plugin-host (forwarding + package install)`,port:e.options.port,pluginsDir:e.options.pluginsDir,authFile:e.options.authFile,providers:n.length})}function yt(e,t){let n=e.loadResult(),{providers:r,errors:i}=F(n);z(t,200,{status:`ok`,pluginsDir:e.options.pluginsDir,packages:n.plugins.map(e=>({package:e.package,version:e.version,providers:e.providers.length})),providers:r.map(t=>W(t,e.auth.has(t.id))),errors:i})}function G(e,t){return F(e.loadResult()).providers.filter(e=>e.source.package===t)}function bt(e,t){let n=P(e.options.pluginsDir).map(t=>{let n=G(e,t.name);return{package:t.name,version:t.version,description:t.description,license:t.license,repository:t.repository,loaded:n.length>0,providers:n.map(t=>({id:t.id,name:t.name,protocol:t.protocol,models:t.models.length,signedIn:e.auth.has(t.id)}))}});z(t,200,{status:`ok`,pluginsDir:e.options.pluginsDir,count:n.length,packages:n,errors:e.loadResult().errors,canInstall:e.install!==void 0&&e.reload!==void 0})}function xt(e,t,n){let r=ze(e.options.pluginsDir,t);if(r===void 0)return B(n,404,L(`未安装:${t}`,`not_found`));let i=G(e,r.name);z(n,200,{status:`ok`,package:r.name,version:r.version,description:r.description,license:r.license,repository:r.repository,dir:r.dir,entry:r.main,readme:e.install===void 0?null:Ae(r.dir),loaded:i.length>0,providers:i.map(t=>W(t,e.auth.has(t.id))),errors:e.loadResult().errors.filter(e=>e.includes(r.name))})}async function K(e){if(e.reload===void 0)return[];try{return await e.reload()}catch(t){return e.log(`[plugin-host] 重载失败:${t instanceof Error?t.message:String(t)}`),[`重载失败:${t instanceof Error?t.message:String(t)}`]}}function q(e){let t=e.options.mainPort;if(!Number.isFinite(t)||t<=0){e.log(`[plugin-host] 未由 ezllm 托管(MAIN_PORT 未注入),装/卸后的重启需手动执行`);return}let n=`http://127.0.0.1:${String(t)}/api/service/plugins/restart`;fetch(n,{method:`POST`}).then(t=>{t.ok||e.log(`[plugin-host] 自请重启失败:HTTP ${String(t.status)}(插件已装,重启需手动)`)}).catch(t=>{e.log(`[plugin-host] 自请重启失败:${t instanceof Error?t.message:String(t)}(插件已装,重启需手动)`)})}var J=Promise.resolve();function Y(e){let t=J.then(e,e);return J=t.then(()=>void 0,()=>void 0),t}async function St(e,t,n){if(e.install===void 0||e.reload===void 0)return B(n,501,L(`本进程未启用安装能力(未注入 npm 依赖)`,`not_implemented`));let r;try{r=await H(t,V())}catch(e){return B(n,413,L(e instanceof Error?e.message:String(e),`invalid_request_error`))}let i;try{i=r.length===0?{}:JSON.parse(r.toString(`utf8`))}catch{return B(n,400,L(`body 不是合法 JSON`,`invalid_request_error`))}if(!R(i)||typeof i.package!=`string`||i.package.trim()===``)return B(n,400,L(`缺少 package(npm 包名,可带 @版本)`,`invalid_request_error`));let a=i.package.trim(),o=typeof i.version==`string`&&i.version.trim()!==``?i.version.trim():``,s=o===``?a:`${k(a)??a}@${o}`;if(O(s)===null)return B(n,400,L(`不是合法的 npm 包规格:${s}`,`invalid_request_error`));e.log(`[plugin-host] 安装 ${s}`);let c=await Oe(e.install,s);if(!c.ok)return e.log(`[plugin-host] ✗ 安装失败:${s}`),B(n,502,L(c.message,`install_error`));let l=await K(e),u=k(s)??s,d=ze(e.options.pluginsDir,u);e.log(`[plugin-host] ✓ 已安装 ${u}${d?`@${d.version}`:``}`),z(n,200,{status:`ok`,package:u,version:d?.version??``,loaded:G(e,u).length>0,providers:G(e,u).map(e=>({id:e.id,protocol:e.protocol,models:e.models.length})),npmOutput:c.output.slice(-2e3),reloadErrors:l,restartRequested:e.options.mainPort>0}),q(e)}async function Ct(e,t,n){if(e.install===void 0||e.reload===void 0)return B(n,501,L(`本进程未启用卸载能力(未注入 npm 依赖)`,`not_implemented`));e.log(`[plugin-host] 卸载 ${t}`);let r=await ke(e.install,t);if(!r.ok)return B(n,r.message.startsWith(`未安装`)?404:502,L(r.message,r.message.startsWith(`未安装`)?`not_found`:`install_error`));let i=await K(e);e.log(`[plugin-host] ✓ 已卸载 ${t}`),z(n,200,{status:`ok`,package:t,npmOutput:r.output.slice(-2e3),reloadErrors:i,restartRequested:e.options.mainPort>0}),q(e)}var wt=36e5;async function Tt(e,t){let n=e.versionCache??=new Map,r=Date.now(),i=[];for(let t of P(e.options.pluginsDir)){let a=null;if(e.install!==void 0){let i=n.get(t.name);i!==void 0&&r-i.at<wt?a=i.latest:(a=await Te(e.install,t.name),a!==null&&n.set(t.name,{latest:a,at:r}))}i.push({package:t.name,current:t.version,latest:a,updateAvailable:a!==null&&a!==t.version})}z(t,200,{status:`ok`,canCheck:e.install!==void 0,packages:i})}function X(e){let t=e.hooks?.hooks.auth?.methods;return Array.isArray(t)?t:[]}function Z(e,t){let n=e.signIns?.get(t.id);return{provider:t.id,signedIn:e.auth.has(t.id),methods:X(t).map(e=>({type:e.type,label:e.label??``})),state:n?.state??`idle`,...n===void 0?{}:{url:n.url,instructions:n.instructions,...n.error===void 0?{}:{error:n.error},...n.accountId===void 0?{}:{accountId:n.accountId}}}}function Et(e,t,n){z(n,200,Z(e,t))}function Dt(e,t){let n=e.when;if(n!==void 0){let e=typeof n.key==`string`?t[n.key]:void 0;if(e===void 0)return!1;let r=e===n.value;if(n.op===`eq`?!r:r)return!1}return!(typeof e.condition==`function`&&!e.condition(t))}function Ot(e,t){let n=[];for(let r of e.prompts??[])typeof r.key!=`string`||r.key===``||r.key in t||Dt(r,t)&&n.push(r);return n}function kt(e){return e.map(e=>({type:typeof e.type==`string`?e.type:`text`,key:typeof e.key==`string`?e.key:``,message:typeof e.message==`string`?e.message:``,placeholder:typeof e.placeholder==`string`?e.placeholder:``,options:Array.isArray(e.options)?e.options:[]}))}async function At(e,t,n,r,i,a){let o=e.signIns??=new Map,s=typeof n.placeholder==`string`?n.placeholder:``,c=n.label??n.type;if(i===``)return z(a,200,{...Z(e,t),needKey:!0,placeholder:s,label:c,inputs:r});let l=n.authorize;if(typeof l!=`function`){let n=Object.keys(r).length>0?{metadata:r}:{};return e.auth.save(t.id,{type:`api`,key:i,...n}),o.set(t.id,{state:`success`,url:``,instructions:``}),e.log(`[plugin-host] ✓ ${t.id} 登录完成(API key 已保存)`),z(a,200,Z(e,t))}let u;try{let e=await l(r);u=R(e)?e:{}}catch(e){return B(a,502,L(`起登录流程失败:${e instanceof Error?e.message:String(e)}`,`upstream_error`))}if(u.type!==`success`){let n=typeof u.error==`string`&&u.error!==``?u.error:`登录未完成`;return o.set(t.id,{state:`failed`,url:``,instructions:``,error:n}),e.log(`[plugin-host] ✗ ${t.id} 登录失败:${n}`),z(a,200,Z(e,t))}if(`refresh`in u){let n={...u};delete n.type,delete n.provider,e.auth.save(t.id,{...n,type:`oauth`})}else{let n=typeof u.key==`string`&&u.key!==``?u.key:i,a={...r,...R(u.metadata)?u.metadata:{}};e.auth.save(t.id,{type:`api`,key:n,...Object.keys(a).length>0?{metadata:a}:{}})}let d=typeof u.accountId==`string`?u.accountId:``;return o.set(t.id,{state:`success`,url:``,instructions:``,...d===``?{}:{accountId:d}}),e.log(`[plugin-host] ✓ ${t.id} 登录完成${d===``?``:`(${d})`}`),z(a,200,Z(e,t))}async function jt(e,t,n,r){let i=X(t);if(i.length===0)return B(r,400,L(`${t.id} 未声明登录方式(auth.methods),无法登录;该插件可能不需要登录`,`invalid_request_error`));let a={};try{let e=await H(n,V());if(e.length>0){let t=JSON.parse(e.toString(`utf-8`));a=R(t)?t:{}}}catch{return B(r,400,L(`请求体不是合法 JSON`,`invalid_request_error`))}let o=e.signIns??=new Map;if(o.get(t.id)?.state===`pending`)return z(r,200,Z(e,t));let s=typeof a.method==`string`?a.method:``;if(s===``&&i.length>1)return z(r,200,{...Z(e,t),needMethod:!0});let c=s===``?i[0]:i.find(e=>e.label===s);if(c===void 0)return B(r,400,L(`未知登录方式:${s}(可选:${i.map(e=>e.label??`(未命名)`).join(`、`)})`,`invalid_request_error`));let l={};if(R(a.inputs))for(let[e,t]of Object.entries(a.inputs))typeof t==`string`&&(l[e]=t);let u=typeof a.key==`string`?a.key.trim():``,d=Ot(c,l);if(d.length>0)return z(r,200,{...Z(e,t),needInputs:kt(d),inputs:l});if(c.type===`api`)return await At(e,t,c,l,u,r);let f=c.authorize;if(typeof f!=`function`)return B(r,400,L(`登录方式「${c.label??c.type}」缺少 authorize 实现(插件声明与实现不一致)`,`invalid_request_error`));let p;try{p=await f(c.prompts?.length?l:void 0)}catch(e){return B(r,502,L(`起登录流程失败:${e instanceof Error?e.message:String(e)}`,`upstream_error`))}let m=p.callback;if(typeof m!=`function`)return B(r,400,L(`登录流程没有 callback(插件声明与实现不一致)`,`invalid_request_error`));let h={state:`pending`,url:typeof p.url==`string`?p.url:``,instructions:typeof p.instructions==`string`?p.instructions:``};o.set(t.id,h);let g=s===``?c.label??c.type:s;return e.log(`[plugin-host] ▶ ${t.id} 登录已发起(${g}${h.url===``?`,读取本机凭据`:`,等待浏览器完成`})`),(async()=>{try{let n=await m(),r=R(n)?n:{};if(r.type===`success`){e.auth.save(t.id,{...r,type:`oauth`});let n=typeof r.accountId==`string`?r.accountId:``;h.state=`success`,h.accountId=n,e.log(`[plugin-host] ✓ ${t.id} 登录完成${n===``?``:`(${n})`}`)}else{let n=typeof r.error==`string`&&r.error!==``?r.error:`登录未完成`;h.state=`failed`,h.error=n,e.log(`[plugin-host] ✗ ${t.id} 登录失败:${n}`)}}catch(n){h.state=`failed`,h.error=n instanceof Error?n.message:String(n),e.log(`[plugin-host] ✗ ${t.id} 登录异常:${h.error}`)}})(),z(r,200,Z(e,t))}function Mt(e,t){let{providers:n}=F(e.loadResult());z(t,200,{service:`ezllm-plugin-host`,endpoints:[`GET /health`,`GET /plugins`,`GET /p/<id>/v1/models`,`GET /p/<id>/usage`,`POST /p/<id>/v1/{chat/completions|messages|responses}`,`GET /packages`,`GET /packages/updates`,`GET /packages/<name>`,`POST /packages`,`DELETE /packages/<name>`,`GET /login/<id>`,`POST /login/<id>`],providers:n.map(e=>({id:e.id,apiBase:e.protocol===null?null:`/p/${e.id}/v1`,protocol:e.protocol}))})}function Nt(e){let t=/^\/p\/([^/]+)\/v1(?:\/(.*))?$/.exec(e);if(t===null)return null;let n=decodeURIComponent(t[1]??``);return n===``?null:{id:n,rest:(t[2]??``).replace(/\/+$/,``)}}function Q(e,t){return F(e.loadResult()).providers.find(e=>e.id===t)}function $(e,t){if(t.hooks===null)throw Error(`provider「${t.id}」没有 auth 钩子(插件声明与实现不一致)`);return new Qe(t.id,t.hooks.auth,{store:e.auth,providerInfo:t.config,log:e.log})}async function Pt(e,t,n){let r=t.models,i=!1,a=t.hooks?.hooks.provider?.models;if(typeof a==`function`&&t.hooks!==null&&e.auth.has(t.id)){let n=$(e,t);await n.ensureFresh();let o=n.current();if(o!==void 0)try{let e=await a(t.config,{auth:o}),n=Object.keys(e??{});n.length>0&&(r=n,i=!0)}catch(n){e.log(`[plugin-host] ${t.id}: provider.models 失败,回落声明表(${n instanceof Error?n.message:String(n)})`)}}z(n,200,{object:`list`,provider:t.id,fromHook:i,data:r.map(e=>({id:e,object:`model`,owned_by:t.id}))})}async function Ft(e,t,n){let r=t.hooks?.auth.usage;if(t.hooks===null||t.capabilities.usage===!1||typeof r!=`function`)return B(n,400,L(`该插件未实现额度查询`,`invalid_request_error`));let i=$(e,t);await i.ensureFresh();let a;try{a=await r(i.getAuth,t.config)}catch(r){let i=r instanceof Error?r.message:String(r);return e.log(`[plugin-host] ✗ ${t.id} 额度查询失败:${i}`),B(n,502,L(`额度查询失败:${i}`,`upstream_error`))}z(n,200,a)}async function It(e,t,n,r,i){if(t.protocol===null)return B(i,501,L(`${t.id} 的协议未知(插件未声明可识别的 npm),不可转发`,`not_implemented`));if(n!==t.path)return B(i,404,L(`${t.id} 只提供 /p/${t.id}/v1/${t.path}(插件声明的协议是 ${t.protocol})`,`not_found`));let a;try{a=await ct(r,V())}catch(e){return B(i,413,L(e instanceof Error?e.message:String(e),`invalid_request_error`))}let o=r.headers[rt],s=typeof o==`string`?o:``,c=ft(a,t),l=Date.now();e.log(`[plugin-host] ▶ ${t.id} ${n} model=${c} bytes=${String(a.length)}${s?` session=${s}`:``}`);let u=await lt({provider:t,credentials:$(e,t),model:c,path:n,method:`POST`,req:r,body:a,sessionId:s},e.log);if(u.kind===`error`)return e.log(`[plugin-host] ✗ ${t.id} ${n} ${String(u.status)} ${u.message}`),B(i,u.status,L(u.message,u.status===401?`auth_error`:`upstream_error`));let d=u.response,f=dt(d);e.log(`[plugin-host] ◀ ${t.id} ${n} status=${String(d.status)} ${f?`stream`:`json`} ${String(Date.now()-l)}ms`),await ut(d,(e,t)=>{i.headersSent||i.writeHead(e,t)},e=>{i.write(e)}),i.end()}async function Lt(e,t,n){let r=et(t.url??`/`),i=(t.method??`GET`).toUpperCase();try{if(_t(e.options,t),pt(e.options,t),i===`GET`&&r===`/health`)return vt(e,n);if(i===`GET`&&r===`/plugins`)return yt(e,n);if(i===`GET`&&r===`/`)return Mt(e,n);if(r===`/packages`)return i===`GET`?bt(e,n):i===`POST`?await Y(()=>St(e,t,n)):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`));if(r===`/packages/updates`)return i===`GET`?await Tt(e,n):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`));let a=/^\/packages\/(.+)$/.exec(r);if(a!==null){let t=decodeURIComponent(a[1]??``);return t===``?B(n,404,L(`未找到:${i} ${r}`,`not_found`)):i===`GET`?xt(e,t,n):i===`DELETE`?await Y(()=>Ct(e,t,n)):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`))}let o=/^\/login\/(.+)$/.exec(r);if(o!==null){let a=decodeURIComponent(o[1]??``),s=a===``?void 0:Q(e,a);return s===void 0?B(n,404,L(`未加载 provider:${a}`,`not_found`)):i===`GET`?Et(e,s,n):i===`POST`?await jt(e,s,t,n):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`))}let s=/^\/p\/([^/]+)\/usage$/.exec(r);if(s!==null){let t=decodeURIComponent(s[1]??``),a=Q(e,t);return a===void 0?B(n,404,L(`未加载 provider:${t}`,`not_found`)):i===`GET`?await Ft(e,a,n):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`))}let c=Nt(r);if(c!==null){let a=Q(e,c.id);return a===void 0?B(n,404,L(`未加载 provider:${c.id}`,`not_found`)):i===`GET`&&c.rest===`models`?await Pt(e,a,n):i===`POST`?await It(e,a,c.rest,t,n):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`))}B(n,404,L(`未找到:${i} ${r}`,`not_found`))}catch(e){let t=e instanceof Error?e.message:String(e),r=t===`invalid api key`?401:t.startsWith(U)?403:500;B(n,r,L(t,r===500?`internal_error`:`invalid_request_error`))}}function Rt(e){return te((t,n)=>{Lt(e,t,n).catch(t=>{e.log(`[fatal] handler error: ${t instanceof Error&&t.stack?t.stack:String(t)}`);try{n.headersSent||n.writeHead(500,{"Content-Type":`application/json`}),n.end(JSON.stringify({detail:L(String(t),`internal_error`)}))}catch{}})})}function zt(e){return{...e,pluginsDir:e.pluginsDir===``?se():e.pluginsDir,authFile:e.authFile===``?ce():e.authFile}}function Bt(e,t,n){let{providers:r,errors:i}=F(t);_(`==== 插件宿主预检 ====`),_(`平台 : ${process.platform}`),_(`Node : ${process.version}`),_(`插件目录 : ${e.pluginsDir}`),_(`凭据文件 : ${e.authFile}`),_(`已装插件 : ${String(t.plugins.length)} 个(provider ${String(r.length)} 个)`);for(let e of r){let t=e.protocol??`未知协议(不可用)`,r=n(e.id)?`已登录`:`未登录`;_(` - ${e.id}(${e.name}): ${t} | ${String(e.models.length)} 模型 | ${r}`)}for(let e of i)v(` ! ${e}`);r.length===0&&v(" 未加载到任何 provider——用 `npm install <plugin> --prefix` 装进插件目录后重启"),_(`======================`)}async function Vt(e=process.argv.slice(2),t=process.env){let{options:n,help:r}=Ue(e,t);if(r)return process.stdout.write(We()),null;let i=zt(n),a=new ge(i.authFile),o={plugins:[],errors:[]},s=()=>o,c={directory:i.pluginsDir,saveAuth:(e,t)=>{a.save(e,t),_(`[plugin-host] 凭据已保存:${e}`)}},l=async()=>{let e=await He(i.pluginsDir,c);return o=e,e.errors};try{await l()}catch(e){v(`[plugin-host] 加载插件失败:${e instanceof Error?e.message:String(e)}`)}i.skipCheck||Bt(i,s(),e=>a.has(e));let u=Ce(i.nodeDir,w()),d={pluginsDir:i.pluginsDir,npmCli:u,log:e=>_(e)};_(`[plugin-host] ✅ 监听 http://${i.host}:${String(i.port)}`),_(`[plugin-host] GET /health`),_(`[plugin-host] GET /plugins`),_(`[plugin-host] GET /packages · POST /packages · DELETE /packages/<name>`),_(`[plugin-host] GET /login/<id> · POST /login/<id>`),_(`[plugin-host] 安装能力: ${u===null?`npm 未找到(仅可运行已装插件)`:`已启用(${u})`}`),i.apiKey&&_(`[plugin-host] 鉴权已启用(API key 已设置)`),_(`[plugin-host] 按 Ctrl+C 退出。`);let f=Rt({options:i,auth:a,loadResult:s,reload:l,install:d,log:e=>_(e)});return await new Promise((e,t)=>{f.once(`error`,t),f.listen(i.port,i.host,()=>{f.off(`error`,t),e()})}),_(`[plugin-host] listening on ${i.host}:${String(i.port)}`),{server:f,options:i,auth:a,loadResult:s,reload:l}}var Ht=process.argv[1];Ht!==void 0&&import.meta.url===i(n(Ht)).href&&Vt().catch(e=>{ne(`[plugin-host] 启动失败:${e instanceof Error?e.message:String(e)}`),process.exitCode=1});export{Vt as main};