ezllm 0.0.49 → 0.0.51
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/dist/cli.js +22 -23
- package/dist/daemon.js +14 -15
- package/dist/plugin-host.js +3 -3
- package/dist/server.js +59 -58
- package/dist/web-ui/assets/{DashboardView-CNsnnzad.css → DashboardView-CrDoH6I9.css} +1 -1
- package/dist/web-ui/assets/{DashboardView--YRv0pdB.js → DashboardView-bM9PqDiJ.js} +2 -2
- package/dist/web-ui/assets/LayoutView-C_LbQR7r.css +1 -0
- package/dist/web-ui/assets/LayoutView-CnpMPvtX.js +1 -0
- package/dist/web-ui/assets/{LoginView-DyiYIltJ.js → LoginView-CAMVAyqQ.js} +1 -1
- package/dist/web-ui/assets/{LogsView-BlO9eUXV.js → LogsView-HFN9O54e.js} +1 -1
- package/dist/web-ui/assets/{ManagedServicesView-DalOm5fB.js → ManagedServicesView-BUUPqj4x.js} +1 -1
- package/dist/web-ui/assets/{PluginsView-CMjFrpCT.js → PluginsView-a2QVnO1e.js} +1 -1
- package/dist/web-ui/assets/ProvidersView-B9lw6njY.css +1 -0
- package/dist/web-ui/assets/{ProvidersView-PlwErhJU.js → ProvidersView-BKyDkKE1.js} +9 -9
- package/dist/web-ui/assets/{SettingsView-Dl-dHm8U.js → SettingsView-CNcoc3UN.js} +1 -1
- package/dist/web-ui/assets/{SetupView-BblW5D0m.js → SetupView-CWmlLn1Y.js} +1 -1
- package/dist/web-ui/assets/{config-DRb_7Qvh.js → config-xKRJvzmN.js} +1 -1
- package/dist/web-ui/assets/{index-BviWrBv1.js → index-BPFg2Vwy.js} +1 -1
- package/dist/web-ui/assets/{message-BgUNwRuF.js → message-BCzerIAw.js} +1 -1
- package/dist/web-ui/assets/{pluginProviders-CMNUPYaO.js → pluginProviders-D3aFIsze.js} +1 -1
- package/dist/web-ui/assets/{providers-fXKPM_nK.js → providers-A7mAhs_N.js} +1 -1
- package/dist/web-ui/assets/{router-Cpy-sU_q.js → router-B4JKSClW.js} +2 -2
- package/dist/web-ui/assets/{useCopyToast-DreqvPDI.js → useCopyToast-DtJhxffK.js} +1 -1
- package/dist/web-ui/assets/useManagerApi-Dfa7oUgw.js +1 -0
- package/dist/web-ui/assets/{useQuickRangeFilter-D2Epk9Kj.js → useQuickRangeFilter-BrncYqWD.js} +1 -1
- package/dist/web-ui/assets/useToast-C4AY3_4n.js +1 -0
- package/dist/web-ui/index.html +3 -3
- package/package.json +1 -1
- package/dist/web-ui/assets/LayoutView-3OpEgcEH.js +0 -1
- package/dist/web-ui/assets/LayoutView-cQeiOjLU.css +0 -1
- package/dist/web-ui/assets/ProvidersView-DZs-NofB.css +0 -1
- package/dist/web-ui/assets/useManagerApi-DBsdJZgc.js +0 -1
- package/dist/web-ui/assets/useToast-BpZaUS2s.js +0 -1
package/dist/plugin-host.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,realpathSync as i,renameSync as a,rmSync as o,statSync as s,writeFileSync as c}from"node:fs";import{basename as l,dirname as u,join as d,resolve as f}from"node:path";import{fileURLToPath as p,pathToFileURL as m}from"node:url";import{createHash as ee,randomBytes as h}from"node:crypto";import{AsyncLocalStorage as te}from"node:async_hooks";import{homedir as ne}from"node:os";import{cwd as re}from"node:process";import{spawn as g}from"node:child_process";import{createServer as ie}from"node:http";var ae=20,oe=2e3,se=1e4,_=3e4;function ce(e){Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,e)}function le(e){return e instanceof Error?e.message:String(e)}var v=class extends Error{constructor(){super(`凭据文件内容异常(非合法 JSON 或形状不符),已拒绝写入以免覆盖现有凭据;请修复或备份后删除该文件`),this.name=`AuthFileCorruptError`}};function ue(e){let t;try{t=n(e,`utf-8`)}catch{throw new v}let r;try{r=JSON.parse(t)}catch{throw new v}if(typeof r!=`object`||!r||Array.isArray(r))throw new v;let i={};for(let[e,t]of Object.entries(r)){if(typeof t!=`object`||!t||Array.isArray(t))throw new v;i[e]=t}return i}function de(e){try{let t=JSON.parse(n(e,`utf-8`));if(typeof t!=`object`||!t||Array.isArray(t))return{};let r={};for(let[e,n]of Object.entries(t))typeof n==`object`&&n&&!Array.isArray(n)&&(r[e]=n);return r}catch{return{}}}function fe(e,n){let r=`${e}.${`${process.pid}.${h(6).toString(`hex`)}`}.tmp`;try{t(u(e),{recursive:!0}),c(r,`${JSON.stringify(n,null,2)}\n`,{encoding:`utf-8`,mode:384}),a(r,e)}finally{try{o(r,{force:!0})}catch{}}}function y(t,n){return e(t)?t:n.find(t=>e(t))??null}var pe=class{#e;#t;#n;#r=null;#i=null;#a=-1;#o=!1;#s=0;constructor(e){this.#e=e.file,this.#t=e.legacyFiles??[],this.#n=e.log,this.#l()}get path(){return this.#e}read(){this.#o&&Date.now()>=this.#s&&this.#l();let e=y(this.#e,this.#t);if(e===null)return{};try{let t=s(e).mtimeMs;if(this.#r!==null&&this.#i===e&&this.#a===t)return{...this.#r};let n=de(e);return this.#r=n,this.#i=e,this.#a=t,{...n}}catch{return{}}}invalidate(){this.#r=null,this.#i=null,this.#a=-1}mutate(e){return this.#d(()=>{let t=e(this.#c());return t===null?null:(fe(this.#e,t),this.#u(),this.#r=t,this.#i=this.#e,this.#a=s(this.#e).mtimeMs,{...t})})}#c(){let e=y(this.#e,this.#t);return e===null?{}:ue(e)}#l(){let t=this.#t.find(t=>e(t));if(t===void 0){this.#o=!1;return}try{let n=this.#d(()=>e(t)?e(this.#e)?(o(t,{force:!0}),this.#n?.(`[auth] 已清理旧凭据文件:${t}(新文件已存在)`),!0):(a(t,this.#e),this.#n?.(`[auth] 凭据文件已迁移:${t} → ${this.#e}`),!0):!0);this.#o=!n,this.#s=n?0:Date.now()+_}catch(e){this.#o=!0,this.#s=Date.now()+_,this.#n?.(`[auth] 旧凭据文件处理失败(继续只读旧文件,${String(_/1e3)}s 后重试):${le(e)}`)}}#u(){for(let t of this.#t)if(e(t))try{o(t,{force:!0}),this.#n?.(`[auth] 已清理旧凭据文件:${t}`)}catch{}this.#o=!1}#d(e){let n=`${this.#e}.lock`,r=`${process.pid}.${h(8).toString(`hex`)}`;t(u(n),{recursive:!0});let i=Date.now()+oe;for(;;)try{t(n),c(d(n,`owner`),r,`utf-8`);break}catch(e){if(e.code!==`EEXIST`)throw e;try{if(Date.now()-s(n).mtimeMs>se&&this.#m(n))continue}catch{}if(Date.now()>i)throw Error(`获取凭据文件锁超时:${l(n)}`);ce(ae)}try{return e()}finally{this.#p(n,r)}}#f(e){try{return n(d(e,`owner`),`utf-8`)}catch{return null}}#p(e,t){try{if(this.#f(e)!==t)return;o(e,{recursive:!0,force:!0})}catch{}}#m(e){let t=this.#f(e),n=`${e}.stale.${h(4).toString(`hex`)}`;try{a(e,n)}catch{return!1}let r=this.#f(n);if(t!==null&&r!==null&&t!==r){try{a(n,e)}catch{}return!1}try{o(n,{recursive:!0,force:!0})}catch{}return!0}},b=new te;function x(e,t,n){return b.run({providerId:e,generation:t},n)}var me=3e5,he=864e5,ge=15e3,_e=3e4,S=new class{renewing=new Map;failed=new Map;clear(e){let t=`${e}\0`;for(let e of[...this.renewing.keys()])e.startsWith(t)&&this.renewing.delete(e);for(let e of[...this.failed.keys()])e.startsWith(t)&&this.failed.delete(e)}};function C(e){let t=`${String(e?.refresh??``)}\0${String(e?.access??``)}`;return ee(`sha256`).update(t).digest(`hex`).slice(0,16)}function ve(e){return typeof e==`number`&&Number.isFinite(e)&&e>=0?Math.min(e,he):me}function ye(e,t){if(t.type!==`oauth`)return 0;let n=Number(t.expires);return Number.isFinite(n)&&n>0?Math.max(1,n-e):0}var be=class{#e;constructor(e){this.#e=e}clear(e){(this.#e.registry??S).clear(e)}async ensureFresh(e){let t=this.#e.get(e),n=this.#e.refreshOf(e);if(t===void 0||n===void 0)return;let r=this.#n(),i=ye(ve(this.#e.leadOf(e)),t);if(!i||r<i)return;let a=this.#r(),o=`${e}\0${C(t)}`,s=a.failed.get(o);if(s!==void 0){if(r-s<_e)return;a.failed.delete(o)}let c=a.renewing.get(o);c===void 0&&(c=this.#t(e,t,n,o).finally(()=>a.renewing.delete(o)),a.renewing.set(o,c));let l;await Promise.race([c,new Promise(e=>{l=setTimeout(e,ge)})]),clearTimeout(l)}async#t(e,t,n,r){let i=this.#r(),a=this.#e.generation(e);try{let o=await x(e,a,()=>n({...t}));if(typeof o!=`object`||!o||Array.isArray(o)){i.failed.delete(r);return}let s=this.#e.get(e);if(s===void 0||this.#e.generation(e)!==a){i.failed.delete(r),this.#e.log?.(`[auth] 已丢弃过期的续期结果:${e}`);return}if(C(s)!==C(t)){i.failed.delete(r),this.#e.log?.(`[auth] 凭据已由供应商续期:${e}`);return}if(!this.#e.save(e,{...t,...o},{generation:a})){i.failed.delete(r),this.#e.log?.(`[auth] 已丢弃过期的续期结果:${e}`);return}i.failed.delete(r),this.#e.log?.(`[auth] 凭据已续期:${e}`)}catch(t){i.failed.set(r,this.#n()),this.#e.log?.(`[auth] 凭据续期失败:${e}(${t instanceof Error?t.message:String(t)})`)}}#n(){return this.#e.now?.()??Date.now()}#r(){return this.#e.registry??S}},w=class extends Error{status;constructor(e,t){super(t),this.name=`AuthError`,this.status=e}},T=class extends Error{constructor(e){super(e),this.name=`AuthFailure`}},xe=2e3,Se=6e5;function Ce(e){return e instanceof Error?e.message:String(e)}function we(e){return`auth`in e}function Te(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 Ee(e,t){let n=[];for(let r of e.prompts??[])typeof r.key!=`string`||r.key===``||r.key in t||Te(r,t)&&n.push(r);return n}function De(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:[]}))}function Oe(e){for(let t of[`accountId`,`email`,`name`,`account`,`uid`]){let n=e[t];if(typeof n==`string`&&n.trim()!==``)return n}return null}function ke(e,t){return t==null||t===``||typeof e.accountId==`string`&&e.accountId!==``?e:{...e,accountId:t}}var Ae=class{#e;#t;#n;#r;#i=new Map;#a=new Set;#o=new Map;#s=new Map;constructor(e){this.#e=new pe({file:e.file,legacyFiles:e.legacyFiles??[],log:e.log}),this.#t=e.now??Date.now,this.#n=e.log,this.#r=new be({get:e=>this.get(e),generation:e=>this.generation(e),save:(e,t,n)=>this.save(e,t,n),refreshOf:e=>this.#c(e)?.refresh,leadOf:e=>this.#c(e)?.refreshLead,now:e.now,log:e.log,registry:e.registry});for(let t of e.adapters??[])this.register(t)}register(e){let t=this.#s.get(e.id)??[];return this.#s.set(e.id,[...t,e].sort((e,t)=>(t.priority??10)-(e.priority??10))),()=>{let t=this.#s.get(e.id);if(t===void 0)return;let n=t.filter(t=>t!==e);if(n.length===0?this.#s.delete(e.id):this.#s.set(e.id,n),n.length===0){let t=this.#o.get(e.id);t!==void 0&&this.#u(t),this.#o.delete(e.id),this.#r.clear(e.id)}e.dispose?.()}}#c(e){return this.#s.get(e)?.[0]}hasAdapter(e){return this.#c(e)!==void 0}methodsOf(e){return(this.#c(e)?.methods()??[]).map(e=>({type:e.type,label:e.label??``}))}reload(){this.#e.invalidate()}generation(e){return this.#i.get(e)??0}locked(e){return this.#a.has(e)}lock(e){this.#a.add(e)}beginLogin(e){let t=this.generation(e)+1;return this.#i.set(e,t),this.#a.delete(e),t}get(e){let t=this.#e.read()[e];return t===void 0?void 0:{...t}}has(e){return e in this.#e.read()}providers(){return Object.keys(this.#e.read())}save(e,t,n){return n?.generation!==void 0&&n.generation!==this.generation(e)||this.#a.has(e)?!1:(this.#e.mutate(n=>({...n,[e]:{...t}})),!0)}saveFromPlugin(e,t){let n=b.getStore(),r=n?.providerId===e?{generation:n.generation}:void 0;return this.save(e,t,r)}remove(e){let t=this.generation(e)+1;return this.#i.set(e,t),this.#a.add(e),this.#e.mutate(t=>{if(!(e in t))return null;let n={...t};return delete n[e],n}),t}status(e){let t=this.get(e),n=t!==void 0&&typeof t.expires==`number`&&Number.isFinite(t.expires)?t.expires:null;return{provider:e,signedIn:t!==void 0,type:t!==void 0&&typeof t.type==`string`?t.type:null,account:t===void 0?null:Oe(t),expires:n,expiresInMs:n===null?null:Math.max(0,n-this.#t())}}list(){return[...new Set([...this.#s.keys(),...this.providers()])].sort().map(e=>this.status(e))}view(e){let t=this.#o.get(e);return{provider:e,signedIn:this.has(e),methods:this.methodsOf(e),state:t?.state??`idle`,...t===void 0?{}:{url:t.url,instructions:t.instructions,...t.error===void 0?{}:{error:t.error},...t.accountId===void 0?{}:{accountId:t.accountId},...t.expiresAt===null?{}:{expiresAt:t.expiresAt}}}}async signIn(e,t={}){let n=this.#c(e),r=n?.methods()??[];if(n===void 0||r.length===0)throw new w(400,`${e} 未声明登录方式(auth.methods),无法登录;该供应商可能不需要登录`);if(t.restart!==!0&&this.#o.get(e)?.state===`pending`)return this.view(e);let i=t.method??``,a=t.inputs??{};if(i===``&&r.length>1)return{...this.view(e),needMethod:!0};let o=i===``?r[0]:r.find(e=>(e.label??e.type)===i);if(o===void 0)throw new w(400,`未知登录方式:${i}(可选:${r.map(e=>e.label??`(未命名)`).join(`、`)})`);let s=Ee(o,a);if(s.length>0)return{...this.view(e),needInputs:De(s),inputs:a};let c=(t.key??``).trim();if(o.type===`api`&&c===``)return{...this.view(e),needKey:!0,placeholder:o.placeholder??``,label:o.label??o.type,inputs:a};let l=this.beginLogin(e),u;try{let t={key:c,inputs:a};u=await x(e,l,()=>n.authorize(o,t))}catch(t){if(this.#p(e,l),t instanceof T)return this.#o.set(e,{state:`failed`,url:``,instructions:``,expiresAt:null,attempt:null,error:t.message,generation:l}),this.#n?.(`[auth] ✗ ${e} 登录失败:${t.message}`),this.view(e);throw t instanceof w?t:new w(502,`起登录流程失败:${Ce(t)}`)}if(we(u))return this.save(e,ke(u.auth,u.account),{generation:l})?(this.#o.set(e,{state:`success`,url:``,instructions:``,expiresAt:null,attempt:null,generation:l,...u.account===void 0||u.account===null||u.account===``?{}:{accountId:u.account}}),this.#n?.(`[auth] ✓ ${e} 登录完成${u.account?`(${u.account})`:``}`),this.view(e)):(this.#o.set(e,{state:`failed`,url:``,instructions:``,expiresAt:null,attempt:null,error:`登录结果已过期(已登出或被新的登录流程取代)`,generation:l}),this.view(e));let d={state:`pending`,url:u.url,instructions:u.instructions,expiresAt:u.expiresAt,attempt:u,generation:l};return this.#o.set(e,d),this.#l(e,d),this.#n?.(`[auth] ▶ ${e} 登录已发起(${o.label??o.type}${u.url===``?``:`,等待浏览器完成`})`),this.view(e)}async poll(e){let t=this.#o.get(e);return t===void 0||t.state!==`pending`||t.attempt===null?this.view(e):this.#d(e,t)}#l(e,t){t.deadline=t.expiresAt??this.#t()+Se;let n=()=>{if(this.#o.get(e)!==t||t.state!==`pending`)return;let r=setTimeout(()=>{this.#o.get(e)===t&&t.state===`pending`&&this.#d(e,t).catch(()=>void 0).finally(n)},xe);r.unref?.(),t.timer=r};n()}#u(e){e.timer!==null&&e.timer!==void 0&&clearTimeout(e.timer),e.timer=null}#d(e,t){if(t.polling!==null&&t.polling!==void 0)return t.polling;let n=this.#f(e,t).finally(()=>{t.polling===n&&(t.polling=null)});return t.polling=n,n}async#f(e,t){let n=t.deadline??t.expiresAt;if(n!=null&&this.#t()>n)return this.#u(t),this.#o.delete(e),this.#n?.(`[auth] ✗ ${e} 授权已超时`),{...this.view(e),state:`failed`,error:`授权已超时,请重新发起`};let r=t.attempt;if(r===null)return this.view(e);let i;try{i=await r.poll()}catch(n){let r=Ce(n);return this.#o.get(e)===t?(this.#p(e,t.generation),this.#u(t),this.#o.delete(e),this.#n?.(`[auth] ✗ ${e} 登录失败:${r}`),{...this.view(e),state:`failed`,error:r}):this.view(e)}return this.#o.get(e)!==t||i===null?this.view(e):this.save(e,ke(i.auth,i.account),{generation:t.generation})?(t.state=`success`,t.attempt=null,t.error=void 0,this.#u(t),i.account!==void 0&&i.account!==null&&i.account!==``&&(t.accountId=i.account),this.#n?.(`[auth] ✓ ${e} 登录完成${t.accountId?`(${t.accountId})`:``}`),this.view(e)):(t.state=`failed`,t.error=`登录结果已过期(已登出或被新的登录流程取代)`,t.attempt=null,this.#u(t),this.view(e))}signOut(e){let t=this.has(e),n=this.#o.get(e);return n!==void 0&&this.#u(n),this.remove(e),this.#r.clear(e),this.#o.delete(e),this.#c(e)?.dispose?.(),t&&this.#n?.(`[auth] ⏏ ${e} 已退出登录`),this.view(e)}async ensureFresh(e){await this.#r.ensureFresh(e)}#p(e,t){this.generation(e)===t&&this.get(e)===void 0&&this.#a.add(e)}};function E(e,...t){console.log(e,...t)}function D(e,...t){console.warn(e,...t)}function je(e,...t){console.error(e,...t)}function O(e){if(e===void 0)return;let t=e.trim();return t===``?void 0:t}function k(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 O(i.slice(n.length));if(i===t){let t=r[e+1]??``;return t.startsWith(`-`)?void 0:O(t)}}}function A(e,t){return k(e)??O(process.env[t])}var Me=65535;function Ne(e){let t=O(e);if(t===void 0)return;let n=Number(t);return Number.isInteger(n)&&n>0&&n<=Me?n:void 0}var Pe=(()=>{let e=A(`app-dir`,`ELLM_APP_DIR`),t=e===void 0?void 0:f(re(),e),n=k(`port`),r=Ne(n??O(process.env.ELLM_PORT));n!==void 0&&r===void 0&&D(`--port=${n} 无效(需为 1-65535 的整数),已忽略:本次不生效、也不会写入配置文件。`);let i=A(`node-mirror`,`ELLM_NODE_MIRROR`),a=A(`node-bin`,`ELLM_NODE_BIN`);return t!==void 0&&(process.env.ELLM_APP_DIR=t),i!==void 0&&(process.env.ELLM_NODE_MIRROR=i),a!==void 0&&(process.env.ELLM_NODE_BIN=a),{appDir:t,port:r,nodeMirror:i,nodeBin:a}})(),j=u(p(import.meta.url)),M;function Fe(){return d(ne(),`.ezllm`)}function N(){return M===void 0&&(M=Pe.appDir??Fe()),M}function Ie(){return f(N(),`plugins`)}function Le(){return f(N(),`provider-auth.json`)}function Re(){return f(N(),`plugin-auth.json`)}function ze(e){try{let t=n(d(e,`package.json`),`utf-8`).replace(/^\uFEFF/,``);return JSON.parse(t)}catch{return null}}function P(e,t){let n=j;for(let r=0;r<t;r++){if(ze(n)?.name===e)return n;n=u(n)}return null}P(`ezllm`,5)??f(j,`..`),P(`ellm`,6);var Be=`https://registry.npmmirror.com/`,Ve=`https://registry.npmjs.org/`;function He(e=`mirror`){return e===`official`?[Ve,Be]:[Be,Ve]}async function Ue(e,t=He()){let n=[];for(let r of t)try{return{value:await e(r),registry:r}}catch(e){n.push({registry:r,message:e instanceof Error?e.message:String(e),error:e})}if(n.length===0)throw Error(`没有任何可用的 npm registry`);let r=[...new Set(n.map(e=>e.message))],i=r.length===1?`所有源返回相同错误(已依次尝试:${t.join(`、`)}):\n${r[0]??``}`:`所有源都失败:\n${n.map(e=>`- ${e.registry}:${e.message}`).join(`
|
|
3
|
-
`)}`,a=Error(i);throw a.cause=n[n.length-1]?.error,a}function We(e,t=`SIGTERM`){let n=e.pid;if(process.platform!==`win32`||n===void 0){e.kill(t);return}g(`taskkill`,[`/PID`,String(n),`/T`,`/F`],{stdio:`ignore`,windowsHide:!0}).on(`error`,()=>{e.kill(t)}).unref()}var Ge=/^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(@[a-zA-Z0-9.^~<>=*|xX -]+)?$/,Ke=3e5,qe=1e4,Je=5e3,Ye=65536,Xe=`npm_config_`;function F(e){let t=e.trim();if(t===``||!Ge.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 Ze(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=F(n);return r===null?null:{kind:`npm`,name:r.name,version:r.version}}function Qe(t){return e(t)?s(t).isDirectory()?e(d(t,`package.json`))?null:`目录里没有 package.json:${t}`:`不是目录:${t}`:`目录不存在:${t}`}function I(e){return F(e)?.name??null}function $e(e,t){return d(e,`node_modules`,...t.split(`/`))}function et(t,n){let i=t=>t.endsWith(`npm-cli.js`)&&e(t),a=e=>{for(let t of[[`node_modules`,`npm`,`bin`,`npm-cli.js`],[`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`],[`bin`,`npm-cli.js`]]){let n=d(e,...t);if(i(n))return n}return null};if(t!==``){let e=a(t);if(e!==null)return e;if(i(t))return t;let n=d(t,`npm`,`bin`,`npm-cli.js`);if(i(n))return n}let o=d(u(process.execPath),`node_modules`,`npm`,`bin`,`npm-cli.js`);if(i(o))return o;let s=d(u(process.execPath),`..`,`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`);if(i(s))return s;if(n!==null){let e=d(n,`node`),t=a(e);if(t!==null)return t;try{for(let t of r(e)){let n=a(d(e,t));if(n!==null)return n}}catch{}}return null}function L(e,t,n){let r=n.timeoutMs??Ke;return new Promise(i=>{let a=g(e,[...t],{shell:n.shell,cwd:n.cwd,windowsHide:!0,...n.env===void 0?{}:{env:n.env}}),o=``,s=e=>{o.length<Ye&&(o+=e.toString(`utf8`))};a.stdout.on(`data`,s),a.stderr.on(`data`,s);let c=setTimeout(()=>{let e=Math.max(1,Math.round(r/1e3));o+=`\n[npm 超时 ${String(e)}s,已终止]`,We(a)},r);a.on(`error`,e=>{clearTimeout(c),i({code:null,output:`${o}\n${e.message}`})}),a.on(`close`,e=>{clearTimeout(c),i({code:e,output:o})})})}function tt(e){let t={};for(let[n,r]of Object.entries(e))r!==void 0&&(n.toLowerCase().startsWith(Xe)||(t[n]=r));return t}async function R(e,t,n,r,i,a){let o={...r??{}};i!==void 0&&(o.NPM_CONFIG_USERCONFIG=i,o.NPM_CONFIG_GLOBALCONFIG=`${i}.global-empty`);let s={shell:!1,cwd:n,env:{...tt(process.env),...o},...a===void 0?{}:{timeoutMs:a}};return e===null?L(process.platform===`win32`?`npm.cmd`:`npm`,t,{...s,shell:process.platform===`win32`}):L(process.execPath,[e,...t],s)}async function nt(e,t){let n=await Ue(async n=>{let r=await R(e.npmCli,[`view`,t,`version`,`--json`,`--registry`,n,`--fetch-timeout=${String(Je)}`,`--fetch-retries=0`],e.pluginsDir,e.env,e.userconfig,qe),i=r.code===0?rt(r.output):null;if(i===null)throw Error(`npm view 未能取到 ${t} 的版本(源 ${n})`);return i}).catch(()=>null);return n===null?null:n.value}function rt(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 it(n){t(n,{recursive:!0});let r=d(n,`package.json`);e(r)||c(r,`${JSON.stringify({name:`ezllm-plugins`,private:!0,version:`0.0.0`},null,2)}\n`,`utf8`)}async function at(e,t){let n=Ze(t,e=>f(e));if(n===null)return{ok:!1,message:`不是合法的 npm 包规格,也不是本机目录:${t}`};if(n.kind===`path`){let e=Qe(n.path);if(e!==null)return{ok:!1,message:e}}it(e.pluginsDir);let r=n.kind===`npm`?n.version===``?n.name:`${n.name}@${n.version}`:`file:${n.path}`,i=[`install`,`--no-audit`,`--no-fund`,`--save`,r];return e.log(`[plugin-host] npm install ${r}`),await Ue(async t=>{let n=await R(e.npmCli,[...i,`--registry`,t],e.pluginsDir,e.env,e.userconfig);if(n.code!==0){let e=n.output.trim().slice(-2e3),t=/npm (?:error|ERR!)\s*(?:code\s*)?E404|is not in this registry/i.test(n.output);throw Error(t?`npm 源里没有 ${r}(404)——请确认包名与版本已发布\n${e}`:`npm install 失败(退出码 ${String(n.code)}):\n${e}`)}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 ot(t,n){let r=I(n);if(r===null)return{ok:!1,message:`不是合法的 npm 包名:${n}`};let i=$e(t.pluginsDir,r);if(!e(i))return{ok:!1,message:`未安装:${r}`};t.log(`[plugin-host] npm uninstall ${r}`);let a=await R(t.npmCli,[`uninstall`,`--no-audit`,`--no-fund`,r],t.pluginsDir,t.env,t.userconfig);if(e(i))try{o(i,{recursive:!0,force:!0}),t.log(a.code===0?`[plugin-host] npm 未清理 ${r} 的目录,已手动删除`:`[plugin-host] npm uninstall 退出码 ${String(a.code)},已手动删除 ${r} 的目录`)}catch(e){return{ok:!1,message:`卸载 ${r} 未完成:npm 退出码 ${String(a.code)},且目录残留删除失败(${e instanceof Error?e.message:String(e)})\n${a.output.trim().slice(-1500)}`}}return a.code!==0&&!e(i)?{ok:!0,output:`npm 退出码 ${String(a.code)}(目录已删除)\n${a.output.trim().slice(-1500)}`}:a.code===0?{ok:!0,output:a.output.trim()}:{ok:!1,message:`npm uninstall 失败(退出码 ${String(a.code)}):\n${a.output.trim().slice(-2e3)}`}}function st(t){for(let r of[`README.md`,`readme.md`,`README.MD`,`README`]){let i=d(t,r);if(e(i))try{return n(i,`utf8`).slice(0,204800)}catch{return null}}return null}var ct=[{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 lt(e){return typeof e!=`string`||e===``?null:ct.find(t=>t.npm===e)??null}function z(e){return new Proxy(async()=>({data:void 0}),{get(t,n){if(n!==`then`)return z(`${e}.${String(n)}`)},apply(){return Promise.resolve({data:void 0})}})}function ut(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??b.getStore()?.providerId,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 z(`${t}.${r}`)}});return n(t,`client`)}function dt(e){return{client:ut(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 ft=`@magpie-community`,pt=`ezllmPlugin`;function mt(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 B(t,r){let i=d(t,`package.json`);if(!e(i))return null;let a;try{let e=JSON.parse(n(i,`utf-8`));if(typeof e!=`object`||!e||Array.isArray(e))return null;a=e}catch{return null}let o=typeof a.name==`string`&&a.name!==``?a.name:r;if(!o.startsWith(`${ft}/`)&&a[pt]!==!0)return null;let s=mt(a);if(s===void 0)return null;let c=a.repository,l=typeof c==`string`?c:typeof c==`object`&&c&&typeof c.url==`string`?String(c.url):``;return{name:o,version:typeof a.version==`string`?a.version:`0.0.0`,main:s,description:typeof a.description==`string`?a.description:``,license:typeof a.license==`string`?a.license:``,repository:l,dir:f(t)}}function V(t){let n=d(t,`node_modules`);if(!e(n))return[];let i;try{i=r(n,{withFileTypes:!0})}catch{return[]}let a=[];for(let e of i){if(e.name.startsWith(`.`))continue;if(!e.name.startsWith(`@`)){let t=B(d(n,e.name),e.name);t!==null&&a.push(t);continue}let t=d(n,e.name),i;try{i=r(t,{withFileTypes:!0})}catch{continue}for(let n of i){let r=B(d(t,n.name),`${e.name}/${n.name}`);r!==null&&a.push(r)}}return a}function ht(e){return d(e.dir,e.main)}function H(e,t){return V(e).find(e=>e.name===t)}function gt(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 _t(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=lt(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 vt(t,n){let r=[],i=[];for(let a of V(t)){let t=ht(a);if(!e(t)){i.push(`${a.name}: 入口不存在(${t})`);continue}let o;try{o=await import(m(t).href)}catch(e){i.push(`${a.name}: 导入失败(${e instanceof Error?e.message:String(e)})`);continue}let s=gt(o);if(s.length===0){i.push(`${a.name}: 没有导出插件工厂函数`);continue}let c=[],l={provider:{}};for(let[e,t]of s)try{let e=await t(dt(n));c.push(e),typeof e.config==`function`&&await e.config(l)}catch(t){i.push(`${a.name}#${e}: 工厂/config 失败(${t instanceof Error?t.message:String(t)})`)}if(c.length===0)continue;let u=_t(a,l,c);r.push({package:a.name,version:a.version,providers:u,hooks:c})}return{plugins:r,errors:i}}function U(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 yt(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 bt(){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>/provider-auth.json;旧名 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?})`,` DELETE /login/<id>(退出登录:删本机凭据 + 清会话)`,``,`环境变量(托管形态注入): PLUGIN_DIR · PORT · NODE_DIR`,``].join(`
|
|
2
|
+
import{existsSync as e,mkdirSync as t,readFileSync as n,readdirSync as r,realpathSync as i,renameSync as a,rmSync as o,statSync as s,writeFileSync as c}from"node:fs";import{basename as l,dirname as u,join as d,resolve as f}from"node:path";import{fileURLToPath as p,pathToFileURL as ee}from"node:url";import{createHash as te,randomBytes as m}from"node:crypto";import{AsyncLocalStorage as ne}from"node:async_hooks";import{homedir as re}from"node:os";import{cwd as ie}from"node:process";import{spawn as h}from"node:child_process";import{createServer as ae}from"node:http";var oe=20,se=2e3,ce=1e4,g=3e4;function le(e){Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,e)}function ue(e){return e instanceof Error?e.message:String(e)}var _=class extends Error{constructor(){super(`凭据文件内容异常(非合法 JSON 或形状不符),已拒绝写入以免覆盖现有凭据;请修复或备份后删除该文件`),this.name=`AuthFileCorruptError`}};function de(e){let t;try{t=n(e,`utf-8`)}catch{throw new _}let r;try{r=JSON.parse(t)}catch{throw new _}if(typeof r!=`object`||!r||Array.isArray(r))throw new _;let i={};for(let[e,t]of Object.entries(r)){if(typeof t!=`object`||!t||Array.isArray(t))throw new _;i[e]=t}return i}function fe(e){try{let t=JSON.parse(n(e,`utf-8`));if(typeof t!=`object`||!t||Array.isArray(t))return{};let r={};for(let[e,n]of Object.entries(t))typeof n==`object`&&n&&!Array.isArray(n)&&(r[e]=n);return r}catch{return{}}}function pe(e,n){let r=`${e}.${`${process.pid}.${m(6).toString(`hex`)}`}.tmp`;try{t(u(e),{recursive:!0}),c(r,`${JSON.stringify(n,null,2)}\n`,{encoding:`utf-8`,mode:384}),a(r,e)}finally{try{o(r,{force:!0})}catch{}}}function me(t,n){return e(t)?t:n.find(t=>e(t))??null}var he=class{#e;#t;#n;#r=null;#i=null;#a=-1;#o=!1;#s=0;constructor(e){this.#e=e.file,this.#t=e.legacyFiles??[],this.#n=e.log,this.#l()}get path(){return this.#e}read(){this.#o&&Date.now()>=this.#s&&this.#l();let e=me(this.#e,this.#t);if(e===null)return{};try{let t=s(e).mtimeMs;if(this.#r!==null&&this.#i===e&&this.#a===t)return{...this.#r};let n=fe(e);return this.#r=n,this.#i=e,this.#a=t,{...n}}catch{return{}}}invalidate(){this.#r=null,this.#i=null,this.#a=-1}mutate(e){return this.#d(()=>{let t=e(this.#c());return t===null?null:(pe(this.#e,t),this.#u(),this.#r=t,this.#i=this.#e,this.#a=s(this.#e).mtimeMs,{...t})})}#c(){let e=me(this.#e,this.#t);return e===null?{}:de(e)}#l(){let t=this.#t.find(t=>e(t));if(t===void 0){this.#o=!1;return}try{let n=this.#d(()=>e(t)?e(this.#e)?(o(t,{force:!0}),this.#n?.(`[auth] 已清理旧凭据文件:${t}(新文件已存在)`),!0):(a(t,this.#e),this.#n?.(`[auth] 凭据文件已迁移:${t} → ${this.#e}`),!0):!0);this.#o=!n,this.#s=n?0:Date.now()+g}catch(e){this.#o=!0,this.#s=Date.now()+g,this.#n?.(`[auth] 旧凭据文件处理失败(继续只读旧文件,${String(g/1e3)}s 后重试):${ue(e)}`)}}#u(){for(let t of this.#t)if(e(t))try{o(t,{force:!0}),this.#n?.(`[auth] 已清理旧凭据文件:${t}`)}catch{}this.#o=!1}#d(e){let n=`${this.#e}.lock`,r=`${process.pid}.${m(8).toString(`hex`)}`;t(u(n),{recursive:!0});let i=Date.now()+se;for(;;)try{t(n),c(d(n,`owner`),r,`utf-8`);break}catch(e){if(e.code!==`EEXIST`)throw e;try{if(Date.now()-s(n).mtimeMs>ce&&this.#m(n))continue}catch{}if(Date.now()>i)throw Error(`获取凭据文件锁超时:${l(n)}`);le(oe)}try{return e()}finally{this.#p(n,r)}}#f(e){try{return n(d(e,`owner`),`utf-8`)}catch{return null}}#p(e,t){try{if(this.#f(e)!==t)return;o(e,{recursive:!0,force:!0})}catch{}}#m(e){let t=this.#f(e),n=`${e}.stale.${m(4).toString(`hex`)}`;try{a(e,n)}catch{return!1}let r=this.#f(n);if(t!==null&&r!==null&&t!==r){try{a(n,e)}catch{}return!1}try{o(n,{recursive:!0,force:!0})}catch{}return!0}},v=new ne;function y(e,t,n){return v.run({providerId:e,generation:t},n)}var ge=3e5,_e=864e5,ve=15e3,ye=3e4,be=new class{renewing=new Map;failed=new Map;clear(e){let t=`${e}\0`;for(let e of[...this.renewing.keys()])e.startsWith(t)&&this.renewing.delete(e);for(let e of[...this.failed.keys()])e.startsWith(t)&&this.failed.delete(e)}};function b(e){let t=`${String(e?.refresh??``)}\0${String(e?.access??``)}`;return te(`sha256`).update(t).digest(`hex`).slice(0,16)}function xe(e){return typeof e==`number`&&Number.isFinite(e)&&e>=0?Math.min(e,_e):ge}function Se(e,t){if(t.type!==`oauth`)return 0;let n=Number(t.expires);return Number.isFinite(n)&&n>0?Math.max(1,n-e):0}var Ce=class{#e;constructor(e){this.#e=e}clear(e){(this.#e.registry??be).clear(e)}async ensureFresh(e){let t=this.#e.get(e),n=this.#e.refreshOf(e);if(t===void 0||n===void 0)return;let r=this.#n(),i=Se(xe(this.#e.leadOf(e)),t);if(!i||r<i)return;let a=this.#r(),o=`${e}\0${b(t)}`,s=a.failed.get(o);if(s!==void 0){if(r-s<ye)return;a.failed.delete(o)}let c=a.renewing.get(o);c===void 0&&(c=this.#t(e,t,n,o).finally(()=>a.renewing.delete(o)),a.renewing.set(o,c));let l;await Promise.race([c,new Promise(e=>{l=setTimeout(e,ve)})]),clearTimeout(l)}async#t(e,t,n,r){let i=this.#r(),a=this.#e.generation(e);try{let o=await y(e,a,()=>n({...t}));if(typeof o!=`object`||!o||Array.isArray(o)){i.failed.delete(r);return}let s=this.#e.get(e);if(s===void 0||this.#e.generation(e)!==a){i.failed.delete(r),this.#e.log?.(`[auth] 已丢弃过期的续期结果:${e}`);return}if(b(s)!==b(t)){i.failed.delete(r),this.#e.log?.(`[auth] 凭据已由供应商续期:${e}`);return}if(!this.#e.save(e,{...t,...o},{generation:a})){i.failed.delete(r),this.#e.log?.(`[auth] 已丢弃过期的续期结果:${e}`);return}i.failed.delete(r),this.#e.log?.(`[auth] 凭据已续期:${e}`)}catch(t){i.failed.set(r,this.#n()),this.#e.log?.(`[auth] 凭据续期失败:${e}(${t instanceof Error?t.message:String(t)})`)}}#n(){return this.#e.now?.()??Date.now()}#r(){return this.#e.registry??be}},x=class extends Error{status;constructor(e,t){super(t),this.name=`AuthError`,this.status=e}},S=class extends Error{constructor(e){super(e),this.name=`AuthFailure`}},we=2e3,Te=6e5;function C(e){return e instanceof Error?e.message:String(e)}function Ee(e){return`auth`in e}function De(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 Oe(e,t){let n=[];for(let r of e.prompts??[])typeof r.key!=`string`||r.key===``||r.key in t||De(r,t)&&n.push(r);return n}function ke(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:[]}))}function Ae(e){for(let t of[`accountId`,`email`,`name`,`account`,`uid`]){let n=e[t];if(typeof n==`string`&&n.trim()!==``)return n}return null}function w(e,t){return t==null||t===``||typeof e.accountId==`string`&&e.accountId!==``?e:{...e,accountId:t}}var je=class{#e;#t;#n;#r;#i=new Map;#a=new Set;#o=new Map;#s=new Map;constructor(e){this.#e=new he({file:e.file,legacyFiles:e.legacyFiles??[],log:e.log}),this.#t=e.now??Date.now,this.#n=e.log,this.#r=new Ce({get:e=>this.get(e),generation:e=>this.generation(e),save:(e,t,n)=>this.save(e,t,n),refreshOf:e=>this.#c(e)?.refresh,leadOf:e=>this.#c(e)?.refreshLead,now:e.now,log:e.log,registry:e.registry});for(let t of e.adapters??[])this.register(t)}register(e){let t=this.#s.get(e.id)??[];return this.#s.set(e.id,[...t,e].sort((e,t)=>(t.priority??10)-(e.priority??10))),()=>{let t=this.#s.get(e.id);if(t===void 0)return;let n=t.filter(t=>t!==e);if(n.length===0?this.#s.delete(e.id):this.#s.set(e.id,n),n.length===0){let t=this.#o.get(e.id);t!==void 0&&this.#u(t),this.#o.delete(e.id),this.#r.clear(e.id)}e.dispose?.()}}#c(e){return this.#s.get(e)?.[0]}hasAdapter(e){return this.#c(e)!==void 0}methodsOf(e){return(this.#c(e)?.methods()??[]).map(e=>({type:e.type,label:e.label??``}))}reload(){this.#e.invalidate()}generation(e){return this.#i.get(e)??0}locked(e){return this.#a.has(e)}lock(e){this.#a.add(e)}beginLogin(e){let t=this.generation(e)+1;return this.#i.set(e,t),this.#a.delete(e),t}get(e){let t=this.#e.read()[e];return t===void 0?void 0:{...t}}has(e){return e in this.#e.read()}providers(){return Object.keys(this.#e.read())}save(e,t,n){return n?.generation!==void 0&&n.generation!==this.generation(e)||this.#a.has(e)?!1:(this.#e.mutate(n=>({...n,[e]:{...t}})),!0)}saveFromPlugin(e,t){let n=v.getStore(),r=n?.providerId===e?{generation:n.generation}:void 0;return this.save(e,t,r)}remove(e){let t=this.generation(e)+1;return this.#i.set(e,t),this.#a.add(e),this.#e.mutate(t=>{if(!(e in t))return null;let n={...t};return delete n[e],n}),t}status(e){let t=this.get(e),n=t!==void 0&&typeof t.expires==`number`&&Number.isFinite(t.expires)?t.expires:null;return{provider:e,signedIn:t!==void 0,type:t!==void 0&&typeof t.type==`string`?t.type:null,account:t===void 0?null:Ae(t),expires:n,expiresInMs:n===null?null:Math.max(0,n-this.#t())}}list(){return[...new Set([...this.#s.keys(),...this.providers()])].sort().map(e=>this.status(e))}view(e){let t=this.#o.get(e);return{provider:e,signedIn:this.has(e),methods:this.methodsOf(e),state:t?.state??`idle`,...t===void 0?{}:{url:t.url,instructions:t.instructions,...t.error===void 0?{}:{error:t.error},...t.accountId===void 0?{}:{accountId:t.accountId},...t.expiresAt===null?{}:{expiresAt:t.expiresAt}}}}async signIn(e,t={}){let n=this.#c(e),r=n?.methods()??[];if(n===void 0||r.length===0)throw new x(400,`${e} 未声明登录方式(auth.methods),无法登录;该供应商可能不需要登录`);if(t.restart!==!0&&this.#o.get(e)?.state===`pending`)return this.view(e);let i=t.method??``,a=t.inputs??{};if(i===``&&r.length>1)return{...this.view(e),needMethod:!0};let o=i===``?r[0]:r.find(e=>(e.label??e.type)===i);if(o===void 0)throw new x(400,`未知登录方式:${i}(可选:${r.map(e=>e.label??`(未命名)`).join(`、`)})`);let s=Oe(o,a);if(s.length>0)return{...this.view(e),needInputs:ke(s),inputs:a};let c=(t.key??``).trim();if(o.type===`api`&&c===``)return{...this.view(e),needKey:!0,placeholder:o.placeholder??``,label:o.label??o.type,inputs:a};let l=this.beginLogin(e),u;try{let t={key:c,inputs:a};u=await y(e,l,()=>n.authorize(o,t))}catch(t){if(this.#p(e,l),t instanceof S)return this.#o.set(e,{state:`failed`,url:``,instructions:``,expiresAt:null,attempt:null,error:t.message,generation:l}),this.#n?.(`[auth] ✗ ${e} 登录失败:${t.message}`),this.view(e);throw t instanceof x?t:new x(502,`起登录流程失败:${C(t)}`)}if(Ee(u))return this.save(e,w(u.auth,u.account),{generation:l})?(this.#o.set(e,{state:`success`,url:``,instructions:``,expiresAt:null,attempt:null,generation:l,...u.account===void 0||u.account===null||u.account===``?{}:{accountId:u.account}}),this.#n?.(`[auth] ✓ ${e} 登录完成${u.account?`(${u.account})`:``}`),this.view(e)):(this.#o.set(e,{state:`failed`,url:``,instructions:``,expiresAt:null,attempt:null,error:`登录结果已过期(已登出或被新的登录流程取代)`,generation:l}),this.view(e));let d={state:`pending`,url:u.url,instructions:u.instructions,expiresAt:u.expiresAt,attempt:u,generation:l};return this.#o.set(e,d),this.#l(e,d),this.#n?.(`[auth] ▶ ${e} 登录已发起(${o.label??o.type}${u.url===``?``:`,等待浏览器完成`})`),this.view(e)}async poll(e){let t=this.#o.get(e);return t===void 0||t.state!==`pending`||t.attempt===null?this.view(e):this.#d(e,t)}#l(e,t){t.deadline=t.expiresAt??this.#t()+Te;let n=()=>{if(this.#o.get(e)!==t||t.state!==`pending`)return;let r=setTimeout(()=>{this.#o.get(e)===t&&t.state===`pending`&&this.#d(e,t).catch(()=>void 0).finally(n)},we);r.unref?.(),t.timer=r};n()}#u(e){e.timer!==null&&e.timer!==void 0&&clearTimeout(e.timer),e.timer=null}#d(e,t){if(t.polling!==null&&t.polling!==void 0)return t.polling;let n=this.#f(e,t).finally(()=>{t.polling===n&&(t.polling=null)});return t.polling=n,n}async#f(e,t){let n=t.deadline??t.expiresAt;if(n!=null&&this.#t()>n)return this.#u(t),this.#o.delete(e),this.#n?.(`[auth] ✗ ${e} 授权已超时`),{...this.view(e),state:`failed`,error:`授权已超时,请重新发起`};let r=t.attempt;if(r===null)return this.view(e);let i;try{i=await r.poll()}catch(n){let r=C(n);return this.#o.get(e)===t?(this.#p(e,t.generation),this.#u(t),this.#o.delete(e),this.#n?.(`[auth] ✗ ${e} 登录失败:${r}`),{...this.view(e),state:`failed`,error:r}):this.view(e)}return this.#o.get(e)!==t||i===null?this.view(e):this.save(e,w(i.auth,i.account),{generation:t.generation})?(t.state=`success`,t.attempt=null,t.error=void 0,this.#u(t),i.account!==void 0&&i.account!==null&&i.account!==``&&(t.accountId=i.account),this.#n?.(`[auth] ✓ ${e} 登录完成${t.accountId?`(${t.accountId})`:``}`),this.view(e)):(t.state=`failed`,t.error=`登录结果已过期(已登出或被新的登录流程取代)`,t.attempt=null,this.#u(t),this.view(e))}signOut(e){let t=this.has(e),n=this.#o.get(e);return n!==void 0&&this.#u(n),this.remove(e),this.#r.clear(e),this.#o.delete(e),this.#c(e)?.dispose?.(),t&&this.#n?.(`[auth] ⏏ ${e} 已退出登录`),this.view(e)}async ensureFresh(e){await this.#r.ensureFresh(e)}#p(e,t){this.generation(e)===t&&this.get(e)===void 0&&this.#a.add(e)}};function T(e,...t){console.log(e,...t)}function E(e,...t){console.warn(e,...t)}function Me(e,...t){console.error(e,...t)}function D(e){if(e===void 0)return;let t=e.trim();return t===``?void 0:t}function O(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 D(i.slice(n.length));if(i===t){let t=r[e+1]??``;return t.startsWith(`-`)?void 0:D(t)}}}function k(e,t){return O(e)??D(process.env[t])}var Ne=65535;function Pe(e){let t=D(e);if(t===void 0)return;let n=Number(t);return Number.isInteger(n)&&n>0&&n<=Ne?n:void 0}var Fe=(()=>{let e=k(`app-dir`,`ELLM_APP_DIR`),t=e===void 0?void 0:f(ie(),e),n=O(`port`),r=Pe(n??D(process.env.ELLM_PORT));n!==void 0&&r===void 0&&E(`--port=${n} 无效(需为 1-65535 的整数),已忽略:本次不生效、也不会写入配置文件。`);let i=k(`node-mirror`,`ELLM_NODE_MIRROR`),a=k(`node-bin`,`ELLM_NODE_BIN`);return t!==void 0&&(process.env.ELLM_APP_DIR=t),i!==void 0&&(process.env.ELLM_NODE_MIRROR=i),a!==void 0&&(process.env.ELLM_NODE_BIN=a),{appDir:t,port:r,nodeMirror:i,nodeBin:a}})(),A=u(p(import.meta.url)),j;function Ie(){return d(re(),`.ezllm`)}function M(){return j===void 0&&(j=Fe.appDir??Ie()),j}function Le(){return f(M(),`plugins`)}function Re(){return f(M(),`provider-auth.json`)}function ze(){return f(M(),`plugin-auth.json`)}function Be(e){try{let t=n(d(e,`package.json`),`utf-8`).replace(/^\uFEFF/,``);return JSON.parse(t)}catch{return null}}function N(e,t){let n=A;for(let r=0;r<t;r++){if(Be(n)?.name===e)return n;n=u(n)}return null}N(`ezllm`,5)??f(A,`..`),N(`ellm`,6);function P(e,t=`SIGTERM`){let n=e.pid;if(process.platform!==`win32`||n===void 0){e.kill(t);return}h(`taskkill`,[`/PID`,String(n),`/T`,`/F`],{stdio:`ignore`,windowsHide:!0}).on(`error`,()=>{e.kill(t)}).unref()}var Ve=`https://registry.npmmirror.com/`,F=`https://registry.npmjs.org/`,I=1e4,He=5e3,L=5e3,R=class{#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u;#d;#f;#p=null;#m=null;#h=null;constructor(e={}){this.#e=e.nodePath??process.execPath,this.#t=e.runtimeDir,this.#n=e.npmCliPath,this.#r=e.cwd,this.#i=e.env,this.#a=e.run??((e,t)=>this.#v(e,t??{})),this.#o=e.global??!0,this.#s=e.prefix,this.#c=e.extraArgs??[],this.#l=e.timeoutMs??3e5,this.#u=e.stdio===`capture`?`pipe`:`inherit`,this.#d=e.target,this.#f=e.errorOf}static isNewerVersion(e,t){let n=e=>String(e||``).split(`-`)[0].split(`.`).map(e=>Number(e)||0),r=n(e),i=n(t);for(let e=0;e<3;e++)if((r[e]??0)!==(i[e]??0))return(r[e]??0)>(i[e]??0);return!1}run(e,t={}){return this.#a(e,t)}async checkUpdate(e){let t=e.trim();if(t===``)throw Error(`包名不能为空`);let{value:n,registry:r}=await this.#_(async e=>{let n=await this.#S(this.#a([`view`,t,`version`,`--json`,`--registry`,e,`--prefer-online`,`--fetch-timeout=${String(He)}`,`--fetch-retries=0`],{timeoutMs:I}),11e3,()=>`查询 ${t} 最新版本超时(源 ${e} 在 ${String(I/1e3)}s 内无响应)`);if(n.status!==0)throw Error(`查询 ${t} 最新版本失败(npm 退出码 ${String(n.status)},源 ${e})。请检查网络与 registry 可达性。`);let r=this.#C(`${n.stdout??``}`);if(r===null)throw Error(`查询 ${t} 最新版本失败(输出无法解析,源 ${e})`);return r});return this.#h={spec:`${t}@${n}`,registry:r},{version:n,registry:r}}async installUpdate(){let e=this.#d??this.#h;if(e==null)throw Error(`先调用 checkUpdate 再安装(或在构造时传 target)`);if(this.#o&&(this.#s===void 0||this.#s===``))throw Error(`全局安装缺少 prefix(--prefix 目标目录)`);let t=[`install`,...this.#o?[`-g`,`--prefix`,this.#s]:[],e.spec,...this.#c],{value:n}=await this.#_(async e=>{let n=await this.#a([...t,`--registry`,e],{timeoutMs:this.#l,stdio:this.#u,cwd:this.#r});if(n.status!==0)throw this.#f?.(n,e)??Error(`npm install 失败(退出码 ${String(n.status)},源 ${e})`);return{status:n.status??0,registry:e,stdout:`${n.stdout??``}`}},e.registry);return n}async#g(){if(this.#m===null){let e=[Ve,await(async()=>{try{let e=await this.#S(this.#a([`config`,`get`,`registry`,`--json`],{timeoutMs:L}),L,()=>`读取 npm 默认 registry 超时`),t=`${e.stdout??``}`.split(/\r?\n/).map(e=>e.trim()).filter(e=>e!==``).pop()??``,n=t;try{let e=JSON.parse(t);typeof e==`string`&&(n=e)}catch{}return e.status===0&&/^https?:\/\//.test(n)?n:F}catch{return F}})(),F];this.#m=[...new Set(e)]}return[...this.#m]}async#_(e,t){let n=await this.#g(),r=t===void 0?n:[t,...n],i=r.filter((e,t)=>r.indexOf(e)===t),a=[];for(let t of i)try{return{value:await e(t),registry:t}}catch(e){a.push({source:t,message:e instanceof Error?e.message:String(e),error:e})}if(a.length===0)throw Error(`没有任何可用的 npm registry`);let o=[...new Set(a.map(e=>e.message))],s=o.length===1?`所有源返回相同错误(已依次尝试:${i.join(`、`)}):\n${o[0]??``}`:`所有源都失败:\n${a.map(e=>`- ${e.source}:${e.message}`).join(`
|
|
3
|
+
`)}`,c=Error(s);throw c.cause=a[a.length-1]?.error,c}#v(e,t){let n=t.stdio===`inherit`;return new Promise((r,i)=>{let a;try{a=h(this.#e,[this.#y(),...e],{cwd:t.cwd??this.#r,env:this.#i,stdio:n?[`ignore`,`inherit`,`inherit`]:[`ignore`,`pipe`,`pipe`],windowsHide:!0})}catch(e){i(e);return}let o=``,s=``,c=!1,l=e=>{c||(c=!0,e())},u=t.timeoutMs===void 0?null:setTimeout(()=>{P(a);let e=Math.max(1,Math.round(t.timeoutMs/1e3));l(()=>i(Error(`npm 未在 ${String(e)}s 内完成,已终止`)))},t.timeoutMs);a.stdout?.on(`data`,e=>{o+=String(e)}),a.stderr?.on(`data`,e=>{s+=String(e)}),a.on(`error`,e=>{u!==null&&clearTimeout(u),l(()=>i(e))}),a.on(`close`,e=>{u!==null&&clearTimeout(u),l(()=>r({status:e,stdout:o,stderr:s}))})})}#y(){return this.#p===null&&(this.#p=this.#n??(this.#t===void 0?this.#x(this.#e):this.#b(this.#t))),this.#p}#b(t){let n=[d(t,`node_modules`,`npm`,`bin`,`npm-cli.js`),d(t,`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`)].find(t=>e(t));if(!n)throw Error(`未找到托管运行时自带的 npm(运行时根 ${t})。\n 可执行 npx ezllm install 重新装配托管环境(--node-bin 场景请确认外部 node 带 npm)。`);return n}#x(t){let n=u(t),r=[d(n,`node_modules`,`npm`,`bin`,`npm-cli.js`),d(n,`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`),d(n,`..`,`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`)].find(t=>e(t));if(!r)throw Error(`未找到 node 自带的 npm(node: ${t})。\n 可用 npmCliPath 显式指定,或 npx ezllm install 重新装配托管环境。`);return r}#S(e,t,n){return new Promise((r,i)=>{let a=setTimeout(()=>i(Error(n())),t);e.then(e=>{clearTimeout(a),r(e)},e=>{clearTimeout(a),i(e)})})}#C(e){let t=e=>{try{let t=JSON.parse(e);return typeof t==`string`&&t.trim()!==``?t.trim():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}},Ue=/^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(@[a-zA-Z0-9.^~<>=*|xX -]+)?$/,We=3e5,Ge=65536,Ke=`npm_config_`;function z(e){let t=e.trim();if(t===``||!Ue.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 qe(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=z(n);return r===null?null:{kind:`npm`,name:r.name,version:r.version}}function Je(t){return e(t)?s(t).isDirectory()?e(d(t,`package.json`))?null:`目录里没有 package.json:${t}`:`不是目录:${t}`:`目录不存在:${t}`}function B(e){return z(e)?.name??null}function Ye(e,t){return d(e,`node_modules`,...t.split(`/`))}function Xe(t,n){let i=t=>t.endsWith(`npm-cli.js`)&&e(t),a=e=>{for(let t of[[`node_modules`,`npm`,`bin`,`npm-cli.js`],[`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`],[`bin`,`npm-cli.js`]]){let n=d(e,...t);if(i(n))return n}return null};if(t!==``){let e=a(t);if(e!==null)return e;if(i(t))return t;let n=d(t,`npm`,`bin`,`npm-cli.js`);if(i(n))return n}let o=d(u(process.execPath),`node_modules`,`npm`,`bin`,`npm-cli.js`);if(i(o))return o;let s=d(u(process.execPath),`..`,`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`);if(i(s))return s;if(n!==null){let e=d(n,`node`),t=a(e);if(t!==null)return t;try{for(let t of r(e)){let n=a(d(e,t));if(n!==null)return n}}catch{}}return null}function Ze(e,t,n){let r=n.timeoutMs??We;return new Promise(i=>{let a=h(e,[...t],{shell:n.shell,cwd:n.cwd,windowsHide:!0,...n.env===void 0?{}:{env:n.env}}),o=``,s=e=>{o.length<Ge&&(o+=e.toString(`utf8`))};a.stdout.on(`data`,s),a.stderr.on(`data`,s);let c=setTimeout(()=>{let e=Math.max(1,Math.round(r/1e3));o+=`\n[npm 超时 ${String(e)}s,已终止]`,P(a)},r);a.on(`error`,e=>{clearTimeout(c),i({code:null,output:`${o}\n${e.message}`})}),a.on(`close`,e=>{clearTimeout(c),i({code:e,output:o})})})}function Qe(e){let t={};for(let[n,r]of Object.entries(e))r!==void 0&&(n.toLowerCase().startsWith(Ke)||(t[n]=r));return t}async function V(e,t,n,r,i,a){let o={...r??{}};i!==void 0&&(o.NPM_CONFIG_USERCONFIG=i,o.NPM_CONFIG_GLOBALCONFIG=`${i}.global-empty`);let s={shell:!1,cwd:n,env:{...Qe(process.env),...o},...a===void 0?{}:{timeoutMs:a}};return e===null?Ze(process.platform===`win32`?`npm.cmd`:`npm`,t,{...s,shell:process.platform===`win32`}):Ze(process.execPath,[e,...t],s)}async function $e(e,t){let n=await new R({run:async t=>{let n=await V(e.npmCli,t,e.pluginsDir,e.env,e.userconfig,I);return{status:n.code,stdout:n.output}}}).checkUpdate(t).catch(()=>null);return n===null?null:n.version}function et(n){t(n,{recursive:!0});let r=d(n,`package.json`);e(r)||c(r,`${JSON.stringify({name:`ezllm-plugins`,private:!0,version:`0.0.0`},null,2)}\n`,`utf8`)}async function tt(e,t){let n=qe(t,e=>f(e));if(n===null)return{ok:!1,message:`不是合法的 npm 包规格,也不是本机目录:${t}`};if(n.kind===`path`){let e=Je(n.path);if(e!==null)return{ok:!1,message:e}}et(e.pluginsDir);let r=n.kind===`npm`?n.version===``?n.name:`${n.name}@${n.version}`:`file:${n.path}`;return e.log(`[plugin-host] npm install ${r}`),await new R({run:async(t,n)=>{let r=await V(e.npmCli,t,n?.cwd??e.pluginsDir,e.env,e.userconfig);return{status:r.code,stdout:r.output}},global:!1,cwd:e.pluginsDir,extraArgs:[`--save`,`--no-audit`,`--no-fund`],stdio:`capture`,target:{spec:r},errorOf:(e,t)=>{let n=(e.stdout??``).trim(),i=n.slice(-2e3),a=/npm (?:error|ERR!)\s*(?:code\s*)?E404|is not in this registry/i.test(n);return Error(a?`npm 源里没有 ${r}(404)——请确认包名与版本已发布\n${i}`:`npm install 失败(退出码 ${String(e.status)},源 ${t}):\n${i}`)}}).installUpdate().then(e=>({ok:!0,output:e.stdout.trim()})).catch(e=>({ok:!1,message:e instanceof Error?e.message:`npm install 失败:${String(e)}`}))}async function nt(t,n){let r=B(n);if(r===null)return{ok:!1,message:`不是合法的 npm 包名:${n}`};let i=Ye(t.pluginsDir,r);if(!e(i))return{ok:!1,message:`未安装:${r}`};t.log(`[plugin-host] npm uninstall ${r}`);let a=await V(t.npmCli,[`uninstall`,`--no-audit`,`--no-fund`,r],t.pluginsDir,t.env,t.userconfig);if(e(i))try{o(i,{recursive:!0,force:!0}),t.log(a.code===0?`[plugin-host] npm 未清理 ${r} 的目录,已手动删除`:`[plugin-host] npm uninstall 退出码 ${String(a.code)},已手动删除 ${r} 的目录`)}catch(e){return{ok:!1,message:`卸载 ${r} 未完成:npm 退出码 ${String(a.code)},且目录残留删除失败(${e instanceof Error?e.message:String(e)})\n${a.output.trim().slice(-1500)}`}}return a.code!==0&&!e(i)?{ok:!0,output:`npm 退出码 ${String(a.code)}(目录已删除)\n${a.output.trim().slice(-1500)}`}:a.code===0?{ok:!0,output:a.output.trim()}:{ok:!1,message:`npm uninstall 失败(退出码 ${String(a.code)}):\n${a.output.trim().slice(-2e3)}`}}function rt(t){for(let r of[`README.md`,`readme.md`,`README.MD`,`README`]){let i=d(t,r);if(e(i))try{return n(i,`utf8`).slice(0,204800)}catch{return null}}return null}var it=[{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 at(e){return typeof e!=`string`||e===``?null:it.find(t=>t.npm===e)??null}function ot(e){return new Proxy(async()=>({data:void 0}),{get(t,n){if(n!==`then`)return ot(`${e}.${String(n)}`)},apply(){return Promise.resolve({data:void 0})}})}function st(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??v.getStore()?.providerId,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 ot(`${t}.${r}`)}});return n(t,`client`)}function ct(e){return{client:st(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 lt=`@magpie-community`,ut=`ezllmPlugin`;function dt(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 ft(t,r){let i=d(t,`package.json`);if(!e(i))return null;let a;try{let e=JSON.parse(n(i,`utf-8`));if(typeof e!=`object`||!e||Array.isArray(e))return null;a=e}catch{return null}let o=typeof a.name==`string`&&a.name!==``?a.name:r;if(!o.startsWith(`${lt}/`)&&a[ut]!==!0)return null;let s=dt(a);if(s===void 0)return null;let c=a.repository,l=typeof c==`string`?c:typeof c==`object`&&c&&typeof c.url==`string`?String(c.url):``;return{name:o,version:typeof a.version==`string`?a.version:`0.0.0`,main:s,description:typeof a.description==`string`?a.description:``,license:typeof a.license==`string`?a.license:``,repository:l,dir:f(t)}}function H(t){let n=d(t,`node_modules`);if(!e(n))return[];let i;try{i=r(n,{withFileTypes:!0})}catch{return[]}let a=[];for(let e of i){if(e.name.startsWith(`.`))continue;if(!e.name.startsWith(`@`)){let t=ft(d(n,e.name),e.name);t!==null&&a.push(t);continue}let t=d(n,e.name),i;try{i=r(t,{withFileTypes:!0})}catch{continue}for(let n of i){let r=ft(d(t,n.name),`${e.name}/${n.name}`);r!==null&&a.push(r)}}return a}function pt(e){return d(e.dir,e.main)}function mt(e,t){return H(e).find(e=>e.name===t)}function ht(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 gt(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=at(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 _t(t,n){let r=[],i=[];for(let a of H(t)){let t=pt(a);if(!e(t)){i.push(`${a.name}: 入口不存在(${t})`);continue}let o;try{o=await import(ee(t).href)}catch(e){i.push(`${a.name}: 导入失败(${e instanceof Error?e.message:String(e)})`);continue}let s=ht(o);if(s.length===0){i.push(`${a.name}: 没有导出插件工厂函数`);continue}let c=[],l={provider:{}};for(let[e,t]of s)try{let e=await t(ct(n));c.push(e),typeof e.config==`function`&&await e.config(l)}catch(t){i.push(`${a.name}#${e}: 工厂/config 失败(${t instanceof Error?t.message:String(t)})`)}if(c.length===0)continue;let u=gt(a,l,c);r.push({package:a.name,version:a.version,providers:u,hooks:c})}return{plugins:r,errors:i}}function U(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 vt(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 yt(){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>/provider-auth.json;旧名 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?})`,` DELETE /login/<id>(退出登录:删本机凭据 + 清会话)`,``,`环境变量(托管形态注入): PLUGIN_DIR · PORT · NODE_DIR`,``].join(`
|
|
4
4
|
`)+`
|
|
5
|
-
`}var xt=class extends Error{status;detail;constructor(e,t,n){super(t),this.name=`HttpError`,this.status=e,this.detail=n}};function W(e,t){return{error:{message:e,type:t}}}function G(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function St(e){let t=e.search(/[?#]/);return t>=0?e.slice(0,t):e}function K(e,t,n){if(e.headersSent){try{e.end()}catch{}return}e.writeHead(t,{"Content-Type":`application/json`}),e.end(JSON.stringify(n))}function q(e,t,n){K(e,t,{detail:n})}function J(e=process.env){return Number(e.PLUGIN_HOST_MAX_BODY||33554432)}function Y(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 xt(413,`request body too large`,W(`request body too large`,`invalid_request_error`)));return}i.push(n)}),e.on(`end`,()=>{n(Buffer.concat(i))}),e.on(`error`,r)})}async function*Ct(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 wt(e,t,n){let r=typeof e.accountId==`string`?e.accountId:null;if(`refresh`in e){let t={...e};return delete t.type,delete t.provider,{auth:{...t,type:`oauth`},account:r}}let i=typeof e.key==`string`&&e.key!==``?e.key:t,a={...n,...G(e.metadata)?e.metadata:{}};return{auth:{type:`api`,key:i,...Object.keys(a).length>0?{metadata:a}:{}},account:r}}function Tt(e){let t={...e};return delete t.type,delete t.provider,{auth:{...t,type:`oauth`},account:typeof e.accountId==`string`?e.accountId:null}}function Et(e,t){return e.prompts!==void 0&&e.prompts.length>0?t.inputs:void 0}function Dt(e){return e instanceof Error?e:Error(String(e))}function Ot(e){let t=e.hooks;if(t===null)return null;let n=t.auth,r=Array.isArray(n.methods)?n.methods:[];return{id:e.id,priority:100,methods:()=>r,authorize:async(e,t)=>{let n=e,r=n.authorize,i=Et(n,t);if(n.type===`api`){if(typeof r!=`function`)return{auth:{type:`api`,key:t.key,...Object.keys(t.inputs).length>0?{metadata:t.inputs}:{}}};let e=await r(i),n=G(e)?e:{};if(n.type!==`success`)throw new T(typeof n.error==`string`&&n.error!==``?n.error:`登录未完成`);return wt(n,t.key,t.inputs)}if(typeof r!=`function`)throw new w(400,`登录方式「${n.label??n.type}」缺少 authorize 实现(插件声明与实现不一致)`);let a=await r(i),o=G(a)?a:{},s=o.callback;if(typeof s!=`function`)throw new w(400,`登录流程没有 callback(插件声明与实现不一致)`);let c=null,l=null;return(async()=>{try{let e=await s(),t=G(e)?e:{};if(t.type===`success`)c=Tt(t);else{let e=typeof t.error==`string`&&t.error!==``?t.error:`登录未完成`;l=Error(e)}}catch(e){l=Dt(e)}})(),{url:typeof o.url==`string`?o.url:``,instructions:typeof o.instructions==`string`?o.instructions:``,expiresAt:null,poll:async()=>{if(l!==null)throw l;return c}}},...typeof n.refresh==`function`?{refresh:async t=>await n.refresh({...t},e.config)??null}:{},...typeof n.refreshLead==`number`?{refreshLead:n.refreshLead}:{}}}var kt=class{#e;#t;constructor(e,t){this.#e=e,this.#t=t.auth}current(){return this.#t.get(this.#e)}generation(){return this.#t.generation(this.#e)}getAuth=async()=>this.current();async ensureFresh(){await this.#t.ensureFresh(this.#e)}},At=new Set([`host`,`connection`,`keep-alive`,`proxy-authenticate`,`proxy-authorization`,`te`,`trailer`,`transfer-encoding`,`upgrade`,`content-length`,`authorization`,`x-api-key`,`x-plugin-session`]),jt=`x-plugin-session`;function Mt(e,t){if(t!==``)return t;let n=e.provider.config.models?.[e.model],r=n?.provider?.api??n?.api?.url;if(typeof r==`string`&&r!==``)return r;let i=e.provider.config.api;return typeof i==`string`?i:``}function Nt(e,t){return t===void 0||t===``?{}:e===`anthropic`?{"x-api-key":t}:{authorization:`Bearer ${t}`}}function Pt(e){let t={};for(let[n,r]of Object.entries(e.headers)){let e=n.toLowerCase();At.has(e)||r!==void 0&&(t[e]=Array.isArray(r)?r.join(`, `):r)}return t}function Ft(e,t){let n=new Headers;for(let[r,i]of Object.entries(Nt(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(Pt(e.req)))n.set(t,r);return{headers:n,baseURL:Mt(e,typeof t.baseURL==`string`?t.baseURL:``),fetchFn:typeof t.fetch==`function`?t.fetch:fetch}}async function It(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 Lt(e,t){return Y(e,t)}async function Rt(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=r.generation(),o;try{o=await x(n.id,a,()=>i(r.getAuth,n.config))??{}}catch(e){return{kind:`error`,status:502,message:`插件 loader 失败:${e instanceof Error?e.message:String(e)}`}}let s=Ft(e,o);if(s.baseURL===``)return{kind:`error`,status:502,message:`插件 ${n.source.package} 与 provider「${n.id}」都没有给出上游地址(loader.baseURL 与 config.api 均为空)`};await x(n.id,a,()=>It(s.headers,e,t));let c=`${s.baseURL.replace(/\/+$/,``)}/${e.path.replace(/^\/+/,``)}`;try{return{kind:`ok`,response:await x(n.id,a,()=>s.fetchFn(c,{method:e.method,headers:s.headers,body:e.body.length>0?e.body:void 0}))}}catch(e){return{kind:`error`,status:502,message:`上游请求失败:${e instanceof Error?e.message:String(e)}`}}}async function zt(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 Ct(e.body))n(t)}function Bt(e){return(e.headers.get(`content-type`)??``).includes(`text/event-stream`)}function Vt(e,t){try{let t=JSON.parse(e.toString(`utf8`));if(G(t)&&typeof t.model==`string`&&t.model!==``)return t.model}catch{}return t.models[0]??``}var Ht=new WeakMap,Ut=new WeakMap;function Wt(e){let t=e.loadResult();if(Ht.get(e.auth)===t)return;for(let t of Ut.get(e.auth)??[])t();let n=[];for(let r of U(t).providers){let t=Ot(r);t!==null&&n.push(e.auth.register(t))}Ut.set(e.auth,n),Ht.set(e.auth,t)}function Gt(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 Kt=[`127.0.0.1`,`localhost`,`[::1]`,`::1`],qt=`拒绝的 `;function Jt(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 Yt(e){return e===`0.0.0.0`||e===`::`||e===``}function Xt(e,t){if(Yt(e.host))return;let n=[...Kt,e.host.trim().toLowerCase()],r=Jt(String(t.headers.host??``));if(!n.includes(r))throw Error(`${qt}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 Zt(e){let t=e.hooks?.auth.methods;return Array.isArray(t)&&t.length>0}function Qt(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,needsLogin:Zt(e),hooks:e.capabilities}}function $t(e,t){let{providers:n}=U(e.loadResult());K(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 en(e,t){let n=e.loadResult(),{providers:r,errors:i}=U(n);K(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=>Qt(t,e.auth.has(t.id))),errors:i})}function X(e,t){return U(e.loadResult()).providers.filter(e=>e.source.package===t)}function tn(e,t){let n=V(e.options.pluginsDir).map(t=>{let n=X(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),needsLogin:Zt(t)}))}});K(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 nn(e,t,n){let r=H(e.options.pluginsDir,t);if(r===void 0)return q(n,404,W(`未安装:${t}`,`not_found`));let i=X(e,r.name);K(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:st(r.dir),loaded:i.length>0,providers:i.map(t=>Qt(t,e.auth.has(t.id))),errors:e.loadResult().errors.filter(e=>e.includes(r.name))})}async function Z(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 rn(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 an=Promise.resolve();function on(e){let t=an.then(e,e);return an=t.then(()=>void 0,()=>void 0),t}async function sn(e,t,n){if(e.install===void 0||e.reload===void 0)return q(n,501,W(`本进程未启用安装能力(未注入 npm 依赖)`,`not_implemented`));let r;try{r=await Y(t,J())}catch(e){return q(n,413,W(e instanceof Error?e.message:String(e),`invalid_request_error`))}let i;try{i=r.length===0?{}:JSON.parse(r.toString(`utf8`))}catch{return q(n,400,W(`body 不是合法 JSON`,`invalid_request_error`))}if(!G(i)||typeof i.package!=`string`||i.package.trim()===``)return q(n,400,W(`缺少 package(npm 包名,可带 @版本)`,`invalid_request_error`));let a=i.package.trim(),o=typeof i.version==`string`&&i.version.trim()!==``?i.version.trim():``,s=o===``?a:`${I(a)??a}@${o}`;if(F(s)===null)return q(n,400,W(`不是合法的 npm 包规格:${s}`,`invalid_request_error`));e.log(`[plugin-host] 安装 ${s}`);let c=await at(e.install,s);if(!c.ok)return e.log(`[plugin-host] ✗ 安装失败:${s}`),q(n,502,W(c.message,`install_error`));let l=await Z(e),u=I(s)??s,d=H(e.options.pluginsDir,u);e.log(`[plugin-host] ✓ 已安装 ${u}${d?`@${d.version}`:``}`),K(n,200,{status:`ok`,package:u,version:d?.version??``,loaded:X(e,u).length>0,providers:X(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}),rn(e)}async function cn(e,t,n){if(e.install===void 0||e.reload===void 0)return q(n,501,W(`本进程未启用卸载能力(未注入 npm 依赖)`,`not_implemented`));e.log(`[plugin-host] 卸载 ${t}`);let r=await ot(e.install,t);if(!r.ok)return q(n,r.message.startsWith(`未安装`)?404:502,W(r.message,r.message.startsWith(`未安装`)?`not_found`:`install_error`));let i=await Z(e);e.log(`[plugin-host] ✓ 已卸载 ${t}`),K(n,200,{status:`ok`,package:t,npmOutput:r.output.slice(-2e3),reloadErrors:i,restartRequested:e.options.mainPort>0}),rn(e)}var ln=36e5;async function un(e,t){let n=e.versionCache??=new Map,r=Date.now(),i=[];for(let t of V(e.options.pluginsDir)){let a=null;if(e.install!==void 0){let i=n.get(t.name);i!==void 0&&r-i.at<ln?a=i.latest:(a=await nt(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})}K(t,200,{status:`ok`,canCheck:e.install!==void 0,packages:i})}async function dn(e){let t=await Y(e,J());if(t.length===0)return{};let n;try{n=JSON.parse(t.toString(`utf-8`))}catch{throw Error(`请求体不是合法 JSON`)}let r=G(n)?n:{},i={};if(G(r.inputs))for(let[e,t]of Object.entries(r.inputs))typeof t==`string`&&(i[e]=t);return{...typeof r.method==`string`?{method:r.method}:{},...typeof r.key==`string`?{key:r.key}:{},...Object.keys(i).length>0?{inputs:i}:{}}}async function fn(e,t,n){K(n,200,await e.auth.poll(t.id))}async function pn(e,t,n,r){let i;try{i=await dn(n)}catch(e){return q(r,400,W(e instanceof Error?e.message:String(e),`invalid_request_error`))}try{K(r,200,await e.auth.signIn(t.id,i))}catch(e){if(e instanceof w)return q(r,e.status,W(e.message,e.status===502?`upstream_error`:`invalid_request_error`));throw e}}function mn(e,t,n){K(n,200,e.auth.signOut(t.id))}function hn(e,t){let{providers:n}=U(e.loadResult());K(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>`,`DELETE /login/<id>`],providers:n.map(e=>({id:e.id,apiBase:e.protocol===null?null:`/p/${e.id}/v1`,protocol:e.protocol}))})}function gn(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 U(e.loadResult()).providers.find(e=>e.id===t)}function $(e,t){if(t.hooks===null)throw Error(`provider「${t.id}」没有 auth 钩子(插件声明与实现不一致)`);return new kt(t.id,{auth:e.auth})}async function _n(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 x(t.id,n.generation(),()=>a(t.config,{auth:o})),s=Object.keys(e??{});s.length>0&&(r=s,i=!0)}catch(n){e.log(`[plugin-host] ${t.id}: provider.models 失败,回落声明表(${n instanceof Error?n.message:String(n)})`)}}K(n,200,{object:`list`,provider:t.id,fromHook:i,data:r.map(e=>({id:e,object:`model`,owned_by:t.id}))})}async function vn(e,t,n){let r=t.hooks?.auth.usage;if(t.hooks===null||t.capabilities.usage===!1||typeof r!=`function`)return q(n,400,W(`该插件未实现额度查询`,`invalid_request_error`));let i=$(e,t);await i.ensureFresh();let a;try{a=await x(t.id,i.generation(),()=>r(i.getAuth,t.config))}catch(r){let i=r instanceof Error?r.message:String(r);return e.log(`[plugin-host] ✗ ${t.id} 额度查询失败:${i}`),q(n,502,W(`额度查询失败:${i}`,`upstream_error`))}K(n,200,a)}async function yn(e,t,n,r,i){if(t.protocol===null)return q(i,501,W(`${t.id} 的协议未知(插件未声明可识别的 npm),不可转发`,`not_implemented`));if(n!==t.path)return q(i,404,W(`${t.id} 只提供 /p/${t.id}/v1/${t.path}(插件声明的协议是 ${t.protocol})`,`not_found`));let a;try{a=await Lt(r,J())}catch(e){return q(i,413,W(e instanceof Error?e.message:String(e),`invalid_request_error`))}let o=r.headers[jt],s=typeof o==`string`?o:``,c=Vt(a,t),l=Date.now();e.log(`[plugin-host] ▶ ${t.id} ${n} model=${c} bytes=${String(a.length)}${s?` session=${s}`:``}`);let u=await Rt({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}`),q(i,u.status,W(u.message,u.status===401?`auth_error`:`upstream_error`));let d=u.response,f=Bt(d);e.log(`[plugin-host] ◀ ${t.id} ${n} status=${String(d.status)} ${f?`stream`:`json`} ${String(Date.now()-l)}ms`),await zt(d,(e,t)=>{i.headersSent||i.writeHead(e,t)},e=>{i.write(e)}),i.end()}async function bn(e,t,n){let r=St(t.url??`/`),i=(t.method??`GET`).toUpperCase();try{if(Wt(e),Xt(e.options,t),Gt(e.options,t),i===`GET`&&r===`/health`)return $t(e,n);if(i===`GET`&&r===`/plugins`)return en(e,n);if(i===`GET`&&r===`/`)return hn(e,n);if(r===`/packages`)return i===`GET`?tn(e,n):i===`POST`?await on(()=>sn(e,t,n)):q(n,405,W(`不支持的方法:${i} ${r}`,`invalid_request_error`));if(r===`/packages/updates`)return i===`GET`?await un(e,n):q(n,405,W(`不支持的方法:${i} ${r}`,`invalid_request_error`));let a=/^\/packages\/(.+)$/.exec(r);if(a!==null){let t=decodeURIComponent(a[1]??``);return t===``?q(n,404,W(`未找到:${i} ${r}`,`not_found`)):i===`GET`?nn(e,t,n):i===`DELETE`?await on(()=>cn(e,t,n)):q(n,405,W(`不支持的方法:${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?q(n,404,W(`未加载 provider:${a}`,`not_found`)):i===`GET`?fn(e,s,n):i===`POST`?await pn(e,s,t,n):i===`DELETE`?mn(e,s,n):q(n,405,W(`不支持的方法:${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?q(n,404,W(`未加载 provider:${t}`,`not_found`)):i===`GET`?await vn(e,a,n):q(n,405,W(`不支持的方法:${i} ${r}`,`invalid_request_error`))}let c=gn(r);if(c!==null){let a=Q(e,c.id);return a===void 0?q(n,404,W(`未加载 provider:${c.id}`,`not_found`)):i===`GET`&&c.rest===`models`?await _n(e,a,n):i===`POST`?await yn(e,a,c.rest,t,n):q(n,405,W(`不支持的方法:${i} ${r}`,`invalid_request_error`))}q(n,404,W(`未找到:${i} ${r}`,`not_found`))}catch(e){let t=e instanceof Error?e.message:String(e),r=t===`invalid api key`?401:t.startsWith(qt)?403:500;q(n,r,W(t,r===500?`internal_error`:`invalid_request_error`))}}function xn(e){return ie((t,n)=>{bn(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:W(String(t),`internal_error`)}))}catch{}})})}function Sn(e){return{...e,pluginsDir:e.pluginsDir===``?Ie():e.pluginsDir,authFile:e.authFile===``?Le():e.authFile}}function Cn(e,t,n){let{providers:r,errors:i}=U(t);E(`==== 插件宿主预检 ====`),E(`平台 : ${process.platform}`),E(`Node : ${process.version}`),E(`插件目录 : ${e.pluginsDir}`),E(`凭据文件 : ${e.authFile}`),E(`已装插件 : ${String(t.plugins.length)} 个(provider ${String(r.length)} 个)`);for(let e of r){let t=e.protocol??`未知协议(不可用)`,r=n(e.id)?`已登录`:`未登录`;E(` - ${e.id}(${e.name}): ${t} | ${String(e.models.length)} 模型 | ${r}`)}for(let e of i)D(` ! ${e}`);r.length===0&&D(" 未加载到任何 provider——用 `npm install <plugin> --prefix` 装进插件目录后重启"),E(`======================`)}async function wn(e=process.argv.slice(2),t=process.env){let{options:n,help:r}=yt(e,t);if(r)return process.stdout.write(bt()),null;let i=Sn(n),a=new Ae({file:i.authFile,legacyFiles:n.authFile===``?[Re()]:[],log:e=>E(e)}),o={plugins:[],errors:[]},s=()=>o,c={directory:i.pluginsDir,saveAuth:(e,t)=>{if(!a.saveFromPlugin(e,t)){E(`[plugin-host] 已忽略过期/登出后的凭据写入:${e}`);return}E(`[plugin-host] 凭据已保存:${e}`)}},l=async()=>{let e=await vt(i.pluginsDir,c);return o=e,e.errors};try{await l()}catch(e){D(`[plugin-host] 加载插件失败:${e instanceof Error?e.message:String(e)}`)}i.skipCheck||Cn(i,s(),e=>a.has(e));let u=et(i.nodeDir,N()),d={pluginsDir:i.pluginsDir,npmCli:u,log:e=>E(e)};E(`[plugin-host] ✅ 监听 http://${i.host}:${String(i.port)}`),E(`[plugin-host] GET /health`),E(`[plugin-host] GET /plugins`),E(`[plugin-host] GET /packages · POST /packages · DELETE /packages/<name>`),E(`[plugin-host] GET /login/<id> · POST /login/<id> · DELETE /login/<id>`),E(`[plugin-host] 安装能力: ${u===null?`npm 未找到(仅可运行已装插件)`:`已启用(${u})`}`),i.apiKey&&E(`[plugin-host] 鉴权已启用(API key 已设置)`),E(`[plugin-host] 按 Ctrl+C 退出。`);let f=xn({options:i,auth:a,loadResult:s,reload:l,install:d,log:e=>E(e)});return await new Promise((e,t)=>{f.once(`error`,t),f.listen(i.port,i.host,()=>{f.off(`error`,t),e()})}),E(`[plugin-host] listening on ${i.host}:${String(i.port)}`),{server:f,options:i,auth:a,loadResult:s,reload:l}}function Tn(){let e=process.argv[1];if(e===void 0)return!1;try{let t=i(p(import.meta.url)),n=i(f(e));return process.platform===`win32`?t.toLowerCase()===n.toLowerCase():t===n}catch{return import.meta.url===m(f(e)).href}}Tn()&&wn().catch(e=>{je(`[plugin-host] 启动失败:${e instanceof Error?e.message:String(e)}`),process.exitCode=1});export{wn as main};
|
|
5
|
+
`}var bt=class extends Error{status;detail;constructor(e,t,n){super(t),this.name=`HttpError`,this.status=e,this.detail=n}};function W(e,t){return{error:{message:e,type:t}}}function G(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function xt(e){let t=e.search(/[?#]/);return t>=0?e.slice(0,t):e}function K(e,t,n){if(e.headersSent){try{e.end()}catch{}return}e.writeHead(t,{"Content-Type":`application/json`}),e.end(JSON.stringify(n))}function q(e,t,n){K(e,t,{detail:n})}function J(e=process.env){return Number(e.PLUGIN_HOST_MAX_BODY||33554432)}function Y(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 bt(413,`request body too large`,W(`request body too large`,`invalid_request_error`)));return}i.push(n)}),e.on(`end`,()=>{n(Buffer.concat(i))}),e.on(`error`,r)})}async function*St(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 Ct(e,t,n){let r=typeof e.accountId==`string`?e.accountId:null;if(`refresh`in e){let t={...e};return delete t.type,delete t.provider,{auth:{...t,type:`oauth`},account:r}}let i=typeof e.key==`string`&&e.key!==``?e.key:t,a={...n,...G(e.metadata)?e.metadata:{}};return{auth:{type:`api`,key:i,...Object.keys(a).length>0?{metadata:a}:{}},account:r}}function wt(e){let t={...e};return delete t.type,delete t.provider,{auth:{...t,type:`oauth`},account:typeof e.accountId==`string`?e.accountId:null}}function Tt(e,t){return e.prompts!==void 0&&e.prompts.length>0?t.inputs:void 0}function Et(e){return e instanceof Error?e:Error(String(e))}function Dt(e){let t=e.hooks;if(t===null)return null;let n=t.auth,r=Array.isArray(n.methods)?n.methods:[];return{id:e.id,priority:100,methods:()=>r,authorize:async(e,t)=>{let n=e,r=n.authorize,i=Tt(n,t);if(n.type===`api`){if(typeof r!=`function`)return{auth:{type:`api`,key:t.key,...Object.keys(t.inputs).length>0?{metadata:t.inputs}:{}}};let e=await r(i),n=G(e)?e:{};if(n.type!==`success`)throw new S(typeof n.error==`string`&&n.error!==``?n.error:`登录未完成`);return Ct(n,t.key,t.inputs)}if(typeof r!=`function`)throw new x(400,`登录方式「${n.label??n.type}」缺少 authorize 实现(插件声明与实现不一致)`);let a=await r(i),o=G(a)?a:{},s=o.callback;if(typeof s!=`function`)throw new x(400,`登录流程没有 callback(插件声明与实现不一致)`);let c=null,l=null;return(async()=>{try{let e=await s(),t=G(e)?e:{};if(t.type===`success`)c=wt(t);else{let e=typeof t.error==`string`&&t.error!==``?t.error:`登录未完成`;l=Error(e)}}catch(e){l=Et(e)}})(),{url:typeof o.url==`string`?o.url:``,instructions:typeof o.instructions==`string`?o.instructions:``,expiresAt:null,poll:async()=>{if(l!==null)throw l;return c}}},...typeof n.refresh==`function`?{refresh:async t=>await n.refresh({...t},e.config)??null}:{},...typeof n.refreshLead==`number`?{refreshLead:n.refreshLead}:{}}}var Ot=class{#e;#t;constructor(e,t){this.#e=e,this.#t=t.auth}current(){return this.#t.get(this.#e)}generation(){return this.#t.generation(this.#e)}getAuth=async()=>this.current();async ensureFresh(){await this.#t.ensureFresh(this.#e)}},kt=new Set([`host`,`connection`,`keep-alive`,`proxy-authenticate`,`proxy-authorization`,`te`,`trailer`,`transfer-encoding`,`upgrade`,`content-length`,`authorization`,`x-api-key`,`x-plugin-session`]),At=`x-plugin-session`;function jt(e,t){if(t!==``)return t;let n=e.provider.config.models?.[e.model],r=n?.provider?.api??n?.api?.url;if(typeof r==`string`&&r!==``)return r;let i=e.provider.config.api;return typeof i==`string`?i:``}function Mt(e,t){return t===void 0||t===``?{}:e===`anthropic`?{"x-api-key":t}:{authorization:`Bearer ${t}`}}function Nt(e){let t={};for(let[n,r]of Object.entries(e.headers)){let e=n.toLowerCase();kt.has(e)||r!==void 0&&(t[e]=Array.isArray(r)?r.join(`, `):r)}return t}function Pt(e,t){let n=new Headers;for(let[r,i]of Object.entries(Mt(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(Nt(e.req)))n.set(t,r);return{headers:n,baseURL:jt(e,typeof t.baseURL==`string`?t.baseURL:``),fetchFn:typeof t.fetch==`function`?t.fetch:fetch}}async function Ft(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 It(e,t){return Y(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=r.generation(),o;try{o=await y(n.id,a,()=>i(r.getAuth,n.config))??{}}catch(e){return{kind:`error`,status:502,message:`插件 loader 失败:${e instanceof Error?e.message:String(e)}`}}let s=Pt(e,o);if(s.baseURL===``)return{kind:`error`,status:502,message:`插件 ${n.source.package} 与 provider「${n.id}」都没有给出上游地址(loader.baseURL 与 config.api 均为空)`};await y(n.id,a,()=>Ft(s.headers,e,t));let c=`${s.baseURL.replace(/\/+$/,``)}/${e.path.replace(/^\/+/,``)}`;try{return{kind:`ok`,response:await y(n.id,a,()=>s.fetchFn(c,{method:e.method,headers:s.headers,body:e.body.length>0?e.body:void 0}))}}catch(e){return{kind:`error`,status:502,message:`上游请求失败:${e instanceof Error?e.message:String(e)}`}}}async function Rt(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 St(e.body))n(t)}function zt(e){return(e.headers.get(`content-type`)??``).includes(`text/event-stream`)}function Bt(e,t){try{let t=JSON.parse(e.toString(`utf8`));if(G(t)&&typeof t.model==`string`&&t.model!==``)return t.model}catch{}return t.models[0]??``}var Vt=new WeakMap,Ht=new WeakMap;function Ut(e){let t=e.loadResult();if(Vt.get(e.auth)===t)return;for(let t of Ht.get(e.auth)??[])t();let n=[];for(let r of U(t).providers){let t=Dt(r);t!==null&&n.push(e.auth.register(t))}Ht.set(e.auth,n),Vt.set(e.auth,t)}function Wt(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 Gt=[`127.0.0.1`,`localhost`,`[::1]`,`::1`],Kt=`拒绝的 `;function qt(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 Jt(e){return e===`0.0.0.0`||e===`::`||e===``}function Yt(e,t){if(Jt(e.host))return;let n=[...Gt,e.host.trim().toLowerCase()],r=qt(String(t.headers.host??``));if(!n.includes(r))throw Error(`${Kt}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 Xt(e){let t=e.hooks?.auth.methods;return Array.isArray(t)&&t.length>0}function Zt(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,needsLogin:Xt(e),hooks:e.capabilities}}function Qt(e,t){let{providers:n}=U(e.loadResult());K(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 $t(e,t){let n=e.loadResult(),{providers:r,errors:i}=U(n);K(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=>Zt(t,e.auth.has(t.id))),errors:i})}function X(e,t){return U(e.loadResult()).providers.filter(e=>e.source.package===t)}function en(e,t){let n=H(e.options.pluginsDir).map(t=>{let n=X(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),needsLogin:Xt(t)}))}});K(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 tn(e,t,n){let r=mt(e.options.pluginsDir,t);if(r===void 0)return q(n,404,W(`未安装:${t}`,`not_found`));let i=X(e,r.name);K(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:rt(r.dir),loaded:i.length>0,providers:i.map(t=>Zt(t,e.auth.has(t.id))),errors:e.loadResult().errors.filter(e=>e.includes(r.name))})}async function Z(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 nn(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 rn=Promise.resolve();function an(e){let t=rn.then(e,e);return rn=t.then(()=>void 0,()=>void 0),t}async function on(e,t,n){if(e.install===void 0||e.reload===void 0)return q(n,501,W(`本进程未启用安装能力(未注入 npm 依赖)`,`not_implemented`));let r;try{r=await Y(t,J())}catch(e){return q(n,413,W(e instanceof Error?e.message:String(e),`invalid_request_error`))}let i;try{i=r.length===0?{}:JSON.parse(r.toString(`utf8`))}catch{return q(n,400,W(`body 不是合法 JSON`,`invalid_request_error`))}if(!G(i)||typeof i.package!=`string`||i.package.trim()===``)return q(n,400,W(`缺少 package(npm 包名,可带 @版本)`,`invalid_request_error`));let a=i.package.trim(),o=typeof i.version==`string`&&i.version.trim()!==``?i.version.trim():``,s=o===``?a:`${B(a)??a}@${o}`;if(z(s)===null)return q(n,400,W(`不是合法的 npm 包规格:${s}`,`invalid_request_error`));e.log(`[plugin-host] 安装 ${s}`);let c=await tt(e.install,s);if(!c.ok)return e.log(`[plugin-host] ✗ 安装失败:${s}`),q(n,502,W(c.message,`install_error`));let l=await Z(e),u=B(s)??s,d=mt(e.options.pluginsDir,u);e.log(`[plugin-host] ✓ 已安装 ${u}${d?`@${d.version}`:``}`),K(n,200,{status:`ok`,package:u,version:d?.version??``,loaded:X(e,u).length>0,providers:X(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}),nn(e)}async function sn(e,t,n){if(e.install===void 0||e.reload===void 0)return q(n,501,W(`本进程未启用卸载能力(未注入 npm 依赖)`,`not_implemented`));e.log(`[plugin-host] 卸载 ${t}`);let r=await nt(e.install,t);if(!r.ok)return q(n,r.message.startsWith(`未安装`)?404:502,W(r.message,r.message.startsWith(`未安装`)?`not_found`:`install_error`));let i=await Z(e);e.log(`[plugin-host] ✓ 已卸载 ${t}`),K(n,200,{status:`ok`,package:t,npmOutput:r.output.slice(-2e3),reloadErrors:i,restartRequested:e.options.mainPort>0}),nn(e)}var cn=36e5;async function ln(e,t){let n=e.versionCache??=new Map,r=Date.now(),i=[];for(let t of H(e.options.pluginsDir)){let a=null;if(e.install!==void 0){let i=n.get(t.name);i!==void 0&&r-i.at<cn?a=i.latest:(a=await $e(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&&R.isNewerVersion(a,t.version)})}K(t,200,{status:`ok`,canCheck:e.install!==void 0,packages:i})}async function un(e){let t=await Y(e,J());if(t.length===0)return{};let n;try{n=JSON.parse(t.toString(`utf-8`))}catch{throw Error(`请求体不是合法 JSON`)}let r=G(n)?n:{},i={};if(G(r.inputs))for(let[e,t]of Object.entries(r.inputs))typeof t==`string`&&(i[e]=t);return{...typeof r.method==`string`?{method:r.method}:{},...typeof r.key==`string`?{key:r.key}:{},...Object.keys(i).length>0?{inputs:i}:{}}}async function dn(e,t,n){K(n,200,await e.auth.poll(t.id))}async function fn(e,t,n,r){let i;try{i=await un(n)}catch(e){return q(r,400,W(e instanceof Error?e.message:String(e),`invalid_request_error`))}try{K(r,200,await e.auth.signIn(t.id,i))}catch(e){if(e instanceof x)return q(r,e.status,W(e.message,e.status===502?`upstream_error`:`invalid_request_error`));throw e}}function pn(e,t,n){K(n,200,e.auth.signOut(t.id))}function mn(e,t){let{providers:n}=U(e.loadResult());K(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>`,`DELETE /login/<id>`],providers:n.map(e=>({id:e.id,apiBase:e.protocol===null?null:`/p/${e.id}/v1`,protocol:e.protocol}))})}function hn(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 U(e.loadResult()).providers.find(e=>e.id===t)}function $(e,t){if(t.hooks===null)throw Error(`provider「${t.id}」没有 auth 钩子(插件声明与实现不一致)`);return new Ot(t.id,{auth:e.auth})}async function gn(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 y(t.id,n.generation(),()=>a(t.config,{auth:o})),s=Object.keys(e??{});s.length>0&&(r=s,i=!0)}catch(n){e.log(`[plugin-host] ${t.id}: provider.models 失败,回落声明表(${n instanceof Error?n.message:String(n)})`)}}K(n,200,{object:`list`,provider:t.id,fromHook:i,data:r.map(e=>({id:e,object:`model`,owned_by:t.id}))})}async function _n(e,t,n){let r=t.hooks?.auth.usage;if(t.hooks===null||t.capabilities.usage===!1||typeof r!=`function`)return q(n,400,W(`该插件未实现额度查询`,`invalid_request_error`));let i=$(e,t);await i.ensureFresh();let a;try{a=await y(t.id,i.generation(),()=>r(i.getAuth,t.config))}catch(r){let i=r instanceof Error?r.message:String(r);return e.log(`[plugin-host] ✗ ${t.id} 额度查询失败:${i}`),q(n,502,W(`额度查询失败:${i}`,`upstream_error`))}K(n,200,a)}async function vn(e,t,n,r,i){if(t.protocol===null)return q(i,501,W(`${t.id} 的协议未知(插件未声明可识别的 npm),不可转发`,`not_implemented`));if(n!==t.path)return q(i,404,W(`${t.id} 只提供 /p/${t.id}/v1/${t.path}(插件声明的协议是 ${t.protocol})`,`not_found`));let a;try{a=await It(r,J())}catch(e){return q(i,413,W(e instanceof Error?e.message:String(e),`invalid_request_error`))}let o=r.headers[At],s=typeof o==`string`?o:``,c=Bt(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}`),q(i,u.status,W(u.message,u.status===401?`auth_error`:`upstream_error`));let d=u.response,f=zt(d);e.log(`[plugin-host] ◀ ${t.id} ${n} status=${String(d.status)} ${f?`stream`:`json`} ${String(Date.now()-l)}ms`),await Rt(d,(e,t)=>{i.headersSent||i.writeHead(e,t)},e=>{i.write(e)}),i.end()}async function yn(e,t,n){let r=xt(t.url??`/`),i=(t.method??`GET`).toUpperCase();try{if(Ut(e),Yt(e.options,t),Wt(e.options,t),i===`GET`&&r===`/health`)return Qt(e,n);if(i===`GET`&&r===`/plugins`)return $t(e,n);if(i===`GET`&&r===`/`)return mn(e,n);if(r===`/packages`)return i===`GET`?en(e,n):i===`POST`?await an(()=>on(e,t,n)):q(n,405,W(`不支持的方法:${i} ${r}`,`invalid_request_error`));if(r===`/packages/updates`)return i===`GET`?await ln(e,n):q(n,405,W(`不支持的方法:${i} ${r}`,`invalid_request_error`));let a=/^\/packages\/(.+)$/.exec(r);if(a!==null){let t=decodeURIComponent(a[1]??``);return t===``?q(n,404,W(`未找到:${i} ${r}`,`not_found`)):i===`GET`?tn(e,t,n):i===`DELETE`?await an(()=>sn(e,t,n)):q(n,405,W(`不支持的方法:${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?q(n,404,W(`未加载 provider:${a}`,`not_found`)):i===`GET`?dn(e,s,n):i===`POST`?await fn(e,s,t,n):i===`DELETE`?pn(e,s,n):q(n,405,W(`不支持的方法:${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?q(n,404,W(`未加载 provider:${t}`,`not_found`)):i===`GET`?await _n(e,a,n):q(n,405,W(`不支持的方法:${i} ${r}`,`invalid_request_error`))}let c=hn(r);if(c!==null){let a=Q(e,c.id);return a===void 0?q(n,404,W(`未加载 provider:${c.id}`,`not_found`)):i===`GET`&&c.rest===`models`?await gn(e,a,n):i===`POST`?await vn(e,a,c.rest,t,n):q(n,405,W(`不支持的方法:${i} ${r}`,`invalid_request_error`))}q(n,404,W(`未找到:${i} ${r}`,`not_found`))}catch(e){let t=e instanceof Error?e.message:String(e),r=t===`invalid api key`?401:t.startsWith(Kt)?403:500;q(n,r,W(t,r===500?`internal_error`:`invalid_request_error`))}}function bn(e){return ae((t,n)=>{yn(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:W(String(t),`internal_error`)}))}catch{}})})}function xn(e){return{...e,pluginsDir:e.pluginsDir===``?Le():e.pluginsDir,authFile:e.authFile===``?Re():e.authFile}}function Sn(e,t,n){let{providers:r,errors:i}=U(t);T(`==== 插件宿主预检 ====`),T(`平台 : ${process.platform}`),T(`Node : ${process.version}`),T(`插件目录 : ${e.pluginsDir}`),T(`凭据文件 : ${e.authFile}`),T(`已装插件 : ${String(t.plugins.length)} 个(provider ${String(r.length)} 个)`);for(let e of r){let t=e.protocol??`未知协议(不可用)`,r=n(e.id)?`已登录`:`未登录`;T(` - ${e.id}(${e.name}): ${t} | ${String(e.models.length)} 模型 | ${r}`)}for(let e of i)E(` ! ${e}`);r.length===0&&E(" 未加载到任何 provider——用 `npm install <plugin> --prefix` 装进插件目录后重启"),T(`======================`)}async function Cn(e=process.argv.slice(2),t=process.env){let{options:n,help:r}=vt(e,t);if(r)return process.stdout.write(yt()),null;let i=xn(n),a=new je({file:i.authFile,legacyFiles:n.authFile===``?[ze()]:[],log:e=>T(e)}),o={plugins:[],errors:[]},s=()=>o,c={directory:i.pluginsDir,saveAuth:(e,t)=>{if(!a.saveFromPlugin(e,t)){T(`[plugin-host] 已忽略过期/登出后的凭据写入:${e}`);return}T(`[plugin-host] 凭据已保存:${e}`)}},l=async()=>{let e=await _t(i.pluginsDir,c);return o=e,e.errors};try{await l()}catch(e){E(`[plugin-host] 加载插件失败:${e instanceof Error?e.message:String(e)}`)}i.skipCheck||Sn(i,s(),e=>a.has(e));let u=Xe(i.nodeDir,M()),d={pluginsDir:i.pluginsDir,npmCli:u,log:e=>T(e)};T(`[plugin-host] ✅ 监听 http://${i.host}:${String(i.port)}`),T(`[plugin-host] GET /health`),T(`[plugin-host] GET /plugins`),T(`[plugin-host] GET /packages · POST /packages · DELETE /packages/<name>`),T(`[plugin-host] GET /login/<id> · POST /login/<id> · DELETE /login/<id>`),T(`[plugin-host] 安装能力: ${u===null?`npm 未找到(仅可运行已装插件)`:`已启用(${u})`}`),i.apiKey&&T(`[plugin-host] 鉴权已启用(API key 已设置)`),T(`[plugin-host] 按 Ctrl+C 退出。`);let f=bn({options:i,auth:a,loadResult:s,reload:l,install:d,log:e=>T(e)});return await new Promise((e,t)=>{f.once(`error`,t),f.listen(i.port,i.host,()=>{f.off(`error`,t),e()})}),T(`[plugin-host] listening on ${i.host}:${String(i.port)}`),{server:f,options:i,auth:a,loadResult:s,reload:l}}function wn(){let e=process.argv[1];if(e===void 0)return!1;try{let t=i(p(import.meta.url)),n=i(f(e));return process.platform===`win32`?t.toLowerCase()===n.toLowerCase():t===n}catch{return import.meta.url===ee(f(e)).href}}wn()&&Cn().catch(e=>{Me(`[plugin-host] 启动失败:${e instanceof Error?e.message:String(e)}`),process.exitCode=1});export{Cn as main};
|