ezllm 0.0.44 → 0.0.45
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/plugin-host.js +3 -3
- package/dist/web-ui/assets/{DashboardView-DTL4kRw7.js → DashboardView-CkTFY0Da.js} +1 -1
- package/dist/web-ui/assets/{LayoutView-CU11b-ex.js → LayoutView-CcXUd-b0.js} +1 -1
- package/dist/web-ui/assets/{LoginView-D1Bu2XC9.js → LoginView-Dq7gaxB9.js} +1 -1
- package/dist/web-ui/assets/{LogsView-qU6iMr5I.js → LogsView-CO6GNtfl.js} +1 -1
- package/dist/web-ui/assets/{ManagedServicesView-B6JE5Odv.js → ManagedServicesView-CNLHAVAL.js} +1 -1
- package/dist/web-ui/assets/PluginsView-B2H4xfag.css +1 -0
- package/dist/web-ui/assets/PluginsView-Dljy9HZq.js +2 -0
- package/dist/web-ui/assets/ProvidersView-CD3QYu3i.js +14 -0
- package/dist/web-ui/assets/ProvidersView-Cwe2Q81Y.css +1 -0
- package/dist/web-ui/assets/{ServiceStatusView-A8mjOoU-.js → ServiceStatusView-BgHmV09I.js} +1 -1
- package/dist/web-ui/assets/{SettingsView-CYnqDlfO.js → SettingsView-BLQl0mEB.js} +1 -1
- package/dist/web-ui/assets/{SetupView-BSRObllO.js → SetupView-BzZqIZpo.js} +1 -1
- package/dist/web-ui/assets/{config-CcujDHN0.js → config-CLbcTOt9.js} +1 -1
- package/dist/web-ui/assets/{index-B5mLXy9e.js → index-yak1S-7W.js} +1 -1
- package/dist/web-ui/assets/{message-t8lJfFLj.js → message-K5WVknE-.js} +1 -1
- package/dist/web-ui/assets/pluginProviders-CB38zjhC.js +1 -0
- package/dist/web-ui/assets/{providers-CqcnYJca.js → providers-BuzQNH1W.js} +1 -1
- package/dist/web-ui/assets/{router-C6GERXs3.js → router-tvNif7bs.js} +2 -2
- package/dist/web-ui/assets/{useCopyToast-OBVjElCH.js → useCopyToast-BeWZ6lSF.js} +1 -1
- package/dist/web-ui/assets/{useManagerApi-DoVhcoa7.js → useManagerApi-Wm9Nz3_p.js} +1 -1
- package/dist/web-ui/assets/{useQuickRangeFilter-YdXjHGk-.js → useQuickRangeFilter-DQQ8QAr5.js} +1 -1
- package/dist/web-ui/assets/useToast-D_jja27E.js +1 -0
- package/dist/web-ui/index.html +3 -3
- package/package.json +1 -1
- package/dist/web-ui/assets/PluginsView-Ar17i6aK.css +0 -1
- package/dist/web-ui/assets/PluginsView-Cjv5DQzy.js +0 -2
- package/dist/web-ui/assets/ProvidersView-B31OhA_q.js +0 -14
- package/dist/web-ui/assets/ProvidersView-B6GPraOb.css +0 -1
- package/dist/web-ui/assets/useToast-Du3Ickw8.js +0 -1
package/dist/plugin-host.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{dirname as e,join as t,resolve as n}from"node:path";import{fileURLToPath as r,pathToFileURL as i}from"node:url";import{cwd as a}from"node:process";import{existsSync as o,mkdirSync as s,readFileSync as c,readdirSync as l,renameSync as u,rmSync as d,statSync as f,writeFileSync as p}from"node:fs";import{homedir as m}from"node:os";import{createHash as h,randomBytes as g}from"node:crypto";import{spawn as ee}from"node:child_process";import{createServer as te}from"node:http";function
|
|
3
|
-
`,`utf-8`),u(n,e)}finally{try{d(n,{force:!0})}catch{}}}var fe=`https://registry.npmmirror.com/`,pe=`https://registry.npmjs.org/`;function me(){return[fe,pe]}async function he(e,t=me()){let n;for(let r of t)try{return{value:await e(r),registry:r}}catch(e){n=e}throw n??Error(`没有任何可用的 npm registry`)}function T(e){try{let t=JSON.parse(c(e,`utf-8`));if(typeof t!=`object`||!t||Array.isArray(t))return{};let n={};for(let[e,r]of Object.entries(t))typeof r==`object`&&r&&!Array.isArray(r)&&(n[e]=r);return n}catch{return{}}}function E(e,t){de(e,t)}var ge=class{#e;#t;constructor(e){this.#e=e,this.#t=T(e)}reload(){this.#t=T(this.#e)}get(e){let t=this.#t[e];return t===void 0?void 0:{...t}}has(e){return e in this.#t}providers(){return Object.keys(this.#t)}save(e,t){let n={...this.#t,[e]:{...t}};E(this.#e,n),this.#t=n}remove(e){if(!(e in this.#t))return;let t={...this.#t};delete t[e],E(this.#e,t),this.#t=t}},_e=/^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(@[a-zA-Z0-9.^~<>=*|xX -]+)?$/,D=3e5,ve=65536,ye=`npm_config_`;function O(e){let t=e.trim();if(t===``||!_e.test(t))return null;let n=t.lastIndexOf(`@`);return n<=0?{name:t,version:``}:{name:t.slice(0,n),version:t.slice(n+1)}}function be(e,t=e=>e){let n=e.trim();if(n===``)return null;if(n.startsWith(`./`)||n.startsWith(`../`)||n.startsWith(`file:`)||n.startsWith(`/`)||/^[a-zA-Z]:[\\/]/.test(n)||n.startsWith(`\\\\`)){let e=n.startsWith(`file:`)?n.slice(5):n;return e.trim()===``?null:{kind:`path`,path:t(e.trim())}}let r=O(n);return r===null?null:{kind:`npm`,name:r.name,version:r.version}}function xe(e){return o(e)?f(e).isDirectory()?o(t(e,`package.json`))?null:`目录里没有 package.json:${e}`:`不是目录:${e}`:`目录不存在:${e}`}function k(e){return O(e)?.name??null}function Se(e,n){return t(e,`node_modules`,...n.split(`/`))}function Ce(n,r){let i=e=>e.endsWith(`npm-cli.js`)&&o(e),a=e=>{for(let n of[[`node_modules`,`npm`,`bin`,`npm-cli.js`],[`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`],[`bin`,`npm-cli.js`]]){let r=t(e,...n);if(i(r))return r}return null};if(n!==``){let e=a(n);if(e!==null)return e;if(i(n))return n;let r=t(n,`npm`,`bin`,`npm-cli.js`);if(i(r))return r}let s=t(e(process.execPath),`node_modules`,`npm`,`bin`,`npm-cli.js`);if(i(s))return s;let c=t(e(process.execPath),`..`,`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`);if(i(c))return c;if(r!==null){let e=t(r,`node`),n=a(e);if(n!==null)return n;try{for(let n of l(e)){let r=a(t(e,n));if(r!==null)return r}}catch{}}return null}function A(e,t,n){return new Promise(r=>{let i=ee(e,[...t],{shell:n.shell,cwd:n.cwd,windowsHide:!0,...n.env===void 0?{}:{env:n.env}}),a=``,o=e=>{a.length<ve&&(a+=e.toString(`utf8`))};i.stdout.on(`data`,o),i.stderr.on(`data`,o);let s=setTimeout(()=>{a+=`\n[npm 超时 ${String(D/1e3)}s,已终止]`,i.kill()},D);i.on(`error`,e=>{clearTimeout(s),r({code:null,output:`${a}\n${e.message}`})}),i.on(`close`,e=>{clearTimeout(s),r({code:e,output:a})})})}function we(e){let t={};for(let[n,r]of Object.entries(e))r!==void 0&&(n.toLowerCase().startsWith(ye)||(t[n]=r));return t}async function j(e,t,n,r,i){let a={...r??{}};i!==void 0&&(a.NPM_CONFIG_USERCONFIG=i,a.NPM_CONFIG_GLOBALCONFIG=`${i}.global-empty`);let o={...we(process.env),...a};return e===null?A(process.platform===`win32`?`npm.cmd`:`npm`,t,{shell:process.platform===`win32`,cwd:n,env:o}):A(process.execPath,[e,...t],{shell:!1,cwd:n,env:o})}async function Te(e,t){let n=await he(async n=>{let r=await j(e.npmCli,[`view`,t,`version`,`--json`,`--registry`,n],e.pluginsDir,e.env,e.userconfig),i=r.code===0?Ee(r.output):null;if(i===null)throw Error(`npm view 未能取到 ${t} 的版本(源 ${n})`);return i}).catch(()=>null);return n===null?null:n.value}function Ee(e){let t=e=>{try{let t=JSON.parse(e);return typeof t==`string`&&t!==``?t:null}catch{return null}},n=e.trim(),r=t(n);if(r!==null)return r;let i=n.split(/\r?\n/);for(let e=i.length-1;e>=0;e--){let n=(i[e]??``).trim();if(n===``)continue;let r=t(n);if(r!==null)return r}return null}function De(e){s(e,{recursive:!0});let n=t(e,`package.json`);o(n)||p(n,`${JSON.stringify({name:`ezllm-plugins`,private:!0,version:`0.0.0`},null,2)}\n`,`utf8`)}async function Oe(e,t){let r=be(t,e=>n(e));if(r===null)return{ok:!1,message:`不是合法的 npm 包规格,也不是本机目录:${t}`};if(r.kind===`path`){let e=xe(r.path);if(e!==null)return{ok:!1,message:e}}De(e.pluginsDir);let i=r.kind===`npm`?r.version===``?r.name:`${r.name}@${r.version}`:`file:${r.path}`,a=[`install`,`--no-audit`,`--no-fund`,`--save`,i];return e.log(`[plugin-host] npm install ${i}`),await he(async t=>{let n=await j(e.npmCli,[...a,`--registry`,t],e.pluginsDir,e.env,e.userconfig);if(n.code!==0)throw Error(`npm install 失败(退出码 ${String(n.code)},源 ${t}):\n${n.output.trim().slice(-2e3)}`);return{ok:!0,output:n.output.trim()}}).then(({value:e})=>e).catch(e=>({ok:!1,message:e instanceof Error?e.message:`npm install 失败:${String(e)}`}))}async function ke(e,t){let n=k(t);if(n===null)return{ok:!1,message:`不是合法的 npm 包名:${t}`};let r=Se(e.pluginsDir,n);if(!o(r))return{ok:!1,message:`未安装:${n}`};e.log(`[plugin-host] npm uninstall ${n}`);let i=await j(e.npmCli,[`uninstall`,`--no-audit`,`--no-fund`,n],e.pluginsDir,e.env,e.userconfig);if(o(r))try{d(r,{recursive:!0,force:!0}),e.log(i.code===0?`[plugin-host] npm 未清理 ${n} 的目录,已手动删除`:`[plugin-host] npm uninstall 退出码 ${String(i.code)},已手动删除 ${n} 的目录`)}catch(e){return{ok:!1,message:`卸载 ${n} 未完成:npm 退出码 ${String(i.code)},且目录残留删除失败(${e instanceof Error?e.message:String(e)})\n${i.output.trim().slice(-1500)}`}}return i.code!==0&&!o(r)?{ok:!0,output:`npm 退出码 ${String(i.code)}(目录已删除)\n${i.output.trim().slice(-1500)}`}:i.code===0?{ok:!0,output:i.output.trim()}:{ok:!1,message:`npm uninstall 失败(退出码 ${String(i.code)}):\n${i.output.trim().slice(-2e3)}`}}function Ae(e){for(let n of[`README.md`,`readme.md`,`README.MD`,`README`]){let r=t(e,n);if(o(r))try{return c(r,`utf8`).slice(0,204800)}catch{return null}}return null}var je=[{npm:`@ai-sdk/openai-compatible`,protocol:`openai`,path:`chat/completions`},{npm:`@ai-sdk/anthropic`,protocol:`anthropic`,path:`messages`},{npm:`@ai-sdk/openai`,protocol:`openai-responses`,path:`responses`}];function Me(e){return typeof e!=`string`||e===``?null:je.find(t=>t.npm===e)??null}function M(e){return new Proxy(async()=>({data:void 0}),{get(t,n){if(n!==`then`)return M(`${e}.${String(n)}`)},apply(){return Promise.resolve({data:void 0})}})}function Ne(e){let t={auth:{set:async t=>{let n=t?.path?.id??t?.id??t?.providerID,r=typeof n==`string`&&n!==``?n:e.scope?.key,i=t?.body??t?.auth;return typeof r==`string`&&r!==``&&typeof i==`object`&&i&&await e.saveAuth(r,i),{data:!0}},remove:async()=>({data:!0})},tui:{showToast:async()=>({data:!0})},app:{log:async()=>({data:!0})},config:{get:async()=>({data:{provider:{}}})}},n=(e,t)=>new Proxy(e,{get(e,r){if(typeof r==`symbol`)return Reflect.get(e,r);if(r in e){let i=e[r];return typeof i==`object`&&i?n(i,`${t}.${r}`):i}if(r!==`then`)return M(`${t}.${r}`)}});return n(t,`client`)}function Pe(e){return{client:Ne(e),directory:e.directory,worktree:e.directory,project:{id:`ezllm`,worktree:e.directory},serverUrl:new URL(`http://127.0.0.1:4096`),$:()=>{throw Error("插件请求了 shell(`$`),但 ezllm 的插件宿主不提供:当前插件生态没有任何插件使用它;若某个插件真的需要,请提交 issue 说明用途(提供 shell 等于给第三方代码任意命令执行能力)")}}}var Fe=`@magpie-community`,Ie=`ezllmPlugin`;function Le(e){let t=e.exports,n=typeof t==`string`?t:typeof t==`object`&&t?t[`.`]:void 0,r=typeof e.main==`string`?e.main:n;return typeof r==`string`&&r!==``?r:void 0}function N(e,r){let i=t(e,`package.json`);if(!o(i))return null;let a;try{let e=JSON.parse(c(i,`utf-8`));if(typeof e!=`object`||!e||Array.isArray(e))return null;a=e}catch{return null}let s=typeof a.name==`string`&&a.name!==``?a.name:r;if(!s.startsWith(`${Fe}/`)&&a[Ie]!==!0)return null;let l=Le(a);if(l===void 0)return null;let u=a.repository,d=typeof u==`string`?u:typeof u==`object`&&u&&typeof u.url==`string`?String(u.url):``;return{name:s,version:typeof a.version==`string`?a.version:`0.0.0`,main:l,description:typeof a.description==`string`?a.description:``,license:typeof a.license==`string`?a.license:``,repository:d,dir:n(e)}}function P(e){let n=t(e,`node_modules`);if(!o(n))return[];let r;try{r=l(n,{withFileTypes:!0})}catch{return[]}let i=[];for(let e of r){if(e.name.startsWith(`.`))continue;if(!e.name.startsWith(`@`)){let r=N(t(n,e.name),e.name);r!==null&&i.push(r);continue}let r=t(n,e.name),a;try{a=l(r,{withFileTypes:!0})}catch{continue}for(let n of a){let a=N(t(r,n.name),`${e.name}/${n.name}`);a!==null&&i.push(a)}}return i}function Re(e){return t(e.dir,e.main)}function ze(e,t){return P(e).find(e=>e.name===t)}function Be(e){let t=[];for(let[n,r]of Object.entries(e))n==="default"||n.startsWith(`_`)||typeof r==`function`&&t.push([n,r]);return t}function Ve(e,t,n){let r=[],i=e=>{let t=n.find(t=>t.auth?.provider===e)??n.find(t=>t.provider?.id===e);return t===void 0||t.auth===void 0?null:{hooks:t,auth:t.auth}};for(let[n,a]of Object.entries(t.provider??{})){let t=a??{},o=i(n),s=Me(typeof t.npm==`string`?t.npm:void 0);r.push({id:n,name:typeof t.name==`string`&&t.name!==``?t.name:n,source:{package:e.name,version:e.version},protocol:s?.protocol??null,path:s?.path??``,models:Object.keys(t.models??{}),config:t,hooks:o,capabilities:{models:typeof o?.hooks.provider?.models==`function`,usage:typeof o?.auth.usage==`function`,checkin:typeof o?.auth.checkin==`function`,refresh:typeof o?.auth.refresh==`function`,chatHeaders:typeof o?.hooks[`chat.headers`]==`function`}})}return r}async function He(e,t){let n=[],r=[];for(let a of P(e)){let e=Re(a);if(!o(e)){r.push(`${a.name}: 入口不存在(${e})`);continue}let s;try{s=await import(i(e).href)}catch(e){r.push(`${a.name}: 导入失败(${e instanceof Error?e.message:String(e)})`);continue}let c=Be(s);if(c.length===0){r.push(`${a.name}: 没有导出插件工厂函数`);continue}let l=[],u={provider:{}};for(let[e,n]of c)try{let e=await n(Pe(t));l.push(e),typeof e.config==`function`&&await e.config(u)}catch(t){r.push(`${a.name}#${e}: 工厂/config 失败(${t instanceof Error?t.message:String(t)})`)}if(l.length===0)continue;let d=Ve(a,u,l);n.push({package:a.name,version:a.version,providers:d,hooks:l})}return{plugins:n,errors:r}}function F(e){let t=new Set,n=[],r=[...e.errors];for(let i of e.plugins)for(let e of i.providers){if(t.has(e.id)){r.push(`${e.id}: provider id 重复(${e.source.package}),已忽略后一个`);continue}t.add(e.id),n.push(e)}return{providers:n,errors:r}}function Ue(e,t=process.env){let n={host:`127.0.0.1`,port:Number(t.PORT||29772),pluginsDir:t.PLUGIN_DIR||``,authFile:``,apiKey:t.PLUGIN_HOST_KEY||``,nodeDir:t.NODE_DIR||``,mainPort:Number(t.MAIN_PORT||0),skipCheck:!1,help:!1};for(let t=0;t<e.length;t+=1){let r=e[t]??``,i=r.indexOf(`=`),a=r.startsWith(`--`)&&i>=0?r.slice(0,i):r,o=r.startsWith(`--`)&&i>=0?r.slice(i+1):void 0,s=()=>o===void 0?e[++t]:o;switch(a){case`--host`:n.host=s()||n.host;break;case`--port`:n.port=Number(s());break;case`--plugins-dir`:n.pluginsDir=s()||n.pluginsDir;break;case`--auth-file`:n.authFile=s()||n.authFile;break;case`--api-key`:n.apiKey=s()||``;break;case`--node-dir`:n.nodeDir=s()||``;break;case`--skip-check`:n.skipCheck=!0;break;case`--help`:case`-h`:n.help=!0}}return{help:n.help,options:{host:n.host,port:n.port,pluginsDir:n.pluginsDir,authFile:n.authFile,apiKey:n.apiKey,nodeDir:n.nodeDir,mainPort:n.mainPort,skipCheck:n.skipCheck}}}function We(){return[`plugin-host(OpenCode 插件服务:加载订阅插件并暴露成 OpenAI 兼容端点)`,``,`用法: plugin-host [选项]`,` --host HOST 监听地址(默认 127.0.0.1)`,` --port PORT 监听端口(默认 29772;托管形态由 daemon 注入 PORT = 主端口+2)`,` --plugins-dir DIR 插件安装目录(默认 <APP_DIR>/plugins;托管形态由 daemon 注入 PLUGIN_DIR)`,` --auth-file PATH 凭据文件(默认 <APP_DIR>/plugin-auth.json)`,` --api-key KEY 要求客户端携带的 API key(默认不校验;安装面是写操作,建议设)`,` --node-dir DIR 托管 Node 目录(安装面用其中的 node+npm;默认自动探测)`,` --skip-check 跳过启动预检`,``,`端点: GET /health · GET /plugins · GET / · GET /packages · GET /packages/<name>`,` POST /packages({package, version?})· DELETE /packages/<name>`,` GET /p/<id>/v1/models · POST /p/<id>/v1/{chat/completions|messages|responses}`,` GET /login/<id>(登录方式与状态)· POST /login/<id>(起登录,{method?})`,``,`环境变量(托管形态注入): PLUGIN_DIR · PORT · NODE_DIR`,``].join(`
|
|
2
|
+
import{dirname as e,join as t,resolve as n}from"node:path";import{fileURLToPath as r,pathToFileURL as i}from"node:url";import{cwd as a}from"node:process";import{existsSync as o,mkdirSync as s,readFileSync as c,readdirSync as l,renameSync as u,rmSync as d,statSync as f,writeFileSync as p}from"node:fs";import{homedir as m}from"node:os";import{createHash as h,randomBytes as g}from"node:crypto";import{AsyncLocalStorage as _}from"node:async_hooks";import{spawn as ee}from"node:child_process";import{createServer as te}from"node:http";function v(e,...t){console.log(e,...t)}function y(e,...t){console.warn(e,...t)}function ne(e,...t){console.error(e,...t)}function b(e){if(e===void 0)return;let t=e.trim();return t===``?void 0:t}function x(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 b(i.slice(n.length));if(i===t){let t=r[e+1]??``;return t.startsWith(`-`)?void 0:b(t)}}}function S(e,t){return x(e)??b(process.env[t])}var re=65535;function ie(e){let t=b(e);if(t===void 0)return;let n=Number(t);return Number.isInteger(n)&&n>0&&n<=re?n:void 0}var ae=(()=>{let e=S(`app-dir`,`ELLM_APP_DIR`),t=e===void 0?void 0:n(a(),e),r=x(`port`),i=ie(r??b(process.env.ELLM_PORT));r!==void 0&&i===void 0&&y(`--port=${r} 无效(需为 1-65535 的整数),已忽略:本次不生效、也不会写入配置文件。`);let o=S(`node-mirror`,`ELLM_NODE_MIRROR`),s=S(`node-bin`,`ELLM_NODE_BIN`);return t!==void 0&&(process.env.ELLM_APP_DIR=t),o!==void 0&&(process.env.ELLM_NODE_MIRROR=o),s!==void 0&&(process.env.ELLM_NODE_BIN=s),{appDir:t,port:i,nodeMirror:o,nodeBin:s}})(),oe=e(r(import.meta.url)),C;function se(){return t(m(),`.ezllm`)}function w(){return C===void 0&&(C=ae.appDir??se()),C}function ce(){return n(w(),`plugins`)}function le(){return n(w(),`plugin-auth.json`)}function ue(e){try{let n=c(t(e,`package.json`),`utf-8`).replace(/^\uFEFF/,``);return JSON.parse(n)}catch{return null}}function T(t,n){let r=oe;for(let i=0;i<n;i++){if(ue(r)?.name===t)return r;r=e(r)}return null}T(`ezllm`,5)??n(oe,`..`),T(`ellm`,6);function de(e,t){let n=`${e}.${`${process.pid}.${g(6).toString(`hex`)}`}.tmp`;try{p(n,JSON.stringify(t,null,2)+`
|
|
3
|
+
`,`utf-8`),u(n,e)}finally{try{d(n,{force:!0})}catch{}}}var fe=`https://registry.npmmirror.com/`,pe=`https://registry.npmjs.org/`;function me(){return[fe,pe]}async function E(e,t=me()){let n;for(let r of t)try{return{value:await e(r),registry:r}}catch(e){n=e}throw n??Error(`没有任何可用的 npm registry`)}function he(e){try{let t=JSON.parse(c(e,`utf-8`));if(typeof t!=`object`||!t||Array.isArray(t))return{};let n={};for(let[e,r]of Object.entries(t))typeof r==`object`&&r&&!Array.isArray(r)&&(n[e]=r);return n}catch{return{}}}function ge(e,t){de(e,t)}var _e=class{#e;#t;#n=new Map;#r=new Set;constructor(e){this.#e=e,this.#t=he(e)}reload(){this.#t=he(this.#e);for(let e of Object.keys(this.#t))this.#r.delete(e)}generation(e){return this.#n.get(e)??0}locked(e){return this.#r.has(e)}lock(e){this.#r.add(e)}beginLogin(e){let t=this.generation(e)+1;return this.#n.set(e,t),this.#r.delete(e),t}get(e){let t=this.#t[e];return t===void 0?void 0:{...t}}has(e){return e in this.#t}providers(){return Object.keys(this.#t)}save(e,t,n){if(n?.generation!==void 0&&n.generation!==this.generation(e)||this.#r.has(e))return!1;let r={...this.#t,[e]:{...t}};return ge(this.#e,r),this.#t=r,!0}remove(e){let t=this.generation(e)+1;if(this.#n.set(e,t),this.#r.add(e),!(e in this.#t))return t;let n={...this.#t};return delete n[e],ge(this.#e,n),this.#t=n,t}},D=new _;function O(e,t,n){return D.run({providerId:e,generation:t},n)}var ve=/^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(@[a-zA-Z0-9.^~<>=*|xX -]+)?$/,k=3e5,ye=65536,be=`npm_config_`;function A(e){let t=e.trim();if(t===``||!ve.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 xe(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=A(n);return r===null?null:{kind:`npm`,name:r.name,version:r.version}}function Se(e){return o(e)?f(e).isDirectory()?o(t(e,`package.json`))?null:`目录里没有 package.json:${e}`:`不是目录:${e}`:`目录不存在:${e}`}function j(e){return A(e)?.name??null}function Ce(e,n){return t(e,`node_modules`,...n.split(`/`))}function we(n,r){let i=e=>e.endsWith(`npm-cli.js`)&&o(e),a=e=>{for(let n of[[`node_modules`,`npm`,`bin`,`npm-cli.js`],[`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`],[`bin`,`npm-cli.js`]]){let r=t(e,...n);if(i(r))return r}return null};if(n!==``){let e=a(n);if(e!==null)return e;if(i(n))return n;let r=t(n,`npm`,`bin`,`npm-cli.js`);if(i(r))return r}let s=t(e(process.execPath),`node_modules`,`npm`,`bin`,`npm-cli.js`);if(i(s))return s;let c=t(e(process.execPath),`..`,`lib`,`node_modules`,`npm`,`bin`,`npm-cli.js`);if(i(c))return c;if(r!==null){let e=t(r,`node`),n=a(e);if(n!==null)return n;try{for(let n of l(e)){let r=a(t(e,n));if(r!==null)return r}}catch{}}return null}function M(e,t,n){return new Promise(r=>{let i=ee(e,[...t],{shell:n.shell,cwd:n.cwd,windowsHide:!0,...n.env===void 0?{}:{env:n.env}}),a=``,o=e=>{a.length<ye&&(a+=e.toString(`utf8`))};i.stdout.on(`data`,o),i.stderr.on(`data`,o);let s=setTimeout(()=>{a+=`\n[npm 超时 ${String(k/1e3)}s,已终止]`,i.kill()},k);i.on(`error`,e=>{clearTimeout(s),r({code:null,output:`${a}\n${e.message}`})}),i.on(`close`,e=>{clearTimeout(s),r({code:e,output:a})})})}function Te(e){let t={};for(let[n,r]of Object.entries(e))r!==void 0&&(n.toLowerCase().startsWith(be)||(t[n]=r));return t}async function N(e,t,n,r,i){let a={...r??{}};i!==void 0&&(a.NPM_CONFIG_USERCONFIG=i,a.NPM_CONFIG_GLOBALCONFIG=`${i}.global-empty`);let o={...Te(process.env),...a};return e===null?M(process.platform===`win32`?`npm.cmd`:`npm`,t,{shell:process.platform===`win32`,cwd:n,env:o}):M(process.execPath,[e,...t],{shell:!1,cwd:n,env:o})}async function Ee(e,t){let n=await E(async n=>{let r=await N(e.npmCli,[`view`,t,`version`,`--json`,`--registry`,n],e.pluginsDir,e.env,e.userconfig),i=r.code===0?De(r.output):null;if(i===null)throw Error(`npm view 未能取到 ${t} 的版本(源 ${n})`);return i}).catch(()=>null);return n===null?null:n.value}function De(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 Oe(e){s(e,{recursive:!0});let n=t(e,`package.json`);o(n)||p(n,`${JSON.stringify({name:`ezllm-plugins`,private:!0,version:`0.0.0`},null,2)}\n`,`utf8`)}async function ke(e,t){let r=xe(t,e=>n(e));if(r===null)return{ok:!1,message:`不是合法的 npm 包规格,也不是本机目录:${t}`};if(r.kind===`path`){let e=Se(r.path);if(e!==null)return{ok:!1,message:e}}Oe(e.pluginsDir);let i=r.kind===`npm`?r.version===``?r.name:`${r.name}@${r.version}`:`file:${r.path}`,a=[`install`,`--no-audit`,`--no-fund`,`--save`,i];return e.log(`[plugin-host] npm install ${i}`),await E(async t=>{let n=await N(e.npmCli,[...a,`--registry`,t],e.pluginsDir,e.env,e.userconfig);if(n.code!==0)throw Error(`npm install 失败(退出码 ${String(n.code)},源 ${t}):\n${n.output.trim().slice(-2e3)}`);return{ok:!0,output:n.output.trim()}}).then(({value:e})=>e).catch(e=>({ok:!1,message:e instanceof Error?e.message:`npm install 失败:${String(e)}`}))}async function Ae(e,t){let n=j(t);if(n===null)return{ok:!1,message:`不是合法的 npm 包名:${t}`};let r=Ce(e.pluginsDir,n);if(!o(r))return{ok:!1,message:`未安装:${n}`};e.log(`[plugin-host] npm uninstall ${n}`);let i=await N(e.npmCli,[`uninstall`,`--no-audit`,`--no-fund`,n],e.pluginsDir,e.env,e.userconfig);if(o(r))try{d(r,{recursive:!0,force:!0}),e.log(i.code===0?`[plugin-host] npm 未清理 ${n} 的目录,已手动删除`:`[plugin-host] npm uninstall 退出码 ${String(i.code)},已手动删除 ${n} 的目录`)}catch(e){return{ok:!1,message:`卸载 ${n} 未完成:npm 退出码 ${String(i.code)},且目录残留删除失败(${e instanceof Error?e.message:String(e)})\n${i.output.trim().slice(-1500)}`}}return i.code!==0&&!o(r)?{ok:!0,output:`npm 退出码 ${String(i.code)}(目录已删除)\n${i.output.trim().slice(-1500)}`}:i.code===0?{ok:!0,output:i.output.trim()}:{ok:!1,message:`npm uninstall 失败(退出码 ${String(i.code)}):\n${i.output.trim().slice(-2e3)}`}}function je(e){for(let n of[`README.md`,`readme.md`,`README.MD`,`README`]){let r=t(e,n);if(o(r))try{return c(r,`utf8`).slice(0,204800)}catch{return null}}return null}var Me=[{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 Ne(e){return typeof e!=`string`||e===``?null:Me.find(t=>t.npm===e)??null}function Pe(e){return new Proxy(async()=>({data:void 0}),{get(t,n){if(n!==`then`)return Pe(`${e}.${String(n)}`)},apply(){return Promise.resolve({data:void 0})}})}function Fe(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??D.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 Pe(`${t}.${r}`)}});return n(t,`client`)}function Ie(e){return{client:Fe(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 Le=`@magpie-community`,Re=`ezllmPlugin`;function ze(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 Be(e,r){let i=t(e,`package.json`);if(!o(i))return null;let a;try{let e=JSON.parse(c(i,`utf-8`));if(typeof e!=`object`||!e||Array.isArray(e))return null;a=e}catch{return null}let s=typeof a.name==`string`&&a.name!==``?a.name:r;if(!s.startsWith(`${Le}/`)&&a[Re]!==!0)return null;let l=ze(a);if(l===void 0)return null;let u=a.repository,d=typeof u==`string`?u:typeof u==`object`&&u&&typeof u.url==`string`?String(u.url):``;return{name:s,version:typeof a.version==`string`?a.version:`0.0.0`,main:l,description:typeof a.description==`string`?a.description:``,license:typeof a.license==`string`?a.license:``,repository:d,dir:n(e)}}function P(e){let n=t(e,`node_modules`);if(!o(n))return[];let r;try{r=l(n,{withFileTypes:!0})}catch{return[]}let i=[];for(let e of r){if(e.name.startsWith(`.`))continue;if(!e.name.startsWith(`@`)){let r=Be(t(n,e.name),e.name);r!==null&&i.push(r);continue}let r=t(n,e.name),a;try{a=l(r,{withFileTypes:!0})}catch{continue}for(let n of a){let a=Be(t(r,n.name),`${e.name}/${n.name}`);a!==null&&i.push(a)}}return i}function Ve(e){return t(e.dir,e.main)}function F(e,t){return P(e).find(e=>e.name===t)}function He(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 Ue(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=Ne(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 We(e,t){let n=[],r=[];for(let a of P(e)){let e=Ve(a);if(!o(e)){r.push(`${a.name}: 入口不存在(${e})`);continue}let s;try{s=await import(i(e).href)}catch(e){r.push(`${a.name}: 导入失败(${e instanceof Error?e.message:String(e)})`);continue}let c=He(s);if(c.length===0){r.push(`${a.name}: 没有导出插件工厂函数`);continue}let l=[],u={provider:{}};for(let[e,n]of c)try{let e=await n(Ie(t));l.push(e),typeof e.config==`function`&&await e.config(u)}catch(t){r.push(`${a.name}#${e}: 工厂/config 失败(${t instanceof Error?t.message:String(t)})`)}if(l.length===0)continue;let d=Ue(a,u,l);n.push({package:a.name,version:a.version,providers:d,hooks:l})}return{plugins:n,errors:r}}function I(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 Ge(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 Ke(){return[`plugin-host(OpenCode 插件服务:加载订阅插件并暴露成 OpenAI 兼容端点)`,``,`用法: plugin-host [选项]`,` --host HOST 监听地址(默认 127.0.0.1)`,` --port PORT 监听端口(默认 29772;托管形态由 daemon 注入 PORT = 主端口+2)`,` --plugins-dir DIR 插件安装目录(默认 <APP_DIR>/plugins;托管形态由 daemon 注入 PLUGIN_DIR)`,` --auth-file PATH 凭据文件(默认 <APP_DIR>/plugin-auth.json)`,` --api-key KEY 要求客户端携带的 API key(默认不校验;安装面是写操作,建议设)`,` --node-dir DIR 托管 Node 目录(安装面用其中的 node+npm;默认自动探测)`,` --skip-check 跳过启动预检`,``,`端点: GET /health · GET /plugins · GET / · GET /packages · GET /packages/<name>`,` POST /packages({package, version?})· DELETE /packages/<name>`,` GET /p/<id>/v1/models · POST /p/<id>/v1/{chat/completions|messages|responses}`,` GET /login/<id>(登录方式与状态)· POST /login/<id>(起登录,{method?})`,` DELETE /login/<id>(退出登录:删本机凭据 + 清会话)`,``,`环境变量(托管形态注入): PLUGIN_DIR · PORT · NODE_DIR`,``].join(`
|
|
4
4
|
`)+`
|
|
5
|
-
`}var Ge=3e5,Ke=864e5,qe=15e3,Je=3e4,I=new class{renewing=new Map;failed=new Map};function Ye(e){let t=`${String(e?.refresh??``)}\0${String(e?.access??``)}`;return h(`sha256`).update(t).digest(`hex`).slice(0,16)}function Xe(e){let t=e.refreshLead;return typeof t==`number`&&Number.isFinite(t)&&t>=0?Math.min(t,Ke):Ge}function Ze(e,t){if(typeof e.refresh!=`function`||t.type!==`oauth`)return 0;let n=Number(t.expires);return Number.isFinite(n)&&n>0?Math.max(1,n-Xe(e)):0}var Qe=class{#e;#t;#n;constructor(e,t,n){this.#e=e,this.#t=t,this.#n=n}current(){return this.#n.store.get(this.#e)}getAuth=async()=>this.current();async ensureFresh(){let e=this.#n.now?.()??Date.now(),t=this.current();if(t===void 0)return;let n=Ze(this.#t,t);if(!n||e<n)return;let r=this.#n.registry??I,i=`${this.#e}\0${Ye(t)}`,a=r.failed.get(i);if(a!==void 0&&e-a<Je)return;let o=r.renewing.get(i);o===void 0&&(o=this.#r(t,i).finally(()=>r.renewing.delete(i)),r.renewing.set(i,o));let s;await Promise.race([o,new Promise(e=>{s=setTimeout(e,qe)})]),clearTimeout(s)}async#r(e,t){let n=this.#n.registry??I,r=this.#t.refresh;if(r!==void 0)try{let i=await r({...e},this.#n.providerInfo);typeof i==`object`&&i&&this.#n.store.save(this.#e,{...e,...i}),n.failed.delete(t),this.#n.log?.(`[plugin-host] 凭据已续期:${this.#e}`)}catch(e){n.failed.set(t,this.#n.now?.()??Date.now()),this.#n.log?.(`[plugin-host] 凭据续期失败:${this.#e}(${e instanceof Error?e.message:String(e)})`)}}},$e=class extends Error{status;detail;constructor(e,t,n){super(t),this.name=`HttpError`,this.status=e,this.detail=n}};function L(e,t){return{error:{message:e,type:t}}}function R(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function et(e){let t=e.search(/[?#]/);return t>=0?e.slice(0,t):e}function z(e,t,n){if(e.headersSent){try{e.end()}catch{}return}e.writeHead(t,{"Content-Type":`application/json`}),e.end(JSON.stringify(n))}function B(e,t,n){z(e,t,{detail:n})}function V(e=process.env){return Number(e.PLUGIN_HOST_MAX_BODY||33554432)}function H(e,t){return new Promise((n,r)=>{let i=[],a=0;e.on(`data`,n=>{if(a+=n.length,a>t){e.destroy(),r(new $e(413,`request body too large`,L(`request body too large`,`invalid_request_error`)));return}i.push(n)}),e.on(`end`,()=>{n(Buffer.concat(i))}),e.on(`error`,r)})}async function*tt(e){if(!e)return;let t=e.getReader();try{for(;;){let{done:e,value:n}=await t.read();if(e)break;n&&(yield Buffer.isBuffer(n)?n:Buffer.from(n))}}finally{try{t.releaseLock()}catch{}}}var nt=new Set([`host`,`connection`,`keep-alive`,`proxy-authenticate`,`proxy-authorization`,`te`,`trailer`,`transfer-encoding`,`upgrade`,`content-length`,`authorization`,`x-api-key`,`x-plugin-session`]),rt=`x-plugin-session`;function it(e,t){return t===void 0||t===``?{}:e===`anthropic`?{"x-api-key":t}:{authorization:`Bearer ${t}`}}function at(e){let t={};for(let[n,r]of Object.entries(e.headers)){let e=n.toLowerCase();nt.has(e)||r!==void 0&&(t[e]=Array.isArray(r)?r.join(`, `):r)}return t}function ot(e,t){let n=new Headers;for(let[r,i]of Object.entries(it(e.provider.protocol,t.apiKey)))n.set(r,i);for(let[e,r]of Object.entries(t.headers??{}))n.set(e,String(r));for(let[t,r]of Object.entries(at(e.req)))n.set(t,r);return{headers:n,baseURL:typeof t.baseURL==`string`?t.baseURL:``,fetchFn:typeof t.fetch==`function`?t.fetch:fetch}}async function st(e,t,n){let r=t.provider.hooks?.hooks[`chat.headers`];if(typeof r!=`function`)return;let i={headers:{}};try{await r({sessionID:t.sessionId,agent:`build`,model:{id:t.model,providerID:t.provider.id,api:{id:t.model,npm:t.provider.config.npm}},provider:{source:`custom`,info:t.provider.config,options:t.provider.config.options??{}},message:{id:``,sessionID:t.sessionId,role:`user`,time:{created:Date.now()},agent:`build`,model:{providerID:t.provider.id,modelID:t.model}}},i)}catch(e){n(`[plugin-host] ${t.provider.id}: chat.headers 失败(${e instanceof Error?e.message:String(e)})`);return}for(let[t,n]of Object.entries(i.headers))e.set(t,String(n))}async function ct(e,t){return H(e,t)}async function lt(e,t){let{provider:n,credentials:r}=e;if(n.hooks===null)return{kind:`error`,status:501,message:`插件 ${n.source.package} 声明了 provider「${n.id}」但没有实现 auth 钩子(无登录/转发能力)`};if(await r.ensureFresh(),r.current()===void 0)return{kind:`error`,status:401,message:`${n.id} 尚未登录:请在管理台完成登录后重试`};let i=n.hooks.auth.loader;if(i===void 0)return{kind:`error`,status:501,message:`插件 ${n.source.package} 未实现 auth.loader,无法转发`};let a;try{a=await i(r.getAuth,n.config)??{}}catch(e){return{kind:`error`,status:502,message:`插件 loader 失败:${e instanceof Error?e.message:String(e)}`}}if(a.baseURL===void 0||a.baseURL===``)return{kind:`error`,status:502,message:`插件 ${n.source.package} 未给出 baseURL(登录态可能已失效)`};let o=ot(e,a);await st(o.headers,e,t);let s=`${o.baseURL.replace(/\/+$/,``)}/${e.path.replace(/^\/+/,``)}`;try{return{kind:`ok`,response:await o.fetchFn(s,{method:e.method,headers:o.headers,body:e.body.length>0?new Uint8Array(e.body):void 0})}}catch(e){return{kind:`error`,status:502,message:`上游请求失败:${e instanceof Error?e.message:String(e)}`}}}async function ut(e,t,n){let r={};e.headers.forEach((e,t)=>{let n=t.toLowerCase();n!==`content-encoding`&&n!==`content-length`&&n!==`transfer-encoding`&&(r[n]=e)}),t(e.status,r);for await(let t of tt(e.body))n(t)}function dt(e){return(e.headers.get(`content-type`)??``).includes(`text/event-stream`)}function ft(e,t){try{let t=JSON.parse(e.toString(`utf8`));if(R(t)&&typeof t.model==`string`&&t.model!==``)return t.model}catch{}return t.models[0]??``}function pt(e,t){let n=e.apiKey;if(!n)return;let r=t.headers.authorization,i=t.headers[`x-api-key`],a=``;if(r&&r.startsWith(`Bearer `)&&(a=r.slice(7).trim()),!a&&i&&(a=String(i)),a!==n)throw Error(`invalid api key`)}var mt=[`127.0.0.1`,`localhost`,`[::1]`,`::1`],U=`拒绝的 `;function ht(e){let t=e.trim();if(t===``)return``;if(t.startsWith(`[`)){let e=t.indexOf(`]`);return e<0?t.toLowerCase():t.slice(0,e+1).toLowerCase()}let n=t.lastIndexOf(`:`);return(n<0?t:t.slice(0,n)).toLowerCase()}function gt(e){return e===`0.0.0.0`||e===`::`||e===``}function _t(e,t){if(gt(e.host))return;let n=[...mt,e.host.trim().toLowerCase()],r=ht(String(t.headers.host??``));if(!n.includes(r))throw Error(`${U}Host:${r===``?`(空)`:r}(只接受本机回环地址)`);let i=t.headers.origin;if(typeof i==`string`&&i!==``&&i!==`null`){let e=(()=>{try{return new URL(i).hostname.toLowerCase()}catch{return i.toLowerCase()}})();if(!n.includes(e))throw Error(`拒绝的 Origin:${e}`)}}function W(e,t){return{id:e.id,name:e.name,package:e.source.package,version:e.source.version,protocol:e.protocol,basePath:e.protocol===null?null:`/${e.id}/v1`,models:e.models,signedIn:t,hooks:e.capabilities}}function vt(e,t){let{providers:n}=F(e.loadResult());z(t,200,{status:`ok`,platform:process.platform,node:process.version.replace(/^v/,``),mode:`plugin-host (forwarding + package install)`,port:e.options.port,pluginsDir:e.options.pluginsDir,authFile:e.options.authFile,providers:n.length})}function yt(e,t){let n=e.loadResult(),{providers:r,errors:i}=F(n);z(t,200,{status:`ok`,pluginsDir:e.options.pluginsDir,packages:n.plugins.map(e=>({package:e.package,version:e.version,providers:e.providers.length})),providers:r.map(t=>W(t,e.auth.has(t.id))),errors:i})}function G(e,t){return F(e.loadResult()).providers.filter(e=>e.source.package===t)}function bt(e,t){let n=P(e.options.pluginsDir).map(t=>{let n=G(e,t.name);return{package:t.name,version:t.version,description:t.description,license:t.license,repository:t.repository,loaded:n.length>0,providers:n.map(t=>({id:t.id,name:t.name,protocol:t.protocol,models:t.models.length,signedIn:e.auth.has(t.id)}))}});z(t,200,{status:`ok`,pluginsDir:e.options.pluginsDir,count:n.length,packages:n,errors:e.loadResult().errors,canInstall:e.install!==void 0&&e.reload!==void 0})}function xt(e,t,n){let r=ze(e.options.pluginsDir,t);if(r===void 0)return B(n,404,L(`未安装:${t}`,`not_found`));let i=G(e,r.name);z(n,200,{status:`ok`,package:r.name,version:r.version,description:r.description,license:r.license,repository:r.repository,dir:r.dir,entry:r.main,readme:e.install===void 0?null:Ae(r.dir),loaded:i.length>0,providers:i.map(t=>W(t,e.auth.has(t.id))),errors:e.loadResult().errors.filter(e=>e.includes(r.name))})}async function K(e){if(e.reload===void 0)return[];try{return await e.reload()}catch(t){return e.log(`[plugin-host] 重载失败:${t instanceof Error?t.message:String(t)}`),[`重载失败:${t instanceof Error?t.message:String(t)}`]}}function q(e){let t=e.options.mainPort;if(!Number.isFinite(t)||t<=0){e.log(`[plugin-host] 未由 ezllm 托管(MAIN_PORT 未注入),装/卸后的重启需手动执行`);return}let n=`http://127.0.0.1:${String(t)}/api/service/plugins/restart`;fetch(n,{method:`POST`}).then(t=>{t.ok||e.log(`[plugin-host] 自请重启失败:HTTP ${String(t.status)}(插件已装,重启需手动)`)}).catch(t=>{e.log(`[plugin-host] 自请重启失败:${t instanceof Error?t.message:String(t)}(插件已装,重启需手动)`)})}var J=Promise.resolve();function Y(e){let t=J.then(e,e);return J=t.then(()=>void 0,()=>void 0),t}async function St(e,t,n){if(e.install===void 0||e.reload===void 0)return B(n,501,L(`本进程未启用安装能力(未注入 npm 依赖)`,`not_implemented`));let r;try{r=await H(t,V())}catch(e){return B(n,413,L(e instanceof Error?e.message:String(e),`invalid_request_error`))}let i;try{i=r.length===0?{}:JSON.parse(r.toString(`utf8`))}catch{return B(n,400,L(`body 不是合法 JSON`,`invalid_request_error`))}if(!R(i)||typeof i.package!=`string`||i.package.trim()===``)return B(n,400,L(`缺少 package(npm 包名,可带 @版本)`,`invalid_request_error`));let a=i.package.trim(),o=typeof i.version==`string`&&i.version.trim()!==``?i.version.trim():``,s=o===``?a:`${k(a)??a}@${o}`;if(O(s)===null)return B(n,400,L(`不是合法的 npm 包规格:${s}`,`invalid_request_error`));e.log(`[plugin-host] 安装 ${s}`);let c=await Oe(e.install,s);if(!c.ok)return e.log(`[plugin-host] ✗ 安装失败:${s}`),B(n,502,L(c.message,`install_error`));let l=await K(e),u=k(s)??s,d=ze(e.options.pluginsDir,u);e.log(`[plugin-host] ✓ 已安装 ${u}${d?`@${d.version}`:``}`),z(n,200,{status:`ok`,package:u,version:d?.version??``,loaded:G(e,u).length>0,providers:G(e,u).map(e=>({id:e.id,protocol:e.protocol,models:e.models.length})),npmOutput:c.output.slice(-2e3),reloadErrors:l,restartRequested:e.options.mainPort>0}),q(e)}async function Ct(e,t,n){if(e.install===void 0||e.reload===void 0)return B(n,501,L(`本进程未启用卸载能力(未注入 npm 依赖)`,`not_implemented`));e.log(`[plugin-host] 卸载 ${t}`);let r=await ke(e.install,t);if(!r.ok)return B(n,r.message.startsWith(`未安装`)?404:502,L(r.message,r.message.startsWith(`未安装`)?`not_found`:`install_error`));let i=await K(e);e.log(`[plugin-host] ✓ 已卸载 ${t}`),z(n,200,{status:`ok`,package:t,npmOutput:r.output.slice(-2e3),reloadErrors:i,restartRequested:e.options.mainPort>0}),q(e)}var wt=36e5;async function Tt(e,t){let n=e.versionCache??=new Map,r=Date.now(),i=[];for(let t of P(e.options.pluginsDir)){let a=null;if(e.install!==void 0){let i=n.get(t.name);i!==void 0&&r-i.at<wt?a=i.latest:(a=await Te(e.install,t.name),a!==null&&n.set(t.name,{latest:a,at:r}))}i.push({package:t.name,current:t.version,latest:a,updateAvailable:a!==null&&a!==t.version})}z(t,200,{status:`ok`,canCheck:e.install!==void 0,packages:i})}function X(e){let t=e.hooks?.hooks.auth?.methods;return Array.isArray(t)?t:[]}function Z(e,t){let n=e.signIns?.get(t.id);return{provider:t.id,signedIn:e.auth.has(t.id),methods:X(t).map(e=>({type:e.type,label:e.label??``})),state:n?.state??`idle`,...n===void 0?{}:{url:n.url,instructions:n.instructions,...n.error===void 0?{}:{error:n.error},...n.accountId===void 0?{}:{accountId:n.accountId}}}}function Et(e,t,n){z(n,200,Z(e,t))}function Dt(e,t){let n=e.when;if(n!==void 0){let e=typeof n.key==`string`?t[n.key]:void 0;if(e===void 0)return!1;let r=e===n.value;if(n.op===`eq`?!r:r)return!1}return!(typeof e.condition==`function`&&!e.condition(t))}function Ot(e,t){let n=[];for(let r of e.prompts??[])typeof r.key!=`string`||r.key===``||r.key in t||Dt(r,t)&&n.push(r);return n}function kt(e){return e.map(e=>({type:typeof e.type==`string`?e.type:`text`,key:typeof e.key==`string`?e.key:``,message:typeof e.message==`string`?e.message:``,placeholder:typeof e.placeholder==`string`?e.placeholder:``,options:Array.isArray(e.options)?e.options:[]}))}async function At(e,t,n,r,i,a){let o=e.signIns??=new Map,s=typeof n.placeholder==`string`?n.placeholder:``,c=n.label??n.type;if(i===``)return z(a,200,{...Z(e,t),needKey:!0,placeholder:s,label:c,inputs:r});let l=n.authorize;if(typeof l!=`function`){let n=Object.keys(r).length>0?{metadata:r}:{};return e.auth.save(t.id,{type:`api`,key:i,...n}),o.set(t.id,{state:`success`,url:``,instructions:``}),e.log(`[plugin-host] ✓ ${t.id} 登录完成(API key 已保存)`),z(a,200,Z(e,t))}let u;try{let e=await l(r);u=R(e)?e:{}}catch(e){return B(a,502,L(`起登录流程失败:${e instanceof Error?e.message:String(e)}`,`upstream_error`))}if(u.type!==`success`){let n=typeof u.error==`string`&&u.error!==``?u.error:`登录未完成`;return o.set(t.id,{state:`failed`,url:``,instructions:``,error:n}),e.log(`[plugin-host] ✗ ${t.id} 登录失败:${n}`),z(a,200,Z(e,t))}if(`refresh`in u){let n={...u};delete n.type,delete n.provider,e.auth.save(t.id,{...n,type:`oauth`})}else{let n=typeof u.key==`string`&&u.key!==``?u.key:i,a={...r,...R(u.metadata)?u.metadata:{}};e.auth.save(t.id,{type:`api`,key:n,...Object.keys(a).length>0?{metadata:a}:{}})}let d=typeof u.accountId==`string`?u.accountId:``;return o.set(t.id,{state:`success`,url:``,instructions:``,...d===``?{}:{accountId:d}}),e.log(`[plugin-host] ✓ ${t.id} 登录完成${d===``?``:`(${d})`}`),z(a,200,Z(e,t))}async function jt(e,t,n,r){let i=X(t);if(i.length===0)return B(r,400,L(`${t.id} 未声明登录方式(auth.methods),无法登录;该插件可能不需要登录`,`invalid_request_error`));let a={};try{let e=await H(n,V());if(e.length>0){let t=JSON.parse(e.toString(`utf-8`));a=R(t)?t:{}}}catch{return B(r,400,L(`请求体不是合法 JSON`,`invalid_request_error`))}let o=e.signIns??=new Map;if(o.get(t.id)?.state===`pending`)return z(r,200,Z(e,t));let s=typeof a.method==`string`?a.method:``;if(s===``&&i.length>1)return z(r,200,{...Z(e,t),needMethod:!0});let c=s===``?i[0]:i.find(e=>e.label===s);if(c===void 0)return B(r,400,L(`未知登录方式:${s}(可选:${i.map(e=>e.label??`(未命名)`).join(`、`)})`,`invalid_request_error`));let l={};if(R(a.inputs))for(let[e,t]of Object.entries(a.inputs))typeof t==`string`&&(l[e]=t);let u=typeof a.key==`string`?a.key.trim():``,d=Ot(c,l);if(d.length>0)return z(r,200,{...Z(e,t),needInputs:kt(d),inputs:l});if(c.type===`api`)return await At(e,t,c,l,u,r);let f=c.authorize;if(typeof f!=`function`)return B(r,400,L(`登录方式「${c.label??c.type}」缺少 authorize 实现(插件声明与实现不一致)`,`invalid_request_error`));let p;try{p=await f(c.prompts?.length?l:void 0)}catch(e){return B(r,502,L(`起登录流程失败:${e instanceof Error?e.message:String(e)}`,`upstream_error`))}let m=p.callback;if(typeof m!=`function`)return B(r,400,L(`登录流程没有 callback(插件声明与实现不一致)`,`invalid_request_error`));let h={state:`pending`,url:typeof p.url==`string`?p.url:``,instructions:typeof p.instructions==`string`?p.instructions:``};o.set(t.id,h);let g=s===``?c.label??c.type:s;return e.log(`[plugin-host] ▶ ${t.id} 登录已发起(${g}${h.url===``?`,读取本机凭据`:`,等待浏览器完成`})`),(async()=>{try{let n=await m(),r=R(n)?n:{};if(r.type===`success`){e.auth.save(t.id,{...r,type:`oauth`});let n=typeof r.accountId==`string`?r.accountId:``;h.state=`success`,h.accountId=n,e.log(`[plugin-host] ✓ ${t.id} 登录完成${n===``?``:`(${n})`}`)}else{let n=typeof r.error==`string`&&r.error!==``?r.error:`登录未完成`;h.state=`failed`,h.error=n,e.log(`[plugin-host] ✗ ${t.id} 登录失败:${n}`)}}catch(n){h.state=`failed`,h.error=n instanceof Error?n.message:String(n),e.log(`[plugin-host] ✗ ${t.id} 登录异常:${h.error}`)}})(),z(r,200,Z(e,t))}function Mt(e,t){let{providers:n}=F(e.loadResult());z(t,200,{service:`ezllm-plugin-host`,endpoints:[`GET /health`,`GET /plugins`,`GET /p/<id>/v1/models`,`GET /p/<id>/usage`,`POST /p/<id>/v1/{chat/completions|messages|responses}`,`GET /packages`,`GET /packages/updates`,`GET /packages/<name>`,`POST /packages`,`DELETE /packages/<name>`,`GET /login/<id>`,`POST /login/<id>`],providers:n.map(e=>({id:e.id,apiBase:e.protocol===null?null:`/p/${e.id}/v1`,protocol:e.protocol}))})}function Nt(e){let t=/^\/p\/([^/]+)\/v1(?:\/(.*))?$/.exec(e);if(t===null)return null;let n=decodeURIComponent(t[1]??``);return n===``?null:{id:n,rest:(t[2]??``).replace(/\/+$/,``)}}function Q(e,t){return F(e.loadResult()).providers.find(e=>e.id===t)}function $(e,t){if(t.hooks===null)throw Error(`provider「${t.id}」没有 auth 钩子(插件声明与实现不一致)`);return new Qe(t.id,t.hooks.auth,{store:e.auth,providerInfo:t.config,log:e.log})}async function Pt(e,t,n){let r=t.models,i=!1,a=t.hooks?.hooks.provider?.models;if(typeof a==`function`&&t.hooks!==null&&e.auth.has(t.id)){let n=$(e,t);await n.ensureFresh();let o=n.current();if(o!==void 0)try{let e=await a(t.config,{auth:o}),n=Object.keys(e??{});n.length>0&&(r=n,i=!0)}catch(n){e.log(`[plugin-host] ${t.id}: provider.models 失败,回落声明表(${n instanceof Error?n.message:String(n)})`)}}z(n,200,{object:`list`,provider:t.id,fromHook:i,data:r.map(e=>({id:e,object:`model`,owned_by:t.id}))})}async function Ft(e,t,n){let r=t.hooks?.auth.usage;if(t.hooks===null||t.capabilities.usage===!1||typeof r!=`function`)return B(n,400,L(`该插件未实现额度查询`,`invalid_request_error`));let i=$(e,t);await i.ensureFresh();let a;try{a=await r(i.getAuth,t.config)}catch(r){let i=r instanceof Error?r.message:String(r);return e.log(`[plugin-host] ✗ ${t.id} 额度查询失败:${i}`),B(n,502,L(`额度查询失败:${i}`,`upstream_error`))}z(n,200,a)}async function It(e,t,n,r,i){if(t.protocol===null)return B(i,501,L(`${t.id} 的协议未知(插件未声明可识别的 npm),不可转发`,`not_implemented`));if(n!==t.path)return B(i,404,L(`${t.id} 只提供 /p/${t.id}/v1/${t.path}(插件声明的协议是 ${t.protocol})`,`not_found`));let a;try{a=await ct(r,V())}catch(e){return B(i,413,L(e instanceof Error?e.message:String(e),`invalid_request_error`))}let o=r.headers[rt],s=typeof o==`string`?o:``,c=ft(a,t),l=Date.now();e.log(`[plugin-host] ▶ ${t.id} ${n} model=${c} bytes=${String(a.length)}${s?` session=${s}`:``}`);let u=await lt({provider:t,credentials:$(e,t),model:c,path:n,method:`POST`,req:r,body:a,sessionId:s},e.log);if(u.kind===`error`)return e.log(`[plugin-host] ✗ ${t.id} ${n} ${String(u.status)} ${u.message}`),B(i,u.status,L(u.message,u.status===401?`auth_error`:`upstream_error`));let d=u.response,f=dt(d);e.log(`[plugin-host] ◀ ${t.id} ${n} status=${String(d.status)} ${f?`stream`:`json`} ${String(Date.now()-l)}ms`),await ut(d,(e,t)=>{i.headersSent||i.writeHead(e,t)},e=>{i.write(e)}),i.end()}async function Lt(e,t,n){let r=et(t.url??`/`),i=(t.method??`GET`).toUpperCase();try{if(_t(e.options,t),pt(e.options,t),i===`GET`&&r===`/health`)return vt(e,n);if(i===`GET`&&r===`/plugins`)return yt(e,n);if(i===`GET`&&r===`/`)return Mt(e,n);if(r===`/packages`)return i===`GET`?bt(e,n):i===`POST`?await Y(()=>St(e,t,n)):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`));if(r===`/packages/updates`)return i===`GET`?await Tt(e,n):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`));let a=/^\/packages\/(.+)$/.exec(r);if(a!==null){let t=decodeURIComponent(a[1]??``);return t===``?B(n,404,L(`未找到:${i} ${r}`,`not_found`)):i===`GET`?xt(e,t,n):i===`DELETE`?await Y(()=>Ct(e,t,n)):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`))}let o=/^\/login\/(.+)$/.exec(r);if(o!==null){let a=decodeURIComponent(o[1]??``),s=a===``?void 0:Q(e,a);return s===void 0?B(n,404,L(`未加载 provider:${a}`,`not_found`)):i===`GET`?Et(e,s,n):i===`POST`?await jt(e,s,t,n):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`))}let s=/^\/p\/([^/]+)\/usage$/.exec(r);if(s!==null){let t=decodeURIComponent(s[1]??``),a=Q(e,t);return a===void 0?B(n,404,L(`未加载 provider:${t}`,`not_found`)):i===`GET`?await Ft(e,a,n):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`))}let c=Nt(r);if(c!==null){let a=Q(e,c.id);return a===void 0?B(n,404,L(`未加载 provider:${c.id}`,`not_found`)):i===`GET`&&c.rest===`models`?await Pt(e,a,n):i===`POST`?await It(e,a,c.rest,t,n):B(n,405,L(`不支持的方法:${i} ${r}`,`invalid_request_error`))}B(n,404,L(`未找到:${i} ${r}`,`not_found`))}catch(e){let t=e instanceof Error?e.message:String(e),r=t===`invalid api key`?401:t.startsWith(U)?403:500;B(n,r,L(t,r===500?`internal_error`:`invalid_request_error`))}}function Rt(e){return te((t,n)=>{Lt(e,t,n).catch(t=>{e.log(`[fatal] handler error: ${t instanceof Error&&t.stack?t.stack:String(t)}`);try{n.headersSent||n.writeHead(500,{"Content-Type":`application/json`}),n.end(JSON.stringify({detail:L(String(t),`internal_error`)}))}catch{}})})}function zt(e){return{...e,pluginsDir:e.pluginsDir===``?se():e.pluginsDir,authFile:e.authFile===``?ce():e.authFile}}function Bt(e,t,n){let{providers:r,errors:i}=F(t);_(`==== 插件宿主预检 ====`),_(`平台 : ${process.platform}`),_(`Node : ${process.version}`),_(`插件目录 : ${e.pluginsDir}`),_(`凭据文件 : ${e.authFile}`),_(`已装插件 : ${String(t.plugins.length)} 个(provider ${String(r.length)} 个)`);for(let e of r){let t=e.protocol??`未知协议(不可用)`,r=n(e.id)?`已登录`:`未登录`;_(` - ${e.id}(${e.name}): ${t} | ${String(e.models.length)} 模型 | ${r}`)}for(let e of i)v(` ! ${e}`);r.length===0&&v(" 未加载到任何 provider——用 `npm install <plugin> --prefix` 装进插件目录后重启"),_(`======================`)}async function Vt(e=process.argv.slice(2),t=process.env){let{options:n,help:r}=Ue(e,t);if(r)return process.stdout.write(We()),null;let i=zt(n),a=new ge(i.authFile),o={plugins:[],errors:[]},s=()=>o,c={directory:i.pluginsDir,saveAuth:(e,t)=>{a.save(e,t),_(`[plugin-host] 凭据已保存:${e}`)}},l=async()=>{let e=await He(i.pluginsDir,c);return o=e,e.errors};try{await l()}catch(e){v(`[plugin-host] 加载插件失败:${e instanceof Error?e.message:String(e)}`)}i.skipCheck||Bt(i,s(),e=>a.has(e));let u=Ce(i.nodeDir,w()),d={pluginsDir:i.pluginsDir,npmCli:u,log:e=>_(e)};_(`[plugin-host] ✅ 监听 http://${i.host}:${String(i.port)}`),_(`[plugin-host] GET /health`),_(`[plugin-host] GET /plugins`),_(`[plugin-host] GET /packages · POST /packages · DELETE /packages/<name>`),_(`[plugin-host] GET /login/<id> · POST /login/<id>`),_(`[plugin-host] 安装能力: ${u===null?`npm 未找到(仅可运行已装插件)`:`已启用(${u})`}`),i.apiKey&&_(`[plugin-host] 鉴权已启用(API key 已设置)`),_(`[plugin-host] 按 Ctrl+C 退出。`);let f=Rt({options:i,auth:a,loadResult:s,reload:l,install:d,log:e=>_(e)});return await new Promise((e,t)=>{f.once(`error`,t),f.listen(i.port,i.host,()=>{f.off(`error`,t),e()})}),_(`[plugin-host] listening on ${i.host}:${String(i.port)}`),{server:f,options:i,auth:a,loadResult:s,reload:l}}var Ht=process.argv[1];Ht!==void 0&&import.meta.url===i(n(Ht)).href&&Vt().catch(e=>{ne(`[plugin-host] 启动失败:${e instanceof Error?e.message:String(e)}`),process.exitCode=1});export{Vt as main};
|
|
5
|
+
`}var qe=3e5,Je=864e5,Ye=15e3,Xe=3e4,L=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 R(e){let t=`${String(e?.refresh??``)}\0${String(e?.access??``)}`;return h(`sha256`).update(t).digest(`hex`).slice(0,16)}function Ze(e){let t=e.refreshLead;return typeof t==`number`&&Number.isFinite(t)&&t>=0?Math.min(t,Je):qe}function Qe(e,t){if(typeof e.refresh!=`function`||t.type!==`oauth`)return 0;let n=Number(t.expires);return Number.isFinite(n)&&n>0?Math.max(1,n-Ze(e)):0}var $e=class{#e;#t;#n;constructor(e,t,n){this.#e=e,this.#t=t,this.#n=n}current(){return this.#n.store.get(this.#e)}generation(){return this.#n.store.generation(this.#e)}getAuth=async()=>this.current();async ensureFresh(){let e=this.#n.now?.()??Date.now(),t=this.current();if(t===void 0)return;let n=Qe(this.#t,t);if(!n||e<n)return;let r=this.#n.registry??L,i=`${this.#e}\0${R(t)}`,a=r.failed.get(i);if(a!==void 0){if(e-a<Xe)return;r.failed.delete(i)}let o=r.renewing.get(i);o===void 0&&(o=this.#r(t,i).finally(()=>r.renewing.delete(i)),r.renewing.set(i,o));let s;await Promise.race([o,new Promise(e=>{s=setTimeout(e,Ye)})]),clearTimeout(s)}async#r(e,t){let n=this.#n.registry??L,r=this.#t.refresh;if(r===void 0)return;let i=this.#n.store.generation(this.#e);try{let a=await O(this.#e,i,()=>r({...e},this.#n.providerInfo));if(typeof a!=`object`||!a||Array.isArray(a)){n.failed.delete(t);return}let o=this.current();if(o===void 0||this.#n.store.generation(this.#e)!==i){n.failed.delete(t),this.#n.log?.(`[plugin-host] 已丢弃过期的续期结果:${this.#e}`);return}if(R(o)!==R(e)){n.failed.delete(t),this.#n.log?.(`[plugin-host] 凭据已由插件续期:${this.#e}`);return}if(!this.#n.store.save(this.#e,{...e,...a},{generation:i})){n.failed.delete(t),this.#n.log?.(`[plugin-host] 已丢弃过期的续期结果:${this.#e}`);return}n.failed.delete(t),this.#n.log?.(`[plugin-host] 凭据已续期:${this.#e}`)}catch(e){n.failed.set(t,this.#n.now?.()??Date.now()),this.#n.log?.(`[plugin-host] 凭据续期失败:${this.#e}(${e instanceof Error?e.message:String(e)})`)}}},et=class extends Error{status;detail;constructor(e,t,n){super(t),this.name=`HttpError`,this.status=e,this.detail=n}};function z(e,t){return{error:{message:e,type:t}}}function B(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function tt(e){let t=e.search(/[?#]/);return t>=0?e.slice(0,t):e}function V(e,t,n){if(e.headersSent){try{e.end()}catch{}return}e.writeHead(t,{"Content-Type":`application/json`}),e.end(JSON.stringify(n))}function H(e,t,n){V(e,t,{detail:n})}function U(e=process.env){return Number(e.PLUGIN_HOST_MAX_BODY||33554432)}function W(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 et(413,`request body too large`,z(`request body too large`,`invalid_request_error`)));return}i.push(n)}),e.on(`end`,()=>{n(Buffer.concat(i))}),e.on(`error`,r)})}async function*nt(e){if(!e)return;let t=e.getReader();try{for(;;){let{done:e,value:n}=await t.read();if(e)break;n&&(yield Buffer.isBuffer(n)?n:Buffer.from(n))}}finally{try{t.releaseLock()}catch{}}}var rt=new Set([`host`,`connection`,`keep-alive`,`proxy-authenticate`,`proxy-authorization`,`te`,`trailer`,`transfer-encoding`,`upgrade`,`content-length`,`authorization`,`x-api-key`,`x-plugin-session`]),it=`x-plugin-session`;function at(e,t){return t===void 0||t===``?{}:e===`anthropic`?{"x-api-key":t}:{authorization:`Bearer ${t}`}}function ot(e){let t={};for(let[n,r]of Object.entries(e.headers)){let e=n.toLowerCase();rt.has(e)||r!==void 0&&(t[e]=Array.isArray(r)?r.join(`, `):r)}return t}function st(e,t){let n=new Headers;for(let[r,i]of Object.entries(at(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(ot(e.req)))n.set(t,r);return{headers:n,baseURL:typeof t.baseURL==`string`?t.baseURL:``,fetchFn:typeof t.fetch==`function`?t.fetch:fetch}}async function ct(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 W(e,t)}async function ut(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 O(n.id,a,()=>i(r.getAuth,n.config))??{}}catch(e){return{kind:`error`,status:502,message:`插件 loader 失败:${e instanceof Error?e.message:String(e)}`}}if(o.baseURL===void 0||o.baseURL===``)return{kind:`error`,status:502,message:`插件 ${n.source.package} 未给出 baseURL(登录态可能已失效)`};let s=st(e,o);await O(n.id,a,()=>ct(s.headers,e,t));let c=`${s.baseURL.replace(/\/+$/,``)}/${e.path.replace(/^\/+/,``)}`;try{return{kind:`ok`,response:await O(n.id,a,()=>s.fetchFn(c,{method:e.method,headers:s.headers,body:e.body.length>0?new Uint8Array(e.body):void 0}))}}catch(e){return{kind:`error`,status:502,message:`上游请求失败:${e instanceof Error?e.message:String(e)}`}}}async function dt(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 nt(e.body))n(t)}function ft(e){return(e.headers.get(`content-type`)??``).includes(`text/event-stream`)}function pt(e,t){try{let t=JSON.parse(e.toString(`utf8`));if(B(t)&&typeof t.model==`string`&&t.model!==``)return t.model}catch{}return t.models[0]??``}function mt(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 ht=[`127.0.0.1`,`localhost`,`[::1]`,`::1`],G=`拒绝的 `;function gt(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 _t(e){return e===`0.0.0.0`||e===`::`||e===``}function vt(e,t){if(_t(e.host))return;let n=[...ht,e.host.trim().toLowerCase()],r=gt(String(t.headers.host??``));if(!n.includes(r))throw Error(`${G}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 K(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:Y(e).length>0,hooks:e.capabilities}}function yt(e,t){let{providers:n}=I(e.loadResult());V(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 bt(e,t){let n=e.loadResult(),{providers:r,errors:i}=I(n);V(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=>K(t,e.auth.has(t.id))),errors:i})}function q(e,t){return I(e.loadResult()).providers.filter(e=>e.source.package===t)}function xt(e,t){let n=P(e.options.pluginsDir).map(t=>{let n=q(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:Y(t).length>0}))}});V(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 St(e,t,n){let r=F(e.options.pluginsDir,t);if(r===void 0)return H(n,404,z(`未安装:${t}`,`not_found`));let i=q(e,r.name);V(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:je(r.dir),loaded:i.length>0,providers:i.map(t=>K(t,e.auth.has(t.id))),errors:e.loadResult().errors.filter(e=>e.includes(r.name))})}async function J(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 Ct(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 wt=Promise.resolve();function Tt(e){let t=wt.then(e,e);return wt=t.then(()=>void 0,()=>void 0),t}async function Et(e,t,n){if(e.install===void 0||e.reload===void 0)return H(n,501,z(`本进程未启用安装能力(未注入 npm 依赖)`,`not_implemented`));let r;try{r=await W(t,U())}catch(e){return H(n,413,z(e instanceof Error?e.message:String(e),`invalid_request_error`))}let i;try{i=r.length===0?{}:JSON.parse(r.toString(`utf8`))}catch{return H(n,400,z(`body 不是合法 JSON`,`invalid_request_error`))}if(!B(i)||typeof i.package!=`string`||i.package.trim()===``)return H(n,400,z(`缺少 package(npm 包名,可带 @版本)`,`invalid_request_error`));let a=i.package.trim(),o=typeof i.version==`string`&&i.version.trim()!==``?i.version.trim():``,s=o===``?a:`${j(a)??a}@${o}`;if(A(s)===null)return H(n,400,z(`不是合法的 npm 包规格:${s}`,`invalid_request_error`));e.log(`[plugin-host] 安装 ${s}`);let c=await ke(e.install,s);if(!c.ok)return e.log(`[plugin-host] ✗ 安装失败:${s}`),H(n,502,z(c.message,`install_error`));let l=await J(e),u=j(s)??s,d=F(e.options.pluginsDir,u);e.log(`[plugin-host] ✓ 已安装 ${u}${d?`@${d.version}`:``}`),V(n,200,{status:`ok`,package:u,version:d?.version??``,loaded:q(e,u).length>0,providers:q(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}),Ct(e)}async function Dt(e,t,n){if(e.install===void 0||e.reload===void 0)return H(n,501,z(`本进程未启用卸载能力(未注入 npm 依赖)`,`not_implemented`));e.log(`[plugin-host] 卸载 ${t}`);let r=await Ae(e.install,t);if(!r.ok)return H(n,r.message.startsWith(`未安装`)?404:502,z(r.message,r.message.startsWith(`未安装`)?`not_found`:`install_error`));let i=await J(e);e.log(`[plugin-host] ✓ 已卸载 ${t}`),V(n,200,{status:`ok`,package:t,npmOutput:r.output.slice(-2e3),reloadErrors:i,restartRequested:e.options.mainPort>0}),Ct(e)}var Ot=36e5;async function kt(e,t){let n=e.versionCache??=new Map,r=Date.now(),i=[];for(let t of P(e.options.pluginsDir)){let a=null;if(e.install!==void 0){let i=n.get(t.name);i!==void 0&&r-i.at<Ot?a=i.latest:(a=await Ee(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})}V(t,200,{status:`ok`,canCheck:e.install!==void 0,packages:i})}function Y(e){let t=e.hooks?.hooks.auth?.methods;return Array.isArray(t)?t:[]}function X(e,t){let n=e.signIns?.get(t.id);return{provider:t.id,signedIn:e.auth.has(t.id),methods:Y(t).map(e=>({type:e.type,label:e.label??``})),state:n?.state??`idle`,...n===void 0?{}:{url:n.url,instructions:n.instructions,...n.error===void 0?{}:{error:n.error},...n.accountId===void 0?{}:{accountId:n.accountId}}}}function At(e,t,n){V(n,200,X(e,t))}function jt(e,t,n){let r=e.auth.has(t.id);e.auth.remove(t.id),L.clear(t.id),e.signIns?.delete(t.id),r&&e.log(`[plugin-host] ⏏ ${t.id} 已退出登录`),V(n,200,X(e,t))}function Mt(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 Nt(e,t){let n=[];for(let r of e.prompts??[])typeof r.key!=`string`||r.key===``||r.key in t||Mt(r,t)&&n.push(r);return n}function Pt(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 Z(e,t,n){e.auth.generation(t)===n&&e.auth.get(t)===void 0&&e.auth.lock(t)}async function Ft(e,t,n,r,i,a){let o=e.signIns??=new Map,s=typeof n.placeholder==`string`?n.placeholder:``,c=n.label??n.type;if(i===``)return V(a,200,{...X(e,t),needKey:!0,placeholder:s,label:c,inputs:r});let l=e.auth.beginLogin(t.id),u=()=>e.auth.generation(t.id)===l,d=n.authorize;if(typeof d!=`function`){let n={type:`api`,key:i,...Object.keys(r).length>0?{metadata:r}:{}};return!e.auth.save(t.id,n,{generation:l})||!u()?V(a,200,X(e,t)):(o.set(t.id,{state:`success`,url:``,instructions:``}),e.log(`[plugin-host] ✓ ${t.id} 登录完成(API key 已保存)`),V(a,200,X(e,t)))}let f;try{let e=await O(t.id,l,()=>d(r));f=B(e)?e:{}}catch(n){return Z(e,t.id,l),H(a,502,z(`起登录流程失败:${n instanceof Error?n.message:String(n)}`,`upstream_error`))}if(f.type!==`success`){let n=typeof f.error==`string`&&f.error!==``?f.error:`登录未完成`;return Z(e,t.id,l),u()&&o.set(t.id,{state:`failed`,url:``,instructions:``,error:n}),e.log(`[plugin-host] ✗ ${t.id} 登录失败:${n}`),V(a,200,X(e,t))}let p;if(`refresh`in f){let e={...f};delete e.type,delete e.provider,p={...e,type:`oauth`}}else{let e=typeof f.key==`string`&&f.key!==``?f.key:i,t={...r,...B(f.metadata)?f.metadata:{}};p={type:`api`,key:e,...Object.keys(t).length>0?{metadata:t}:{}}}if(!e.auth.save(t.id,p,{generation:l})||!u())return V(a,200,X(e,t));let m=typeof f.accountId==`string`?f.accountId:``;return o.set(t.id,{state:`success`,url:``,instructions:``,...m===``?{}:{accountId:m}}),e.log(`[plugin-host] ✓ ${t.id} 登录完成${m===``?``:`(${m})`}`),V(a,200,X(e,t))}async function It(e,t,n,r){let i=Y(t);if(i.length===0)return H(r,400,z(`${t.id} 未声明登录方式(auth.methods),无法登录;该插件可能不需要登录`,`invalid_request_error`));let a={};try{let e=await W(n,U());if(e.length>0){let t=JSON.parse(e.toString(`utf-8`));a=B(t)?t:{}}}catch{return H(r,400,z(`请求体不是合法 JSON`,`invalid_request_error`))}let o=e.signIns??=new Map;if(o.get(t.id)?.state===`pending`)return V(r,200,X(e,t));let s=typeof a.method==`string`?a.method:``;if(s===``&&i.length>1)return V(r,200,{...X(e,t),needMethod:!0});let c=s===``?i[0]:i.find(e=>e.label===s);if(c===void 0)return H(r,400,z(`未知登录方式:${s}(可选:${i.map(e=>e.label??`(未命名)`).join(`、`)})`,`invalid_request_error`));let l={};if(B(a.inputs))for(let[e,t]of Object.entries(a.inputs))typeof t==`string`&&(l[e]=t);let u=typeof a.key==`string`?a.key.trim():``,d=Nt(c,l);if(d.length>0)return V(r,200,{...X(e,t),needInputs:Pt(d),inputs:l});if(c.type===`api`)return await Ft(e,t,c,l,u,r);let f=c.authorize;if(typeof f!=`function`)return H(r,400,z(`登录方式「${c.label??c.type}」缺少 authorize 实现(插件声明与实现不一致)`,`invalid_request_error`));let p={state:`pending`,url:``,instructions:``};o.set(t.id,p);let m=e.auth.beginLogin(t.id),h=()=>o.get(t.id)===p,g;try{g=await O(t.id,m,()=>f(c.prompts?.length?l:void 0))}catch(n){return h()&&o.delete(t.id),Z(e,t.id,m),H(r,502,z(`起登录流程失败:${n instanceof Error?n.message:String(n)}`,`upstream_error`))}if(!h())return V(r,200,X(e,t));let _=g.callback;if(typeof _!=`function`)return o.delete(t.id),Z(e,t.id,m),H(r,400,z(`登录流程没有 callback(插件声明与实现不一致)`,`invalid_request_error`));p.url=typeof g.url==`string`?g.url:``,p.instructions=typeof g.instructions==`string`?g.instructions:``;let ee=s===``?c.label??c.type:s;return e.log(`[plugin-host] ▶ ${t.id} 登录已发起(${ee}${p.url===``?`,读取本机凭据`:`,等待浏览器完成`})`),(async()=>{try{let n=await O(t.id,m,()=>_());if(!h())return;let r=B(n)?n:{};if(r.type===`success`){if(!e.auth.save(t.id,{...r,type:`oauth`},{generation:m})){e.log(`[plugin-host] ${t.id} 登录结果已过期(已登出或被新登录取代),丢弃`);return}let n=typeof r.accountId==`string`?r.accountId:``;p.state=`success`,p.accountId=n,e.log(`[plugin-host] ✓ ${t.id} 登录完成${n===``?``:`(${n})`}`)}else{let n=typeof r.error==`string`&&r.error!==``?r.error:`登录未完成`;p.state=`failed`,p.error=n,Z(e,t.id,m),e.log(`[plugin-host] ✗ ${t.id} 登录失败:${n}`)}}catch(n){if(!h())return;p.state=`failed`,p.error=n instanceof Error?n.message:String(n),Z(e,t.id,m),e.log(`[plugin-host] ✗ ${t.id} 登录异常:${p.error}`)}})(),V(r,200,X(e,t))}function Lt(e,t){let{providers:n}=I(e.loadResult());V(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 Rt(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 I(e.loadResult()).providers.find(e=>e.id===t)}function $(e,t){if(t.hooks===null)throw Error(`provider「${t.id}」没有 auth 钩子(插件声明与实现不一致)`);return new $e(t.id,t.hooks.auth,{store:e.auth,providerInfo:t.config,log:e.log})}async function zt(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 O(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)})`)}}V(n,200,{object:`list`,provider:t.id,fromHook:i,data:r.map(e=>({id:e,object:`model`,owned_by:t.id}))})}async function Bt(e,t,n){let r=t.hooks?.auth.usage;if(t.hooks===null||t.capabilities.usage===!1||typeof r!=`function`)return H(n,400,z(`该插件未实现额度查询`,`invalid_request_error`));let i=$(e,t);await i.ensureFresh();let a;try{a=await O(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}`),H(n,502,z(`额度查询失败:${i}`,`upstream_error`))}V(n,200,a)}async function Vt(e,t,n,r,i){if(t.protocol===null)return H(i,501,z(`${t.id} 的协议未知(插件未声明可识别的 npm),不可转发`,`not_implemented`));if(n!==t.path)return H(i,404,z(`${t.id} 只提供 /p/${t.id}/v1/${t.path}(插件声明的协议是 ${t.protocol})`,`not_found`));let a;try{a=await lt(r,U())}catch(e){return H(i,413,z(e instanceof Error?e.message:String(e),`invalid_request_error`))}let o=r.headers[it],s=typeof o==`string`?o:``,c=pt(a,t),l=Date.now();e.log(`[plugin-host] ▶ ${t.id} ${n} model=${c} bytes=${String(a.length)}${s?` session=${s}`:``}`);let u=await ut({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}`),H(i,u.status,z(u.message,u.status===401?`auth_error`:`upstream_error`));let d=u.response,f=ft(d);e.log(`[plugin-host] ◀ ${t.id} ${n} status=${String(d.status)} ${f?`stream`:`json`} ${String(Date.now()-l)}ms`),await dt(d,(e,t)=>{i.headersSent||i.writeHead(e,t)},e=>{i.write(e)}),i.end()}async function Ht(e,t,n){let r=tt(t.url??`/`),i=(t.method??`GET`).toUpperCase();try{if(vt(e.options,t),mt(e.options,t),i===`GET`&&r===`/health`)return yt(e,n);if(i===`GET`&&r===`/plugins`)return bt(e,n);if(i===`GET`&&r===`/`)return Lt(e,n);if(r===`/packages`)return i===`GET`?xt(e,n):i===`POST`?await Tt(()=>Et(e,t,n)):H(n,405,z(`不支持的方法:${i} ${r}`,`invalid_request_error`));if(r===`/packages/updates`)return i===`GET`?await kt(e,n):H(n,405,z(`不支持的方法:${i} ${r}`,`invalid_request_error`));let a=/^\/packages\/(.+)$/.exec(r);if(a!==null){let t=decodeURIComponent(a[1]??``);return t===``?H(n,404,z(`未找到:${i} ${r}`,`not_found`)):i===`GET`?St(e,t,n):i===`DELETE`?await Tt(()=>Dt(e,t,n)):H(n,405,z(`不支持的方法:${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?H(n,404,z(`未加载 provider:${a}`,`not_found`)):i===`GET`?At(e,s,n):i===`POST`?await It(e,s,t,n):i===`DELETE`?jt(e,s,n):H(n,405,z(`不支持的方法:${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?H(n,404,z(`未加载 provider:${t}`,`not_found`)):i===`GET`?await Bt(e,a,n):H(n,405,z(`不支持的方法:${i} ${r}`,`invalid_request_error`))}let c=Rt(r);if(c!==null){let a=Q(e,c.id);return a===void 0?H(n,404,z(`未加载 provider:${c.id}`,`not_found`)):i===`GET`&&c.rest===`models`?await zt(e,a,n):i===`POST`?await Vt(e,a,c.rest,t,n):H(n,405,z(`不支持的方法:${i} ${r}`,`invalid_request_error`))}H(n,404,z(`未找到:${i} ${r}`,`not_found`))}catch(e){let t=e instanceof Error?e.message:String(e),r=t===`invalid api key`?401:t.startsWith(G)?403:500;H(n,r,z(t,r===500?`internal_error`:`invalid_request_error`))}}function Ut(e){return te((t,n)=>{Ht(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:z(String(t),`internal_error`)}))}catch{}})})}function Wt(e){return{...e,pluginsDir:e.pluginsDir===``?ce():e.pluginsDir,authFile:e.authFile===``?le():e.authFile}}function Gt(e,t,n){let{providers:r,errors:i}=I(t);v(`==== 插件宿主预检 ====`),v(`平台 : ${process.platform}`),v(`Node : ${process.version}`),v(`插件目录 : ${e.pluginsDir}`),v(`凭据文件 : ${e.authFile}`),v(`已装插件 : ${String(t.plugins.length)} 个(provider ${String(r.length)} 个)`);for(let e of r){let t=e.protocol??`未知协议(不可用)`,r=n(e.id)?`已登录`:`未登录`;v(` - ${e.id}(${e.name}): ${t} | ${String(e.models.length)} 模型 | ${r}`)}for(let e of i)y(` ! ${e}`);r.length===0&&y(" 未加载到任何 provider——用 `npm install <plugin> --prefix` 装进插件目录后重启"),v(`======================`)}async function Kt(e=process.argv.slice(2),t=process.env){let{options:n,help:r}=Ge(e,t);if(r)return process.stdout.write(Ke()),null;let i=Wt(n),a=new _e(i.authFile),o={plugins:[],errors:[]},s=()=>o,c={directory:i.pluginsDir,saveAuth:(e,t)=>{let n=D.getStore(),r=n?.providerId===e?{generation:n.generation}:void 0;if(!a.save(e,t,r)){v(`[plugin-host] 已忽略过期/登出后的凭据写入:${e}`);return}v(`[plugin-host] 凭据已保存:${e}`)}},l=async()=>{let e=await We(i.pluginsDir,c);return o=e,e.errors};try{await l()}catch(e){y(`[plugin-host] 加载插件失败:${e instanceof Error?e.message:String(e)}`)}i.skipCheck||Gt(i,s(),e=>a.has(e));let u=we(i.nodeDir,w()),d={pluginsDir:i.pluginsDir,npmCli:u,log:e=>v(e)};v(`[plugin-host] ✅ 监听 http://${i.host}:${String(i.port)}`),v(`[plugin-host] GET /health`),v(`[plugin-host] GET /plugins`),v(`[plugin-host] GET /packages · POST /packages · DELETE /packages/<name>`),v(`[plugin-host] GET /login/<id> · POST /login/<id> · DELETE /login/<id>`),v(`[plugin-host] 安装能力: ${u===null?`npm 未找到(仅可运行已装插件)`:`已启用(${u})`}`),i.apiKey&&v(`[plugin-host] 鉴权已启用(API key 已设置)`),v(`[plugin-host] 按 Ctrl+C 退出。`);let f=Ut({options:i,auth:a,loadResult:s,reload:l,install:d,log:e=>v(e)});return await new Promise((e,t)=>{f.once(`error`,t),f.listen(i.port,i.host,()=>{f.off(`error`,t),e()})}),v(`[plugin-host] listening on ${i.host}:${String(i.port)}`),{server:f,options:i,auth:a,loadResult:s,reload:l}}var qt=process.argv[1];qt!==void 0&&import.meta.url===i(n(qt)).href&&Kt().catch(e=>{ne(`[plugin-host] 启动失败:${e instanceof Error?e.message:String(e)}`),process.exitCode=1});export{Kt as main};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{A as e,F as t,I as n,M as r,O as i,P as a,Pt as o,Q as s,Tt as c,V as l,Wt as u,Z as d,a as f,at as ee,bt as p,et as m,fn as h,ht as te,it as g,j as _,k as v,mt as y,nt as b,sn as ne,vt as x,w as S}from"./router-
|
|
1
|
+
import{A as e,F as t,I as n,M as r,O as i,P as a,Pt as o,Q as s,Tt as c,V as l,Wt as u,Z as d,a as f,at as ee,bt as p,et as m,fn as h,ht as te,it as g,j as _,k as v,mt as y,nt as b,sn as ne,vt as x,w as S}from"./router-tvNif7bs.js";import{t as C}from"./useManagerApi-Wm9Nz3_p.js";import{t as w}from"./providers-BuzQNH1W.js";import{n as T,t as E}from"./useCopyToast-BeWZ6lSF.js";import{n as D,t as re}from"./useQuickRangeFilter-DQQ8QAr5.js";import{d as O,f as k,i as A,l as j,n as M,r as N}from"./format-CPPf8IfM.js";x();function ie(e){let{loadError:t,run:n}=T(e),r=c(null);function i(e){let t=async()=>{let i=await n(e);return i?r.value===t&&(r.value=null):r.value=t,i};return t}function a(e){n(r.value??e)}return{loadError:t,track:i,retry:a}}l(),x(),u();var ae={class:`flex-col-gap`},oe={class:`kv-grid`},se={class:`kv-label`},ce={class:`kv-value`},le=[`onClick`],ue={class:`kv-item kv-span-2`},de={class:`kv-value`},fe={key:1,class:`kv-unit`},pe={class:`kv-item`},me={class:`kv-value`},he={class:`kv-unit`},ge={class:`kv-item`},_e={class:`kv-value`},ve={class:`flex-between card-header-wrap`},ye={class:`flex-gap-8`},be={class:`usage-grid`},xe={class:`usage-cell`},Se={class:`usage-num orange`},Ce={class:`usage-cell`},we={class:`usage-num blue`},Te={class:`usage-cell`},Ee={class:`usage-num green`},De={class:`usage-cell`},Oe={class:`usage-num purple`},ke={class:`usage-cell`},Ae={class:`usage-num purple`},je={key:0,class:`usage-cell`},Me={class:`usage-num gray`},Ne={class:`usage-cell`},Pe={class:`usage-num blue`},Fe={class:`usage-cell`},Ie=[`title`],Le={class:`usage-cell`},Re=[`title`],ze={key:0},Be=[`onClick`],Ve=[`title`],He=[`title`],P=n({__name:`DashboardView`,setup(n){let l=w(),{copyWithToast:u}=E(),{loadError:x,track:T,retry:P}=ie(),Ue={local:`本机 API 地址`,lan:`局域网地址`,other:`API 地址`},F=c(null),I=c(!0),L=c(``),{rangeKey:R,dateRange:z,onRangeChange:We}=re(()=>B(),`today`),Ge=()=>{R.value=``,B()},Ke=i(()=>{let e=new Set;for(let t of l.suppliers)for(let n of t.models)e.add(n.modelName);if(F.value?.models)for(let t of F.value.models)e.add(t.model);return[...e].sort()}),qe=D(),B=T(async()=>{let e=qe();I.value=!0;try{let t=new URLSearchParams;L.value&&t.set(`model`,L.value),z.value?.length===2&&(t.set(`startDate`,z.value[0]),t.set(`endDate`,z.value[1]));let n=t.toString(),r=await f.get(`/api/usage${n?`?${n}`:``}`);if(!e())return;F.value=r}catch(t){if(!e())return;throw t}finally{e()&&(I.value=!1)}}),Je=D(),V=T(async()=>{let e=Je();try{await Promise.all([B(),l.loadProviders()])}catch(t){if(!e())return;throw t}finally{e()&&(I.value=!1)}}),Ye=()=>P(V),H=window.location.hostname,U=Number(window.location.port)||null,W=i(()=>A({protocol:window.location.protocol,hostname:H,port:U})),G=c(null),K=c(null),q=c(!1),J=c(!0),Y=c(Date.now()),X=c(null),Z=c([]),Q=i(()=>X.value??U),Xe=e=>A({protocol:`http:`,hostname:e,port:Q.value}),Ze=i(()=>H===`127.0.0.1`||H===`::1`||H===`localhost`?`local`:Z.value.includes(H)?`lan`:`other`),$=i(()=>Q.value?Z.value.map(Xe).filter(e=>e!==W.value):[]),Qe=i(()=>{let e=Q.value?Xe(`127.0.0.1`):``;return e!==``&&e!==W.value?e:``}),$e=i(()=>{let e=[];return Ze.value===`lan`?e.push({label:`局域网地址`,values:[W.value,...$.value]}):(e.push({label:Ue[Ze.value],values:[W.value]}),$.value.length>0&&e.push({label:`局域网地址`,values:$.value})),Qe.value!==``&&e.push({label:`本机 API 地址`,values:[Qe.value]}),e.map((t,n)=>({...t,span2:e.length%2==1&&n===e.length-1}))}),et=async()=>{let[e,t]=await Promise.allSettled([C.processes(),f.get(`/api/status`)]);if(J.value=!1,e.status===`fulfilled`&&(G.value=e.value[0]??null),t.status===`rejected`){K.value=null,q.value=!0;return}let n=t.value?.masterKey;K.value=typeof n==`string`?n:null,q.value=typeof n!=`string`;let r=t.value?.listenPort;X.value=typeof r==`number`&&r>0?r:null;let i=t.value?.lanAddresses;Z.value=Array.isArray(i)?i.filter(e=>typeof e==`string`):[],Y.value=Date.now()},tt=e=>{if(!e.startedAt)return`-`;let t=Math.max(0,Math.floor((Y.value-e.startedAt)/1e3)),n=Math.floor(t/86400);t-=n*86400;let r=Math.floor(t/3600);t-=r*3600;let i=Math.floor(t/60);return t-=i*60,[n?`${n}天`:``,r?`${r}时`:``,i?`${i}分`:``,`${t}秒`].filter(Boolean).join(` `)},nt=i(()=>q.value?`获取失败,请刷新重试`:K.value===null?`-`:`未设置(/v1 免鉴权)`),rt;return d(()=>{V(),et(),rt=window.setInterval(et,1e4)}),s(()=>window.clearInterval(rt)),(n,i)=>{let s=g(`el-button`),c=g(`el-alert`),l=g(`el-card`),d=g(`el-option`),f=g(`el-select`),C=g(`el-date-picker`),w=g(`el-table-column`),T=g(`el-table`),E=g(`el-empty`),D=ee(`loading`);return m(),r(`div`,ae,[o(x)?(m(),e(c,{key:0,title:o(x),type:`error`,"show-icon":``,closable:!1},{default:y(()=>[t(s,{size:`small`,onClick:Ye},{default:y(()=>[...i[4]||=[a(`重试`,-1)]]),_:1})]),_:1},8,[`title`])):_(``,!0),t(l,{shadow:`never`},{header:y(()=>[...i[5]||=[v(`span`,{class:`fw-600`},`服务信息`,-1)]]),default:y(()=>[te((m(),r(`div`,oe,[(m(!0),r(S,null,b($e.value,e=>(m(),r(`div`,{key:e.label+e.values[0],class:ne([`kv-item`,{"kv-span-2":e.span2}])},[v(`div`,se,h(e.label),1),v(`div`,ce,[(m(!0),r(S,null,b(e.values,e=>(m(),r(`div`,{key:e},[v(`span`,{class:`copy-text`,onClick:t=>o(u)(e)},h(e),9,le)]))),128))])],2))),128)),v(`div`,ue,[i[6]||=v(`div`,{class:`kv-label`},`API 密钥`,-1),v(`div`,de,[K.value?(m(),r(`span`,{key:0,class:`copy-text`,onClick:i[0]||=e=>o(u)(K.value)},h(K.value),1)):_(``,!0),K.value?(m(),r(`span`,fe,`(/v1 需 Bearer 认证)`)):(m(),r(S,{key:2},[a(h(nt.value),1)],64))])]),v(`div`,pe,[i[9]||=v(`div`,{class:`kv-label`},`运行状态`,-1),v(`div`,me,[G.value?(m(),r(S,{key:0},[i[7]||=v(`span`,{class:`status-dot running`},null,-1),i[8]||=a(`运行中 `,-1),v(`span`,he,`PID `+h(G.value.pid??`-`),1)],64)):(m(),r(S,{key:1},[a(`-`)],64))])]),v(`div`,ge,[i[10]||=v(`div`,{class:`kv-label`},`运行时长`,-1),v(`div`,_e,h(G.value?tt(G.value):`-`),1)])])),[[D,I.value||J.value]])]),_:1}),F.value?(m(),e(l,{key:1,shadow:`never`},{header:y(()=>[v(`div`,ve,[i[12]||=v(`span`,{class:`fw-600`},`用量统计`,-1),v(`div`,ye,[t(f,{modelValue:L.value,"onUpdate:modelValue":i[1]||=e=>L.value=e,placeholder:`全部模型`,clearable:``,filterable:``,style:{width:`220px`},onChange:o(B)},{default:y(()=>[(m(!0),r(S,null,b(Ke.value,t=>(m(),e(d,{key:t,label:t,value:t},null,8,[`label`,`value`]))),128))]),_:1},8,[`modelValue`,`onChange`]),t(f,{modelValue:o(R),"onUpdate:modelValue":i[2]||=e=>p(R)?R.value=e:null,placeholder:`时间范围`,clearable:``,style:{width:`130px`},onChange:o(We)},{default:y(()=>[t(d,{label:`今天`,value:`today`}),t(d,{label:`昨天`,value:`yesterday`}),t(d,{label:`前天`,value:`beforeYesterday`}),t(d,{label:`近三天`,value:`last3days`}),t(d,{label:`本周`,value:`week`}),t(d,{label:`本月`,value:`month`})]),_:1},8,[`modelValue`,`onChange`]),t(C,{modelValue:o(z),"onUpdate:modelValue":i[3]||=e=>p(z)?z.value=e:null,type:`daterange`,"range-separator":`至`,"start-placeholder":`开始日期`,"end-placeholder":`结束日期`,"value-format":`YYYY-MM-DD`,style:{width:`260px`},onChange:Ge},null,8,[`modelValue`]),t(s,{loading:I.value,onClick:o(B)},{default:y(()=>[...i[11]||=[a(`刷新`,-1)]]),_:1},8,[`loading`,`onClick`])])])]),default:y(()=>[v(`div`,be,[v(`div`,xe,[v(`div`,Se,h(o(O)(F.value.total.total_tokens)),1),i[13]||=v(`div`,{class:`usage-label`},`总 Token`,-1)]),v(`div`,Ce,[v(`div`,we,h(o(O)(F.value.total.input_tokens)),1),i[14]||=v(`div`,{class:`usage-label`},`输入 Token`,-1)]),v(`div`,Te,[v(`div`,Ee,h(o(O)(F.value.total.output_tokens)),1),i[15]||=v(`div`,{class:`usage-label`},`输出 Token`,-1)]),v(`div`,De,[v(`div`,Oe,h(o(O)(F.value.total.cache_read_tokens)),1),i[16]||=v(`div`,{class:`usage-label`},`缓存读取`,-1)]),v(`div`,ke,[v(`div`,Ae,h(F.value.total.cache_rate==null?`-`:F.value.total.cache_rate+`%`),1),i[17]||=v(`div`,{class:`usage-label`},`缓存命中率`,-1)]),F.value.logStorageMB==null?_(``,!0):(m(),r(`div`,je,[v(`div`,Me,h(F.value.logStorageMB),1),i[18]||=v(`div`,{class:`usage-label`},`数据目录占用 (MB)`,-1)])),v(`div`,Ne,[v(`div`,Pe,h(o(j)(F.value.total.avg_duration)),1),i[19]||=v(`div`,{class:`usage-label`},`平均耗时`,-1)]),v(`div`,Fe,[v(`div`,{class:`usage-num blue`,title:o(N)},h(o(j)(F.value.total.avg_ttft)),9,Ie),i[20]||=v(`div`,{class:`usage-label`},`平均首 Token`,-1)]),v(`div`,Le,[v(`div`,{class:`usage-num green`,title:o(M)},h(o(k)(F.value.total.tps)),9,Re),i[21]||=v(`div`,{class:`usage-label`},`平均生成速度`,-1)])]),F.value.models?.length?(m(),r(`div`,ze,[i[22]||=v(`div`,{class:`section-title`},`模型用量`,-1),t(T,{data:F.value.models,stripe:``,class:`full-width`},{default:y(()=>[t(w,{prop:`model`,label:`模型`,"min-width":`200`},{default:y(({row:e})=>[v(`span`,{class:`copy-text`,onClick:t=>o(u)(e.model)},h(e.model),9,Be)]),_:1}),t(w,{label:`调用次数`,width:`90`,align:`center`},{default:y(({row:e})=>[a(h(e.calls),1)]),_:1}),t(w,{label:`平均耗时`,width:`110`,align:`center`},{default:y(({row:e})=>[a(h(o(j)(e.avg_duration)),1)]),_:1}),t(w,{label:`速度`,width:`130`,align:`center`},{default:y(({row:e})=>[v(`div`,{title:o(M)},h(o(k)(e.tps)),9,Ve),e.avg_ttft==null?_(``,!0):(m(),r(`div`,{key:0,class:`cell-sub`,title:o(N)},` 首 `+h(o(j)(e.avg_ttft)),9,He))]),_:1}),t(w,{label:`总 Token`,width:`140`,align:`center`},{default:y(({row:e})=>[a(h(o(O)(e.total_tokens)),1)]),_:1}),t(w,{label:`输入 Token`,width:`140`,align:`center`},{default:y(({row:e})=>[a(h(o(O)(e.input_tokens)),1)]),_:1}),t(w,{label:`输出 Token`,width:`140`,align:`center`},{default:y(({row:e})=>[a(h(o(O)(e.output_tokens)),1)]),_:1}),t(w,{label:`缓存读取`,width:`130`,align:`center`},{default:y(({row:e})=>[a(h(o(O)(e.cache_read_tokens)),1)]),_:1}),t(w,{label:`缓存命中率`,width:`110`,align:`center`},{default:y(({row:e})=>[a(h(e.cache_rate==null?`-`:e.cache_rate+`%`),1)]),_:1})]),_:1},8,[`data`])])):(m(),e(E,{key:1,description:`暂无调用记录`,"image-size":60}))]),_:1})):_(``,!0)])}}});export{P as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{A as e,F as t,I as n,M as r,O as i,P as a,Pt as o,Tt as s,V as c,Wt as l,Z as u,at as d,et as f,fn as p,ht as m,i as h,it as g,j as _,k as v,l as y,lt as b,mt as x,n as S,o as C,u as w,vt as T,w as E}from"./router-
|
|
1
|
+
import{A as e,F as t,I as n,M as r,O as i,P as a,Pt as o,Tt as s,V as c,Wt as l,Z as u,at as d,et as f,fn as p,ht as m,i as h,it as g,j as _,k as v,l as y,lt as b,mt as x,n as S,o as C,u as w,vt as T,w as E}from"./router-tvNif7bs.js";import{m as D}from"./message-K5WVknE-.js";import{n as O,t as k}from"./index-yak1S-7W.js";import{n as A,t as j}from"./useManagerApi-Wm9Nz3_p.js";import{t as M}from"./useToast-D_jja27E.js";import{t as N}from"./updateNote-CttDt8SZ.js";c(),l(),T();var P={class:`check-body`},F={class:`version-row`},I={class:`version-row`},L={class:`muted-sm`},R=k(n({__name:`UpdateCheckDialog`,props:{modelValue:{type:Boolean,default:!1},modelModifiers:{}},emits:[`update:modelValue`],setup(n){let{toast:o}=M(),c=b(n,`modelValue`),l=s(!1),u=s(!1),y=s(null),S=s(null),C=i(()=>h.value?.buildSource===`local`),w=s(null),T=s(null),D=null,k=()=>D?.abort(),R=e=>new Date(e).toLocaleString(),z=e=>`https://www.npmjs.com/package/ezllm/v/${e}`,B=async()=>{S.value=null,w.value=null,T.value=null,l.value=!0;try{let e=await j.update();if(y.value=e,!e.supported)return;S.value=await j.checkUpdate()}catch(e){w.value=e.message||`检查更新失败`}finally{l.value=!1}},V=async()=>{if(!S.value)return;let e=S.value.latestVersion;try{await O.confirm(`确定更新到 ${e}?服务会自动重启并在启动时安装新版本:重启期间管理台短暂不可访问(预计 1~3 分钟),完成后自动恢复。`,`立即更新`,{confirmButtonText:`立即更新`,cancelButtonText:`取消`,type:`warning`})}catch{return}u.value=!0;let t=new AbortController;D=t;try{let e=await j.update(),n=await j.applyUpdate();if(!n.updated){T.value=`已是最新版本 ${n.fromVersion}`,S.value={hasUpdate:!1,currentVersion:n.fromVersion,latestVersion:n.fromVersion,checkedAt:Date.now()};return}T.value=`正在重启服务并安装 ${n.toVersion}(预计 1~3 分钟):重启期间管理台短暂不可访问,完成后这里会自动显示结果。`;let r=await A({targetVersion:n.toVersion,sinceAppliedAt:e.lastApply?.appliedAt??null,signal:t.signal});if(t.signal.aborted)return;r&&!r.error?(o(`已更新到 ${r.toVersion}`,`success`),T.value=`已更新 ${r.fromVersion} → ${r.toVersion},服务已在新版本上运行。`,S.value={hasUpdate:!1,currentVersion:r.toVersion,latestVersion:r.toVersion,checkedAt:Date.now()}):r?.error?(o(`更新失败:${r.error}`,`error`),T.value=N(r.error)):T.value=`更新仍在进行(安装耗时较长):稍后刷新页面查看最新状态。`}catch(e){o(e.message||`更新失败`,`error`)}finally{u.value=!1,D===t&&(D=null)}};return(n,i)=>{let o=g(`el-alert`),s=g(`el-tag`),h=g(`el-link`),b=g(`el-button`),D=g(`el-dialog`),O=d(`loading`);return f(),e(D,{modelValue:c.value,"onUpdate:modelValue":i[2]||=e=>c.value=e,title:`检查更新`,width:`420px`,"close-on-click-modal":!u.value,onOpen:B,onClose:k},{footer:x(()=>[S.value?.hasUpdate?(f(),e(b,{key:0,type:`primary`,loading:u.value,onClick:V},{default:x(()=>[a(p(u.value?`更新中...`:`立即更新`),1)]),_:1},8,[`loading`])):_(``,!0),t(b,{onClick:i[1]||=e=>c.value=!1},{default:x(()=>[...i[11]||=[a(`关闭`,-1)]]),_:1})]),default:x(()=>[m((f(),r(`div`,P,[y.value&&!y.value.supported?(f(),e(o,{key:0,title:`当前为源码运行模式,更新功能不可用`,type:`info`,"show-icon":``,closable:!1},{default:x(()=>[...i[3]||=[v(`div`,{class:`alert-hint`},[a(` 仅通过 npx ezllm install 装配的实例支持更新检查。本地开发用仓库里的 `),v(`code`,null,`pnpm upgrade:local`),a(` 更新运行中的服务。 `)],-1)]]),_:1})):S.value?(f(),r(E,{key:1},[v(`div`,F,[i[5]||=v(`span`,{class:`version-label`},`当前版本`,-1),t(s,{size:`small`},{default:x(()=>[a(p(S.value.currentVersion),1)]),_:1}),C.value?(f(),e(s,{key:0,type:`warning`,size:`small`,"disable-transitions":``},{default:x(()=>[...i[4]||=[a(`本地构建`,-1)]]),_:1})):_(``,!0)]),v(`div`,I,[i[6]||=v(`span`,{class:`version-label`},`最新版本`,-1),t(s,{type:S.value.hasUpdate?`warning`:`success`,size:`small`,"disable-transitions":``},{default:x(()=>[a(p(S.value.latestVersion),1)]),_:1},8,[`type`])]),t(o,{title:S.value.hasUpdate?`发现新版本 ${S.value.latestVersion}(当前 ${S.value.currentVersion})`:`已是最新版本 ${S.value.currentVersion}`,type:S.value.hasUpdate?`warning`:`success`,"show-icon":``,closable:!1,class:`mt-12`},null,8,[`title`,`type`]),v(`div`,L,[a(` 检查时间:`+p(R(S.value.checkedAt))+` `,1),S.value.hasUpdate?(f(),r(E,{key:0},[i[8]||=a(` · `,-1),t(h,{type:`primary`,href:z(S.value.latestVersion),target:`_blank`},{default:x(()=>[...i[7]||=[a(`在 npm 查看该版本`,-1)]]),_:1},8,[`href`])],64)):_(``,!0)])],64)):!l.value&&w.value?(f(),e(o,{key:2,title:w.value,type:`error`,"show-icon":``,closable:!1},{default:x(()=>[i[10]||=v(`div`,{class:`alert-hint`},` 检查需访问 npm 源,可能是网络不可达或版本号解析失败;稍后重试即可。 `,-1),t(b,{size:`small`,class:`mt-8`,onClick:B},{default:x(()=>[...i[9]||=[a(`重试`,-1)]]),_:1})]),_:1},8,[`title`])):_(``,!0)])),[[O,l.value]]),T.value?(f(),e(o,{key:0,title:T.value,type:`info`,"show-icon":``,class:`mt-12`,onClose:i[0]||=e=>T.value=null},null,8,[`title`])):_(``,!0)]),_:1},8,[`modelValue`,`close-on-click-modal`])}}}),[[`__scopeId`,`data-v-5e671468`]]);c(),l(),T();var z={class:`layout`},B=[`title`],V={key:0,class:`header-version-tag`},H={class:`header-nav`},U={class:`header-actions`},W={class:`user-chip`},G={class:`user-avatar`},K={class:`user-name`},q={class:`main`},J={class:`page`},Y=k(n({__name:`LayoutView`,setup(n){let c=y(),l=w(),d=s(!1);u(()=>{S()});let m=i(()=>h.value?.version||``),b=i(()=>h.value?.buildSource===`local`),T=i(()=>b.value?`本地构建版本(upgrade:local 产物)`:`当前运行版本`),E=i(()=>h.value?.authRequired!==!1),O=i(()=>{if(!E.value)return`免登录`;let e=localStorage.getItem(`webui_token`)||``;try{return atob(e).split(`:`)[0]||`用户`}catch{return`用户`}}),k=i(()=>{let e=c.path;return e===`/`?`/`:e.startsWith(`/providers`)?`/providers`:e.startsWith(`/service-status`)?`/service-status`:e.startsWith(`/managed-services`)?`/managed-services`:e.startsWith(`/settings`)?`/settings`:e.startsWith(`/logs`)?`/logs`:`/`}),A=e=>{e===`logout`&&(C(),l.push(`/login`)),e===`check-update`&&(d.value=!0)};return(n,i)=>{let s=g(`el-menu-item`),c=g(`el-menu`),l=g(`el-icon`),u=g(`el-dropdown-item`),h=g(`el-dropdown-menu`),y=g(`el-dropdown`),S=g(`el-header`),C=g(`router-view`);return f(),r(`div`,z,[t(S,{class:`header`},{default:x(()=>[i[10]||=v(`div`,{class:`brand`},[v(`span`,{class:`brand-mark`},`EZ`),v(`h1`,null,`LLM`)],-1),m.value?(f(),r(`span`,{key:0,class:`header-version`,title:T.value},[a(` v`+p(m.value),1),b.value?(f(),r(`span`,V,`本地构建`)):_(``,!0)],8,B)):_(``,!0),v(`nav`,H,[t(c,{mode:`horizontal`,"default-active":k.value,ellipsis:!1,router:``,class:`header-menu`},{default:x(()=>[t(s,{index:`/`},{default:x(()=>[...i[1]||=[a(`首页`,-1)]]),_:1}),t(s,{index:`/providers`},{default:x(()=>[...i[2]||=[a(`供应商管理`,-1)]]),_:1}),t(s,{index:`/service-status`},{default:x(()=>[...i[3]||=[a(`服务状态`,-1)]]),_:1}),t(s,{index:`/managed-services`},{default:x(()=>[...i[4]||=[a(`托管服务`,-1)]]),_:1}),t(s,{index:`/plugins`},{default:x(()=>[...i[5]||=[a(`插件`,-1)]]),_:1}),t(s,{index:`/logs`},{default:x(()=>[...i[6]||=[a(`日志`,-1)]]),_:1}),t(s,{index:`/settings`},{default:x(()=>[...i[7]||=[a(`设置`,-1)]]),_:1})]),_:1},8,[`default-active`])]),v(`div`,U,[t(y,{trigger:`click`,onCommand:A,class:`ml-12`},{dropdown:x(()=>[t(h,null,{default:x(()=>[t(u,{command:`check-update`},{default:x(()=>[...i[8]||=[a(`检查更新`,-1)]]),_:1}),E.value?(f(),e(u,{key:0,command:`logout`,divided:``},{default:x(()=>[...i[9]||=[a(`退出`,-1)]]),_:1})):_(``,!0)]),_:1})]),default:x(()=>[v(`div`,W,[v(`span`,G,p(O.value.charAt(0).toUpperCase()),1),v(`span`,K,p(O.value),1),t(l,{class:`el-icon--right`},{default:x(()=>[t(o(D))]),_:1})])]),_:1})])]),_:1}),v(`main`,q,[v(`div`,J,[t(C)])]),t(R,{modelValue:d.value,"onUpdate:modelValue":i[0]||=e=>d.value=e},null,8,[`modelValue`])])}}}),[[`__scopeId`,`data-v-4e7e0e0d`]]);export{Y as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{A as e,F as t,I as n,M as r,P as i,S as a,Tt as o,V as s,a as c,c as l,et as u,it as d,j as f,k as p,m,mt as h,u as g,vt as _,x as v}from"./router-
|
|
1
|
+
import{A as e,F as t,I as n,M as r,P as i,S as a,Tt as o,V as s,a as c,c as l,et as u,it as d,j as f,k as p,m,mt as h,u as g,vt as _,x as v}from"./router-tvNif7bs.js";import{t as y}from"./index-yak1S-7W.js";s(),m(),_();var b={class:`login-wrap`},x=y(n({__name:`LoginView`,setup(n){let s=g(),m=o(``),_=o(``),y=o(``),x=o(!1);async function S(){if(!m.value){y.value=`请输入用户名`;return}if(!_.value){y.value=`请输入密码`;return}x.value=!0,y.value=``;try{let e=await c.post(`/api/auth/login`,{username:m.value.trim(),password:_.value});l(e.token),s.push(`/`)}catch(e){y.value=e.message||`登录失败`}finally{x.value=!1}}return(n,o)=>{let s=d(`el-input`),c=d(`el-form-item`),l=d(`el-alert`),g=d(`el-button`),C=d(`el-form`),w=d(`el-card`);return u(),r(`div`,b,[t(w,{shadow:`never`,class:`login-card`},{default:h(()=>[o[3]||=p(`div`,{class:`login-brand`},[p(`span`,{class:`brand-mark`},`EZ`),p(`span`,{class:`login-title`},`LLM`)],-1),o[4]||=p(`p`,{class:`login-sub`},`本地 LLM 网关 · 管理台登录`,-1),t(C,{"label-position":`top`,onSubmit:a(S,[`prevent`])},{default:h(()=>[t(c,{label:`用户名`},{default:h(()=>[t(s,{modelValue:m.value,"onUpdate:modelValue":o[0]||=e=>m.value=e,placeholder:`请输入用户名`,onKeyup:v(S,[`enter`])},null,8,[`modelValue`])]),_:1}),t(c,{label:`密码`},{default:h(()=>[t(s,{modelValue:_.value,"onUpdate:modelValue":o[1]||=e=>_.value=e,type:`password`,placeholder:`请输入密码`,"show-password":``,onKeyup:v(S,[`enter`])},null,8,[`modelValue`])]),_:1}),y.value?(u(),e(l,{key:0,title:y.value,type:`error`,"show-icon":``,class:`mb-16`},null,8,[`title`])):f(``,!0),t(g,{type:`primary`,class:`full-width`,loading:x.value,onClick:S},{default:h(()=>[...o[2]||=[i(` 登录 `,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})])}}}),[[`__scopeId`,`data-v-b3e448fa`]]);export{x as default};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{A as e,Ct as t,F as n,I as r,M as i,O as a,P as o,Pt as s,S as c,Tt as l,V as u,Wt as d,Z as f,a as p,at as ee,bt as m,et as h,fn as g,ft as _,ht as v,it as y,j as b,k as x,m as S,mt as C,nt as w,vt as T,w as E,x as te,xt as D}from"./router-
|
|
1
|
+
import{A as e,Ct as t,F as n,I as r,M as i,O as a,P as o,Pt as s,S as c,Tt as l,V as u,Wt as d,Z as f,a as p,at as ee,bt as m,et as h,fn as g,ft as _,ht as v,it as y,j as b,k as x,m as S,mt as C,nt as w,vt as T,w as E,x as te,xt as D}from"./router-tvNif7bs.js";import{R as O,S as ne}from"./message-K5WVknE-.js";import{n as k,t as A}from"./index-yak1S-7W.js";import{t as re}from"./providers-BuzQNH1W.js";import{n as j,t as M}from"./useCopyToast-BeWZ6lSF.js";import{t as N}from"./useToast-D_jja27E.js";import{n as P,t as F}from"./useQuickRangeFilter-DQQ8QAr5.js";import{C as I,S as L,a as ie,b as R,c as z,d as B,f as V,g as H,h as U,l as W,m as G,n as ae,o as oe,p as K,r as q,s as se,t as ce,x as le}from"./format-CPPf8IfM.js";u(),T(),d();var ue={class:`detail-content`},J={key:0,class:`ip-link`},Y={class:`ip-pop`},X={class:`ip-pop-row`},de={key:0,class:`ip-pop-row`},fe={key:1,class:`ip-pop-error`},pe={key:2},me=[`href`],he={key:1},ge={key:1},_e=[`title`],ve=[`title`],ye={class:`error-text`},be={class:`pane-bar`},Z={class:`json-block`},xe={class:`pane-bar`},Q={class:`json-block`},Se={class:`pane-bar`},Ce={class:`json-block`},$={class:`pane-bar`},we={class:`json-block`},Te={class:`pane-bar`},Ee={class:`json-block`},De={class:`pane-bar`},Oe={class:`json-block`},ke={class:`pane-bar`},Ae={class:`json-block`},je={class:`pane-bar`},Me={class:`json-block`},Ne=A(r({__name:`LogDetailDialog`,setup(t,{expose:r}){let{copyWithToast:c}=M(),{loadError:u,run:d}=j(),f=l(!1),m=l(null),_=l(!1),S=l(`info`),w=l(!1),T=l(null),te=P(),O=e=>{T.value=e,f.value=!0,m.value=null,S.value=`info`;let t=te();_.value=!0,d(async()=>{try{let n=`?id=${String(e.id)}`,r=await p.get(`/api/logs/detail`+n);if(!t())return;if(r&&r.detail){let e={...r.detail,...r.meta};e.response&&=D(e.response),e.request&&=D(e.request),m.value=e}else m.value=r}catch(e){if(!t())return;throw e}finally{t()&&(_.value=!1)}})},ne=()=>{T.value&&O(T.value)};r({open:O});let k=a(()=>K(m.value?.response?.inbound_request)),A=a(()=>K(m.value?.response?.upstream_request)),re=a(()=>U(m.value?.response?.upstream_response_text)),N=a(()=>(m.value?.response?.upstream_stream_frames||[]).join(`
|
|
2
2
|
|
|
3
3
|
`)),F=a(()=>K(m.value?.response?.client_response)),L=a(()=>(m.value?.response?.client_stream_frames||[]).join(`
|
|
4
4
|
|
package/dist/web-ui/assets/{ManagedServicesView-B6JE5Odv.js → ManagedServicesView-CNLHAVAL.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{A as e,F as t,I as n,M as r,O as i,P as a,Pt as o,Q as s,Tt as c,V as l,Wt as u,Z as d,at as f,et as p,fn as m,ht as h,it as g,j as _,k as v,mt as y,vt as b,w as x}from"./router-
|
|
1
|
+
import{A as e,F as t,I as n,M as r,O as i,P as a,Pt as o,Q as s,Tt as c,V as l,Wt as u,Z as d,at as f,et as p,fn as m,ht as h,it as g,j as _,k as v,mt as y,vt as b,w as x}from"./router-tvNif7bs.js";import{W as S}from"./message-K5WVknE-.js";import{n as C,t as w}from"./index-yak1S-7W.js";import{t as T}from"./useManagerApi-Wm9Nz3_p.js";import{n as E,t as D}from"./useCopyToast-BeWZ6lSF.js";import{t as O}from"./useToast-D_jja27E.js";import{m as k}from"./format-CPPf8IfM.js";l(),u(),b();var A={class:`form-hint`},j={key:1,class:`form-hint form-hint--block`},M=w(n({__name:`ManagedServiceModal`,props:{editingName:{},service:{}},emits:[`close`,`saved`],setup(n,{emit:o}){let s=n,l=o,{toast:u}=O(),d=c(),f=c(!1),h=c(!0),b=i(()=>s.service?.pid!=null),S=c({name:s.service?.name??``,exec:s.service?.exec??``,cwd:s.service?.cwd??``,autostart:s.service?.autostart??!0}),C=c((s.service?.args??[]).join(`
|
|
2
2
|
`)),w={name:[{required:!0,message:`请填写服务名`,trigger:`blur`},{pattern:/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/,message:`只能用字母、数字、点、下划线、短横线(字母或数字开头,不超过 64 个字符)`,trigger:`blur`}],cwd:[{required:!0,message:`请填写工作目录`,trigger:`blur`}]},E=async()=>{let e=e=>e.split(`
|
|
3
3
|
`).map(e=>e.trim()).filter(e=>e!==``);if(!await d.value?.validate().catch(()=>!1)){u(`请补全标红的必填项`,`error`);return}let t={name:S.value.name.trim(),exec:S.value.exec.trim(),args:e(C.value),cwd:S.value.cwd.trim(),autostart:S.value.autostart};b.value||(t.start=h.value),f.value=!0;try{s.editingName===null?(await T.createManagedService(t),u(t.start?`服务已添加并启动`:`服务已添加`,`success`)):(await T.updateManagedService(s.editingName,t),u(b.value?`已保存,服务正按新配置重启`:`已保存`,`success`)),l(`saved`)}catch(e){u(e.message||`保存失败`,`error`)}finally{f.value=!1}};return(i,o)=>{let s=g(`el-input`),c=g(`el-form-item`),u=g(`el-switch`),T=g(`el-form`),D=g(`el-button`),O=g(`el-dialog`);return p(),e(O,{"model-value":!0,title:n.editingName?`编辑托管服务`:`添加托管服务`,width:`720px`,top:`6vh`,"align-center":``,"close-on-click-modal":!1,onClose:o[7]||=e=>l(`close`)},{footer:y(()=>[t(D,{disabled:f.value,onClick:o[6]||=e=>l(`close`)},{default:y(()=>[...o[13]||=[a(`取消`,-1)]]),_:1},8,[`disabled`]),t(D,{type:`primary`,loading:f.value,onClick:E},{default:y(()=>[a(m(f.value?`保存中...`:n.editingName?`保存`:`添加服务`),1)]),_:1},8,[`loading`])]),default:y(()=>[t(T,{ref_key:`formRef`,ref:d,model:S.value,rules:w,"label-position":`top`},{default:y(()=>[t(c,{label:`服务名`,prop:`name`},{default:y(()=>[t(s,{modelValue:S.value.name,"onUpdate:modelValue":o[0]||=e=>S.value.name=e,disabled:n.editingName!==null,placeholder:`如 codebuddy-cpa(字母数字开头,可用 . _ -)`},null,8,[`modelValue`,`disabled`]),v(`div`,A,[o[8]||=a(` 名称用于日志文件名(logs/out-<名称>.log)与进程标识;`,-1),n.editingName===null?_(``,!0):(p(),r(x,{key:0},[a(`编辑时不可修改(改名请删除后重新添加)。`)],64))])]),_:1}),t(c,{label:`工作目录`,prop:`cwd`},{default:y(()=>[t(s,{modelValue:S.value.cwd,"onUpdate:modelValue":o[1]||=e=>S.value.cwd=e,placeholder:`如 E:\\gitee\\codebuddy-cpa(服务的运行目录)`},null,8,[`modelValue`]),o[9]||=v(`div`,{class:`form-hint`},`服务在此目录下启动;保存时会校验目录是否存在。`,-1)]),_:1}),t(c,{label:`可执行文件`},{default:y(()=>[t(s,{modelValue:S.value.exec,"onUpdate:modelValue":o[2]||=e=>S.value.exec=e,placeholder:`留空 = 使用 ezllm 主进程的 Node;也可填 npm.cmd 或绝对路径`},null,8,[`modelValue`]),o[10]||=v(`div`,{class:`form-hint`},` 留空时用主进程的 Node 直接运行下方参数(第一个参数通常是脚本相对工作目录的路径)。 Windows 上 .cmd / .bat / 无扩展名的命令(如 npm)会自动经 cmd.exe 执行;其他平台直接执行。 `,-1)]),_:1}),t(c,{label:`参数`},{default:y(()=>[t(s,{modelValue:C.value,"onUpdate:modelValue":o[3]||=e=>C.value=e,type:`textarea`,rows:3,placeholder:`每行一个参数,如:
|
|
4
4
|
src/proxy.js
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.plugin-grid[data-v-60925e93]{grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:16px;display:grid}.plugin-card[data-v-60925e93]{border:1px solid var(--el-border-color-light);border-radius:8px;flex-direction:column;gap:10px;padding:16px;transition:border-color .15s;display:flex}.plugin-card[data-v-60925e93]:hover{border-color:var(--el-color-primary-light-5)}.card-top[data-v-60925e93]{align-items:flex-start;gap:12px;display:flex}.plugin-icon[data-v-60925e93]{background:var(--el-fill-color-light);border-radius:8px;flex-shrink:0;justify-content:center;align-items:center;width:40px;height:40px;font-size:18px;font-weight:600;display:flex;overflow:hidden}.plugin-icon img[data-v-60925e93]{object-fit:contain;width:100%;height:100%}.plugin-titles[data-v-60925e93]{flex:1;min-width:0}.plugin-name[data-v-60925e93]{text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:600;overflow:hidden}.plugin-tags[data-v-60925e93]{flex-wrap:wrap;gap:6px;margin-top:4px;display:flex}.plugin-action[data-v-60925e93]{flex-shrink:0}.plugin-summary[data-v-60925e93]{color:var(--el-text-color-regular);-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;min-height:39px;font-size:13px;line-height:1.5;display:-webkit-box;overflow:hidden}.plugin-meta[data-v-60925e93]{color:var(--el-text-color-secondary);border-top:1px solid var(--el-border-color-lighter);flex-wrap:wrap;gap:12px;padding-top:10px;font-size:12px;display:flex}.mono[data-v-60925e93]{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.installed-error[data-v-60925e93]{margin-bottom:12px}.installed-list[data-v-60925e93]{scrollbar-width:thin;scrollbar-color:var(--el-border-color) transparent;flex-direction:column;gap:12px;max-height:70vh;padding-right:4px;display:flex;overflow:hidden auto}.installed-list[data-v-60925e93]::-webkit-scrollbar:horizontal{height:0;display:none}.installed-summary[data-v-60925e93]{color:var(--el-text-color-secondary);margin-bottom:12px;font-size:12px}.installed-summary-ok[data-v-60925e93]{color:var(--el-color-success)}.installed-card[data-v-60925e93]{border:1px solid var(--el-border-color-light);background:var(--el-bg-color);border-radius:12px;flex-shrink:0;transition:border-color .15s,box-shadow .15s;overflow:hidden;box-shadow:0 1px 2px #00000008}.installed-card[data-v-60925e93]:hover{border-color:var(--el-color-primary-light-7);box-shadow:0 2px 8px #0000000f}.installed-head[data-v-60925e93]{background:var(--el-fill-color-lighter);border-bottom:1px solid var(--el-border-color-lighter);align-items:flex-start;gap:12px;padding:12px 14px;display:flex}.installed-avatar[data-v-60925e93]{background:var(--el-color-primary-light-9);width:32px;height:32px;color:var(--el-color-primary);border-radius:9px;flex-shrink:0;justify-content:center;align-items:center;font-size:14px;font-weight:600;display:flex}.installed-identity[data-v-60925e93]{flex:1;min-width:0}.installed-title[data-v-60925e93]{align-items:center;gap:8px;min-width:0;display:flex}.installed-name[data-v-60925e93]{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--el-text-color-primary);font-size:14px;font-weight:600;overflow:hidden}.installed-version[data-v-60925e93]{border:1px solid var(--el-border-color-light);background:var(--el-bg-color);color:var(--el-text-color-regular);border-radius:999px;flex-shrink:0;padding:0 6px;font-size:11px;line-height:16px}.installed-desc[data-v-60925e93]{color:var(--el-text-color-regular);-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;margin-top:4px;font-size:12px;line-height:1.5;display:-webkit-box;overflow:hidden}.installed-pkgname[data-v-60925e93]{color:var(--el-text-color-placeholder);text-overflow:ellipsis;white-space:nowrap;margin-top:3px;font-size:11px;overflow:hidden}.installed-ops[data-v-60925e93]{flex-shrink:0;align-items:center;gap:4px;padding-top:2px;display:flex}.installed-ops .el-button[data-v-60925e93]{height:auto;padding:0}.installed-ops .card-more[data-v-60925e93]{width:28px;height:28px;color:var(--el-text-color-secondary);border-radius:6px;padding:0}.installed-ops .card-more[data-v-60925e93]:hover{color:var(--el-color-primary);background:var(--el-fill-color-darker)}.danger-text[data-v-60925e93]{color:var(--el-color-danger)}.installed-noprovider[data-v-60925e93]{color:var(--el-text-color-secondary);padding:10px 14px 12px;font-size:12px}.installed-providers[data-v-60925e93]{padding:2px 14px}.installed-provider[data-v-60925e93]{justify-content:space-between;align-items:center;gap:12px;padding:10px 0;display:flex}.installed-provider[data-v-60925e93]:not(:last-child){border-bottom:1px solid var(--el-border-color-lighter)}.installed-provider-info[data-v-60925e93]{align-items:baseline;gap:8px;min-width:0;display:flex}.installed-provider-id[data-v-60925e93]{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--el-text-color-primary);font-size:13px;overflow:hidden}.installed-provider-models[data-v-60925e93]{color:var(--el-text-color-secondary);flex-shrink:0;font-size:12px}.installed-provider-actions[data-v-60925e93]{flex-shrink:0;align-items:center;gap:8px;display:flex}.installed-provider-actions .el-button[data-v-60925e93]{height:auto;padding:0}.installed-state[data-v-60925e93]{color:var(--el-text-color-secondary);white-space:nowrap;align-items:center;gap:4px;font-size:12px;display:inline-flex}.installed-state .status-dot[data-v-60925e93]{background:var(--el-text-color-placeholder);margin-right:0}.installed-state.is-on[data-v-60925e93]{color:var(--el-color-success)}.installed-state.is-on .status-dot[data-v-60925e93]{background:var(--el-color-success)}.method-list[data-v-60925e93]{flex-direction:column;gap:8px;margin-top:12px;display:flex}.method-btn[data-v-60925e93]{justify-content:flex-start;margin-left:0}.field-row[data-v-60925e93]:not(:first-child){margin-top:12px}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{A as e,F as t,I as n,J as r,M as i,O as a,P as o,Pt as s,Tt as c,V as l,Wt as u,Z as ee,et as d,fn as f,it as p,j as m,k as h,m as g,mt as _,nt as v,sn as te,vt as ne,w as y,x as re}from"./router-tvNif7bs.js";import{H as ie,W as ae}from"./message-K5WVknE-.js";import{n as oe,t as b}from"./index-yak1S-7W.js";import{t as x}from"./useManagerApi-Wm9Nz3_p.js";import{n as S,t as se}from"./providers-BuzQNH1W.js";import{t as ce}from"./useToast-D_jja27E.js";import{a as C,c as le,i as ue,l as w,n as de,o as fe,r as pe,s as T,t as me,u as he}from"./pluginProviders-CB38zjhC.js";ne();var E=`https://raw.githubusercontent.com/magpie-community/plugins/main/registry.json`,D=[`https://gh-proxy.com/${E}`,`https://ghfast.top/${E}`,E],O=1e4,k=`plugin_market_cache`,A=1,ge=216e5,_e=36e5,ve=[`provider`];function ye(){try{let e=localStorage.getItem(k);if(e===null)return null;let t=JSON.parse(e);if(t.version!==A||!Array.isArray(t.entries))return null;let n=t.fetchedAt;return n===void 0||typeof n.at!=`number`?null:{version:A,entries:t.entries.filter(Se),fetchedAt:n}}catch{return null}}function be(e,t){try{let n={version:A,entries:e,fetchedAt:t};localStorage.setItem(k,JSON.stringify(n))}catch{}}function j(e){return typeof e==`string`&&e.trim()!==``?e.trim():null}function xe(e){if(typeof e!=`object`||!e||Array.isArray(e))return null;let t=e;return j(t.zh)??j(t.en)}function M(e){switch(e){case`provider`:case`middleware`:case`agent`:case`tools`:case`page`:return e;default:return`provider`}}function N(e){return ve.includes(e)}function Se(e){if(typeof e!=`object`||!e)return!1;let t=e.kind;return typeof t==`string`&&N(t)}function Ce(e){let t=j(e.package);if(t===null)return null;let n=Array.isArray(e.providers)?e.providers.filter(e=>typeof e==`string`):[];return{package:t,name:j(e.name)??t,icon:j(e.icon),providers:n,community:j(e.community),summary:xe(e.summary),kind:M(e.kind),replaces:j(e.replaces),version:null,license:null,description:null,homepage:null,weeklyDownloads:null}}var we=new Map,P=new Map;async function F(e){let t=we.get(e);if(t!==void 0&&Date.now()-t.at<_e)return t.meta;try{let t=await w(`https://registry.npmjs.org/${encodeURIComponent(e)}`),n={version:j(t[`dist-tags`]?.latest),license:j(t.license),description:j(t.description),homepage:j(t.homepage)};return we.set(e,{at:Date.now(),meta:n}),n}catch{return{version:null,license:null,description:null,homepage:null}}}async function I(e){let t=P.get(e);if(t!==void 0&&Date.now()-t.at<_e)return t.downloads;try{let t=await w(`https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(e)}`),n=typeof t.downloads==`number`?t.downloads:null;return P.set(e,{at:Date.now(),downloads:n}),n}catch{return null}}async function Te(){let e=[];for(let t of D)try{let e=await w(t,{timeoutMs:O});return t!==D[0]&&S(`[plugin-market] 清单改由备用源取到:${t}`),{registry:e,source:t}}catch(n){let r=n instanceof Error?n.message:String(n);e.push(`${t} → ${r}`)}throw Error(`插件清单取不到(已试 ${String(D.length)} 个源):\n${e.join(`
|
|
2
|
+
`)}`)}async function Ee(e){let{registry:t,source:n}=await Te(),r=t.plugins;if(!Array.isArray(r))throw Error(`清单格式不是预期形状(缺 plugins 数组):${E}`);let i=r.map(e=>typeof e==`object`&&e?Ce(e):null).filter(Se).sort((e,t)=>e.package.localeCompare(t.package)),a=0;return await Promise.all(i.map(async t=>{let[n,r]=await Promise.all([F(t.package),I(t.package)]);Object.assign(t,n,{weeklyDownloads:r}),a+=1,e?.(a,i.length)})),{entries:i,source:n}}var L=c([]),R=c(!1),z=c(null),B=c({loaded:0,total:0}),V=c(null),H=c(!1),De=null;function Oe(e,t){JSON.stringify(e)!==JSON.stringify(L.value)&&(L.value=e),V.value=t,be(e,t)}async function ke(e={}){let t=e.force===!0,n=L.value.length===0?ye():null;n!==null&&(L.value=n.entries,V.value=n.fetchedAt);let r=L.value.length>0,i=V.value,a=i!==null&&Date.now()-i.at<ge;if(!(!t&&r&&a)){if(De!==null){if(t)await De;else return}if(t||!r){R.value=!0,H.value=!1,B.value={loaded:0,total:0};try{let e=await Ee((e,t)=>{B.value={loaded:e,total:t}});Oe(e.entries,{source:e.source,at:Date.now()}),z.value=null}catch(e){z.value=`插件市场清单加载失败:${e instanceof Error?e.message:String(e)}`}finally{R.value=!1}return}H.value=!0,De=(async()=>{try{let e=await Ee();Oe(e.entries,{source:e.source,at:Date.now()}),z.value=null,H.value=!1}catch(e){S(`[plugin-market] 后台刷新失败,继续用缓存:${e instanceof Error?e.message:String(e)}`)}finally{De=null}})()}}function Ae(){return{entries:L,loading:R,marketError:z,progress:B,fetchedAt:V,stale:H,loadMarket:ke}}l(),ne(),u(),g();var je={class:`page-head`},Me={class:`page-head-actions`},Ne={class:`flex-between`},Pe={class:`muted-sm`},Fe={key:0,class:`loading-block`},Ie={key:1,class:`loading-block`},Le={key:2,class:`plugin-grid`},Re={class:`card-top`},ze={class:`plugin-icon`},Be=[`src`,`alt`,`onError`],Ve={key:1},He={class:`plugin-titles`},Ue=[`title`],We={class:`plugin-tags`},Ge={class:`plugin-action`},Ke=[`title`],qe={class:`plugin-meta`},Je={key:0},Ye={key:1,class:`mono`},Xe={key:2,class:`muted-disabled`},Ze={key:2,class:`installed-summary`},Qe={class:`installed-summary-ok`},$e={key:3,class:`installed-list`},et={class:`installed-head`},tt={class:`installed-avatar`},nt={class:`installed-identity`},rt={class:`installed-title`},it=[`title`],at={class:`installed-version`},ot=[`title`],st=[`title`],ct={class:`installed-ops`},lt={key:0,class:`installed-noprovider`},ut={key:1,class:`installed-providers`},dt={class:`installed-provider-info`},ft=[`title`],pt={class:`installed-provider-models`},mt={class:`installed-provider-actions`},ht={class:`method-list`},gt={class:`muted-sm`},_t={key:0,class:`muted-sm`},vt={class:`muted-sm`},yt={class:`muted-sm`},bt={key:0,class:`mt-8`},xt=[`href`],St={key:2,class:`muted-sm mt-8`},Ct=5e3,wt=b(n({__name:`PluginsView`,setup(n){let{toast:l}=ce(),u=se(),g=c(!0),ne=c(`服务未由 ezllm 主进程托管(源码运行模式);请用 ezllm 启动服务`),b=c(!1),S=c(!1),E=c(null),D=c(!1),O=c(!0),k=c(!1),{entries:A,loading:ge,marketError:_e,progress:ve,fetchedAt:ye,stale:be,loadMarket:j}=Ae(),xe=c(new Set),M=c([]),N=c(null),Se=a(()=>{let e=M.value.flatMap(e=>e.providers);return{total:e.length,signedIn:e.filter(e=>e.signedIn).length}}),Ce=e=>A.value.find(t=>t.package===e)?.name??e.replace(/^@[^/]+\//,``),we=e=>{let t=Ce(e).trim();return t===``?`?`:t.charAt(0).toUpperCase()},P=c(null),F=c(!1),I=c(``),Te=c(!1),Ee=c(!1),L=c({}),R=c(!1),z=c(!0),B=a(()=>S.value&&E.value!==null),V=e=>M.value.some(t=>t.package===e.package),H=e=>e.icon===null||xe.value.has(e.package)?null:/^https?:\/\//i.test(e.icon)?e.icon:null,De=e=>e.icon!==null&&!/^https?:\/\//i.test(e.icon)?e.icon:(e.name.charAt(0)||`?`).toUpperCase(),Oe=e=>{xe.value=new Set(xe.value).add(e.package)},ke=e=>e>=1e3?`${(e/1e3).toFixed(e>=1e4?0:1)}k`:String(e),wt=async()=>{try{let e=await x.plugins();g.value=!0,b.value=e.enabled,O.value=e.autoUpdate,S.value=e.running,E.value=e.port}catch(e){if(typeof e?.message==`string`&&e.message.includes(`未由 ezllm 主进程托管`)){g.value=!1,ne.value=e.message,b.value=!1,O.value=!1,S.value=!1,E.value=null;return}throw e}},U=async()=>{if(!B.value){M.value=[],N.value=null;return}let e=E.value;if(e!==null)try{let t=await w(T(e,`/packages`));M.value=Array.isArray(t.packages)?t.packages:[],N.value=null}catch(e){M.value=[],N.value=`无法读取已装插件(插件服务可能正在重启):${e?.message??`请求失败`}`}},Tt=e=>{let t=Math.floor((Date.now()-e)/6e4);if(t<1)return`刚刚`;if(t<60)return`${String(t)} 分钟前`;let n=Math.floor(t/60);return n<24?`${String(n)} 小时前`:new Date(e).toLocaleDateString()},Et=a(()=>{if(ge.value)return`正在加载 ${String(ve.value.loaded)}/${String(ve.value.total)}…`;let e=ye.value===null?``:Tt(ye.value.at),t=e===``?``:` · ${e}更新`,n=be.value?` · 更新中…`:``;return`${String(A.value.length)} 个插件 · 已装 ${String(M.value.length)} 个${t}${n}`}),Dt=async(e=!1)=>{await j({force:e})},Ot=async()=>{try{await wt()}catch(e){_e.value=e?.message??`插件服务状态加载失败`;return}await U(),await Dt(),W()},kt=c(!1),At=async e=>{let t=await w(T(e,`/plugins`));return Array.isArray(t.providers)?t.providers:[]},jt=async(e,t)=>{try{let n=await w(`${ue(t,e)}/v1/models`,{timeoutMs:Ct}),r=(n.data??[]).map(e=>typeof e.id==`string`?e.id.trim():``).filter(e=>e!==``);return{fromHook:n.fromHook===!0,ids:r}}catch{return null}},Mt=async()=>{let e=E.value;if(B.value&&e!==null){kt.value=!0;try{await u.loadProviders();let t=await At(e),n=pe(u.suppliers,t,e),r=t.filter(e=>e.protocol!==null&&n.providers.filter(t=>C(t.apiBase)===e.id).some(t=>me(t,e))),i=new Map;await Promise.all(r.map(async t=>{let n=await jt(t.id,e);n!==null&&i.set(t.id,n)}));let a=de(n.providers,t,i);if(n.added.length===0&&n.updated.length===0&&a.refreshed.length===0)return;await u.saveProviders(a.providers);let o=[];n.added.length>0&&o.push(`新增 ${String(n.added.length)} 个供应商`),n.updated.length>0&&o.push(`修正 ${String(n.updated.length)} 个供应商`),a.refreshed.length>0&&o.push(`刷新 ${String(a.refreshed.length)} 个供应商的模型清单`),l(`已接入网关:${o.join(`、`)}`,`success`)}catch(e){l(e?.message??`供应商配置同步失败`,`error`)}finally{kt.value=!1}}},Nt=null,W=()=>(Nt===null&&(Nt=Mt().finally(()=>{Nt=null})),Nt),Pt=async e=>{D.value=!0;try{let t=await x.setPluginEnabled(e);b.value=t.enabled,S.value=t.running,E.value=t.port,l(e?`插件服务已开启`:`插件服务已关闭`,`success`),await U()}catch(e){await wt().catch(()=>void 0),l(e?.message??`操作失败`,`error`)}finally{D.value=!1}},Ft=async e=>{if(e){await Pt(!0);return}try{await oe.confirm(`关闭插件服务?已装插件保留,但插件模型的请求将不可用。`,`关闭确认`,{confirmButtonText:`关闭`,cancelButtonText:`取消`,type:`warning`})}catch{await wt().catch(()=>void 0);return}await Pt(!1)},It=async e=>{let t=!!e;k.value=!0;try{let e=await x.setPluginAutoUpdate(t);O.value=e.autoUpdate,l(t?`已开启插件自动更新`:`已关闭插件自动更新`,`success`)}catch(e){await wt().catch(()=>void 0),l(e?.message??`操作失败`,`error`)}finally{k.value=!1}},Lt=async e=>{await zt(e.package)},Rt=async()=>{let e=I.value.trim();if(e!==``){Ee.value=!0;try{await zt(e),F.value=!1,I.value=``}finally{Ee.value=!1}}},zt=async e=>{let t=E.value;if(t===null){l(`插件服务未开启`,`error`);return}P.value=e;try{(await he(T(t,`/packages`),{package:e})).loaded===!1?l(`${e} 已安装但未加载成功(重启插件服务或查看日志)`,`warning`):l(`已安装 ${e}`,`success`),await U(),await W()}catch(e){l(e?.message??`安装失败`,`error`)}finally{P.value=null}},Bt=async e=>{let t=E.value;if(t===null)return;let n=[];try{await u.loadProviders(),n=fe(u.suppliers,e.providers.map(e=>e.id))}catch{}let r=n.length===0?``:`网关里对应的供应商(${n.join(`、`)})也会一并移除。`;try{await oe.confirm(`确定卸载 ${e.package}?该插件提供的模型将不可用。${r}`,`确认卸载`,{confirmButtonText:`卸载`,cancelButtonText:`取消`,type:`warning`})}catch{return}P.value=e.package;try{await le(T(t,`/packages/${encodeURIComponent(e.package)}`)),n.length>0&&await u.saveProviders(u.suppliers.filter(e=>!n.includes(e.name))),l(`已卸载 ${e.package}`,`success`),await U()}catch(e){l(e?.message??`卸载失败`,`error`)}finally{P.value=null}},Vt=(e,t)=>{e===`uninstall`&&Bt(t)},Ht=()=>{I.value=``,F.value=!0},Ut=async()=>{await U(),Te.value=!0},Wt=a(()=>B.value?z.value?`检查已装插件是否有新版本`:`宿主当前无法检查更新`:`插件服务未开启`),Gt=e=>{let t=L.value[e];return t&&t.updateAvailable&&t.latest?t.latest:null},Kt=async(e=!1)=>{let t=E.value;if(t===null){e||l(`插件服务未开启`,`error`);return}R.value=!0;try{let n=await w(T(t,`/packages/updates`)),r=Array.isArray(n.packages)?n.packages:[];z.value=n.canCheck;let i={};for(let e of r)i[e.package]=e;if(L.value=i,e)return;if(!n.canCheck){l(`宿主当前无法检查更新`,`warning`);return}let a=r.filter(e=>e.updateAvailable).length;l(a>0?`发现 ${String(a)} 个插件有新版本`:`全部插件已是最新`,`success`)}catch(t){e||l(t?.message??`检查更新失败`,`error`)}finally{R.value=!1}},qt=async e=>{let t=E.value,n=L.value[e]?.latest;if(t===null){l(`插件服务未开启`,`error`);return}if(n!=null){P.value=e;try{await he(T(t,`/packages`),{package:e,version:n});let r=await x.restartPlugins();S.value=r.running,E.value=r.port,l(`已更新到 ${n} 并重启插件服务`,`success`),delete L.value[e],await U(),await W(),await Kt(!0)}catch(e){l(e?.message??`更新失败`,`error`)}finally{P.value=null}}},Jt=c(null),Yt=c(!1),Xt=c(``),Zt=c([]),G=c(!1),Qt=c(``),K=c(``),$t=c(``),q=c(null),J=c(!1),en=c(``),tn=c(``),nn=c(``),Y=c(``),rn=c(void 0),an=c({}),X=c(null),on=c(!1),Z=c(!1),sn=c(``),cn=c([]),Q=c({}),ln=c(void 0),un=c(null),dn=c(!1),$=new Map,fn=!1,pn=e=>{$.set(e,($.get(e)??0)+1)};r(()=>{fn=!0,$.clear()});let mn=(e,t)=>{K.value!==``&&K.value!==e&&pn(K.value),K.value=e,Qt.value=t.url??``,$t.value=t.instructions??``,q.value=null,G.value=!0},hn=async e=>{let t=E.value;if(t===null)return;let n=($.get(e)??0)+1;$.set(e,n);let r=()=>!fn&&$.get(e)===n,i=Date.now()+18e4;for(;;){if(await new Promise(e=>setTimeout(e,2e3)),!r())return;let n;try{n=await w(T(t,`/login/${encodeURIComponent(e)}`))}catch(t){r()&&K.value===e&&(q.value=t?.message??`查询登录状态失败`);return}if(!r())return;if(n.state===`success`){K.value===e&&(G.value=!1),l(`已登录${n.accountId?`(${n.accountId})`:``}`,`success`),await U(),await W();return}if(n.state===`failed`){K.value===e&&(q.value=n.error??`登录失败`);return}if(Date.now()>i){K.value===e&&(q.value=`等待超时,可关闭后重新点击「登录」`);return}}},gn=async(e,t,n,r)=>{let i=E.value;if(i===null){l(`插件服务未开启`,`error`);return}pn(e);let a={};t!==void 0&&(a.method=t),Object.keys(n).length>0&&(a.inputs=n),r!==void 0&&r!==``&&(a.key=r);let o=await he(T(i,`/login/${encodeURIComponent(e)}`),a);if(o.needMethod===!0){Xt.value=e,Zt.value=o.methods,Yt.value=!0;return}if(o.needInputs!==void 0&&o.needInputs.length>0){J.value=!1,sn.value=e,ln.value=t,cn.value=o.needInputs;let r={...o.inputs??n};for(let e of o.needInputs)r[e.key]=r[e.key]??``;Q.value=r,un.value=null,Z.value=!0;return}if(o.needKey===!0){Z.value=!1,en.value=e,rn.value=t,tn.value=o.label??``,nn.value=o.placeholder??``,an.value={...o.inputs??n},Y.value=``,X.value=null,J.value=!0;return}if(o.state===`success`){Z.value=!1,J.value=!1,l(`已登录${o.accountId?`(${o.accountId})`:``}`,`success`),await U(),await W();return}if(o.state===`failed`){let e=o.error??`登录失败`;if(J.value){X.value=e;return}if(Z.value){un.value=e;return}l(e,`error`);return}Z.value=!1,J.value=!1,mn(e,o),hn(e)},_n=async(e,t)=>{Jt.value=e;try{await gn(e,t,{})}catch(e){l(e?.message??`发起登录失败`,`error`)}finally{Jt.value=null}},vn=c(null),yn=async e=>{let t=E.value;if(t===null){l(`插件服务未开启`,`error`);return}try{await oe.confirm(`确定退出 ${e.id} 的登录?本机保存的凭据会被删除,该供应商的模型将立即不可用(插件与网关配置保留)。`,`确认退出登录`,{confirmButtonText:`退出登录`,cancelButtonText:`取消`,type:`warning`})}catch{return}pn(e.id),K.value===e.id&&(G.value=!1),vn.value=e.id;try{await le(T(t,`/login/${encodeURIComponent(e.id)}`)),l(`已退出 ${e.id} 的登录`,`success`),await U()}catch(e){let t=String(e?.message??``);l(t.includes(`405`)?`当前插件服务版本不支持退出登录,请先升级 ezllm`:e?.message??`退出登录失败`,`error`)}finally{vn.value=null}},bn=async e=>{Yt.value=!1,await _n(Xt.value,e)},xn=async()=>{dn.value=!0;try{await gn(sn.value,ln.value,{...Q.value})}catch(e){un.value=e?.message??`提交失败`}finally{dn.value=!1}},Sn=async()=>{if(Y.value.trim()===``){X.value=`请填写密钥`;return}on.value=!0,X.value=null;try{await gn(en.value,rn.value,{...an.value},Y.value)}catch(e){X.value=e?.message??`提交失败`}finally{on.value=!1}};return ee(Ot),(n,r)=>{let a=p(`el-switch`),c=p(`el-button`),l=p(`el-icon`),u=p(`el-alert`),ee=p(`el-tag`),oe=p(`el-card`),x=p(`el-input`),se=p(`el-form-item`),ce=p(`el-form`),C=p(`el-dialog`),le=p(`el-empty`),ue=p(`el-dropdown-item`),w=p(`el-dropdown-menu`),de=p(`el-dropdown`),fe=p(`el-option`),pe=p(`el-select`);return d(),i(`div`,null,[h(`div`,je,[r[23]||=h(`div`,null,[h(`div`,{class:`page-head-title`},`插件`),h(`div`,{class:`page-head-sub`},` 订阅社区 OpenCode 插件——把订阅账号(CodeBuddy、智谱 Coding Plan 等)当作供应商接入网关 `)],-1),h(`div`,Me,[t(a,{modelValue:b.value,"onUpdate:modelValue":r[0]||=e=>b.value=e,loading:D.value,disabled:!g.value||D.value,"active-text":`插件服务`,"inline-prompt":``,onChange:Ft},null,8,[`modelValue`,`loading`,`disabled`]),t(a,{modelValue:O.value,"onUpdate:modelValue":r[1]||=e=>O.value=e,loading:k.value,disabled:!g.value||k.value,"active-text":`自动更新`,"inline-prompt":``,title:`插件会在后台每小时检查并更新`,onChange:It},null,8,[`modelValue`,`loading`,`disabled`]),t(c,{disabled:!S.value,onClick:Ut},{default:_(()=>[...r[20]||=[o(`已装插件`,-1)]]),_:1},8,[`disabled`]),t(c,{disabled:!S.value,onClick:Ht},{default:_(()=>[t(l,null,{default:_(()=>[t(s(ae))]),_:1}),r[21]||=o(` 手动安装 `,-1)]),_:1},8,[`disabled`]),t(c,{loading:s(ge),onClick:r[2]||=e=>Dt(!0)},{default:_(()=>[...r[22]||=[o(`刷新`,-1)]]),_:1},8,[`loading`])])]),g.value?S.value?m(``,!0):(d(),e(u,{key:1,type:`info`,"show-icon":``,closable:!1,class:`mb-16`,title:`插件服务未开启`},{default:_(()=>[r[25]||=h(`div`,{class:`muted-sm`},`开启后即可安装并加载插件;下方清单仍可浏览。`,-1),t(c,{size:`small`,type:`primary`,class:`mt-8`,loading:D.value,onClick:r[3]||=e=>Pt(!0)},{default:_(()=>[...r[24]||=[o(` 开启插件服务 `,-1)]]),_:1},8,[`loading`])]),_:1})):(d(),e(u,{key:0,type:`warning`,"show-icon":``,closable:!1,class:`mb-16`,title:`插件服务不可用`},{default:_(()=>[o(f(ne.value),1)]),_:1})),s(_e)?(d(),e(u,{key:2,type:`error`,"show-icon":``,closable:!1,class:`mb-16`,title:s(_e)},{default:_(()=>[t(c,{size:`small`,onClick:Dt},{default:_(()=>[...r[26]||=[o(`重试`,-1)]]),_:1})]),_:1},8,[`title`])):m(``,!0),t(oe,{shadow:`never`},{header:_(()=>[h(`div`,Ne,[r[27]||=h(`span`,{class:`fw-600`},`社区插件`,-1),h(`span`,Pe,f(Et.value),1)])]),default:_(()=>[s(ge)&&s(A).length===0?(d(),i(`div`,Fe,`正在从社区清单加载…`)):s(A).length===0?(d(),i(`div`,Ie,` 清单里没有本宿主支持的插件(只收 provider 类),可点右上角「刷新」重试,或用「手动安装」直接按包名安装。 `)):(d(),i(`div`,Le,[(d(!0),i(y,null,v(s(A),t=>(d(),i(`div`,{key:t.package,class:`plugin-card`},[h(`div`,Re,[h(`div`,ze,[H(t)?(d(),i(`img`,{key:0,src:H(t),alt:t.name,onError:e=>Oe(t)},null,40,Be)):(d(),i(`span`,Ve,f(De(t)),1))]),h(`div`,He,[h(`div`,{class:`plugin-name`,title:t.name},f(t.name),9,Ue),h(`div`,We,[t.community?(d(),e(ee,{key:0,size:`small`,type:`info`,"disable-transitions":``},{default:_(()=>[o(f(t.community),1)]),_:2},1024)):m(``,!0)])]),h(`div`,Ge,[V(t)?(d(),e(ee,{key:0,type:`success`,size:`small`,"disable-transitions":``},{default:_(()=>[...r[28]||=[o(`已装`,-1)]]),_:1})):(d(),e(c,{key:1,type:`primary`,size:`small`,disabled:!B.value||P.value===t.package,loading:P.value===t.package,onClick:e=>Lt(t)},{default:_(()=>[...r[29]||=[o(` 安装 `,-1)]]),_:1},8,[`disabled`,`loading`,`onClick`]))])]),h(`div`,{class:`plugin-summary`,title:t.summary??``},f(t.summary??`(清单未提供简介)`),9,Ke),h(`div`,qe,[h(`span`,null,`供应商 `+f(t.providers.length),1),t.weeklyDownloads===null?m(``,!0):(d(),i(`span`,Je,f(ke(t.weeklyDownloads))+`/周`,1)),t.version?(d(),i(`span`,Ye,`v`+f(t.version),1)):(d(),i(`span`,Xe,`版本未知`))])]))),128))]))]),_:1}),t(C,{modelValue:F.value,"onUpdate:modelValue":r[6]||=e=>F.value=e,title:`手动安装插件`,width:`520px`},{footer:_(()=>[t(c,{onClick:r[5]||=e=>F.value=!1},{default:_(()=>[...r[31]||=[o(`取消`,-1)]]),_:1}),t(c,{type:`primary`,loading:Ee.value,disabled:I.value.trim()===``,onClick:Rt},{default:_(()=>[...r[32]||=[o(` 安装 `,-1)]]),_:1},8,[`loading`,`disabled`])]),default:_(()=>[t(ce,{"label-position":`top`},{default:_(()=>[t(se,{label:`npm 包名`},{default:_(()=>[t(x,{modelValue:I.value,"onUpdate:modelValue":r[4]||=e=>I.value=e,placeholder:`例:@magpie-community/opencode-workbuddy-auth`,clearable:``},null,8,[`modelValue`]),r[30]||=h(`div`,{class:`muted-sm`},[o(`可带版本(`),h(`span`,{class:`mono`},`name@1.2.3`),o(`);清单加载不出来时用这个。`)],-1)]),_:1})]),_:1})]),_:1},8,[`modelValue`]),t(C,{modelValue:Te.value,"onUpdate:modelValue":r[9]||=e=>Te.value=e,title:`已装插件`,width:`800px`,top:`6vh`},{footer:_(()=>[t(c,{disabled:!B.value||kt.value,loading:kt.value,title:`把宿主已加载的插件供应商补进网关配置:缺的整条新增,已在的只修地址/协议(模型清单保持你的选择)`,onClick:W},{default:_(()=>[...r[37]||=[o(`接入网关`,-1)]]),_:1},8,[`disabled`,`loading`]),t(c,{disabled:!B.value||!z.value||P.value!==null,loading:R.value,title:Wt.value,onClick:r[7]||=e=>Kt()},{default:_(()=>[...r[38]||=[o(`检查更新`,-1)]]),_:1},8,[`disabled`,`loading`,`title`]),t(c,{onClick:r[8]||=e=>Te.value=!1},{default:_(()=>[...r[39]||=[o(`关闭`,-1)]]),_:1})]),default:_(()=>[N.value?(d(),e(u,{key:0,type:`warning`,"show-icon":``,closable:!1,class:`installed-error`,title:N.value},null,8,[`title`])):m(``,!0),M.value.length===0&&!N.value?(d(),e(le,{key:1,description:`尚未安装任何插件`,"image-size":80})):m(``,!0),M.value.length>0?(d(),i(`div`,Ze,[o(f(M.value.length)+` 个插件 · `+f(Se.value.total)+` 个供应商 · `,1),h(`span`,Qe,f(Se.value.signedIn)+` 个已登录`,1)])):m(``,!0),M.value.length>0?(d(),i(`div`,$e,[(d(!0),i(y,null,v(M.value,n=>(d(),i(`div`,{key:n.package,class:`installed-card`},[h(`div`,et,[h(`div`,tt,f(we(n.package)),1),h(`div`,nt,[h(`div`,rt,[h(`span`,{class:`installed-name`,title:n.package},f(Ce(n.package)),9,it),h(`span`,at,`v`+f(n.version||`-`),1),n.loaded?m(``,!0):(d(),e(ee,{key:0,type:`danger`,size:`small`,"disable-transitions":``},{default:_(()=>[...r[33]||=[o(`加载失败`,-1)]]),_:1}))]),n.description?(d(),i(`div`,{key:0,class:`installed-desc`,title:n.description},f(n.description),9,ot)):m(``,!0),Ce(n.package)===n.package?m(``,!0):(d(),i(`div`,{key:1,class:`mono installed-pkgname`,title:n.package},f(n.package),9,st))]),h(`div`,ct,[Gt(n.package)?(d(),e(c,{key:0,link:``,type:`primary`,size:`small`,disabled:P.value!==null||R.value,loading:P.value===n.package,onClick:e=>qt(n.package)},{default:_(()=>[o(`更新到 `+f(Gt(n.package)),1)]),_:2},1032,[`disabled`,`loading`,`onClick`])):m(``,!0),t(de,{trigger:`click`,placement:`bottom-end`,onCommand:e=>Vt(e,n)},{dropdown:_(()=>[t(w,null,{default:_(()=>[t(ue,{command:`uninstall`,disabled:P.value!==null},{default:_(()=>[...r[34]||=[h(`span`,{class:`danger-text`},`卸载插件`,-1)]]),_:1},8,[`disabled`])]),_:1})]),default:_(()=>[t(c,{link:``,size:`small`,class:`card-more`,title:`更多操作`},{default:_(()=>[t(l,null,{default:_(()=>[t(s(ie))]),_:1})]),_:1})]),_:1},8,[`onCommand`])])]),n.providers.length?(d(),i(`div`,ut,[(d(!0),i(y,null,v(n.providers,t=>(d(),i(`div`,{key:t.id,class:`installed-provider`},[h(`div`,dt,[h(`span`,{class:`mono installed-provider-id`,title:t.id},f(t.id),9,ft),h(`span`,pt,f(t.models)+` 个模型`,1)]),h(`div`,mt,[h(`span`,{class:te([`installed-state`,{"is-on":t.signedIn}])},[h(`span`,{class:te([`status-dot`,{running:t.signedIn}])},null,2),h(`span`,null,f(t.signedIn?`已登录`:t.needsLogin===!1?`无需登录`:`未登录`),1)],2),t.signedIn?(d(),e(c,{key:0,link:``,size:`small`,loading:vn.value===t.id,onClick:e=>yn(t)},{default:_(()=>[...r[35]||=[o(`退出登录`,-1)]]),_:1},8,[`loading`,`onClick`])):t.needsLogin===!1?m(``,!0):(d(),e(c,{key:1,link:``,type:`primary`,size:`small`,loading:Jt.value===t.id,onClick:e=>_n(t.id)},{default:_(()=>[...r[36]||=[o(`登录`,-1)]]),_:1},8,[`loading`,`onClick`]))])]))),128))])):(d(),i(`div`,lt,`未声明可用的供应商`))]))),128))])):m(``,!0)]),_:1},8,[`modelValue`]),t(C,{modelValue:Yt.value,"onUpdate:modelValue":r[11]||=e=>Yt.value=e,title:`选择登录方式`,width:`460px`},{footer:_(()=>[t(c,{onClick:r[10]||=e=>Yt.value=!1},{default:_(()=>[...r[40]||=[o(`取消`,-1)]]),_:1})]),default:_(()=>[r[41]||=h(`div`,{class:`muted-sm`},`该插件提供多种登录方式,选一种:`,-1),h(`div`,ht,[(d(!0),i(y,null,v(Zt.value,t=>(d(),e(c,{key:t.label,class:`method-btn`,onClick:e=>bn(t.label)},{default:_(()=>[o(f(t.label||t.type),1)]),_:2},1032,[`onClick`]))),128))])]),_:1},8,[`modelValue`]),t(C,{modelValue:Z.value,"onUpdate:modelValue":r[13]||=e=>Z.value=e,title:`登录 ${sn.value}`,width:`480px`},{footer:_(()=>[t(c,{onClick:r[12]||=e=>Z.value=!1},{default:_(()=>[...r[42]||=[o(`取消`,-1)]]),_:1}),t(c,{type:`primary`,loading:dn.value,onClick:xn},{default:_(()=>[...r[43]||=[o(`确定`,-1)]]),_:1},8,[`loading`])]),default:_(()=>[(d(!0),i(y,null,v(cn.value,t=>(d(),i(`div`,{key:t.key,class:`field-row`},[h(`div`,gt,f(t.message||t.key),1),t.type===`select`?(d(),e(pe,{key:0,modelValue:Q.value[t.key],"onUpdate:modelValue":e=>Q.value[t.key]=e,class:`mt-8`,placeholder:`请选择`,style:{width:`100%`}},{default:_(()=>[(d(!0),i(y,null,v(t.options,t=>(d(),e(fe,{key:t.value,label:t.label??t.value,value:t.value??``},{default:_(()=>[h(`span`,null,f(t.label??t.value),1),t.hint?(d(),i(`span`,_t,`(`+f(t.hint)+`)`,1)):m(``,!0)]),_:2},1032,[`label`,`value`]))),128))]),_:2},1032,[`modelValue`,`onUpdate:modelValue`])):(d(),e(x,{key:1,modelValue:Q.value[t.key],"onUpdate:modelValue":e=>Q.value[t.key]=e,class:`mt-8`,placeholder:t.placeholder||``,onKeyup:re(xn,[`enter`])},null,8,[`modelValue`,`onUpdate:modelValue`,`placeholder`]))]))),128)),un.value?(d(),e(u,{key:0,type:`error`,"show-icon":``,closable:!1,class:`mt-8`,title:un.value},null,8,[`title`])):m(``,!0)]),_:1},8,[`modelValue`,`title`]),t(C,{modelValue:J.value,"onUpdate:modelValue":r[16]||=e=>J.value=e,title:`登录 ${en.value}`,width:`480px`},{footer:_(()=>[t(c,{onClick:r[15]||=e=>J.value=!1},{default:_(()=>[...r[44]||=[o(`取消`,-1)]]),_:1}),t(c,{type:`primary`,loading:on.value,onClick:Sn},{default:_(()=>[...r[45]||=[o(`确定`,-1)]]),_:1},8,[`loading`])]),default:_(()=>[h(`div`,vt,f(tn.value||`该登录方式需要一枚密钥`),1),t(x,{modelValue:Y.value,"onUpdate:modelValue":r[14]||=e=>Y.value=e,class:`mt-8`,placeholder:nn.value||`粘贴密钥`,"show-password":``,onKeyup:re(Sn,[`enter`])},null,8,[`modelValue`,`placeholder`]),X.value?(d(),e(u,{key:0,type:`error`,"show-icon":``,closable:!1,class:`mt-8`,title:X.value},null,8,[`title`])):m(``,!0)]),_:1},8,[`modelValue`,`title`]),t(C,{modelValue:G.value,"onUpdate:modelValue":r[18]||=e=>G.value=e,title:`登录 ${K.value}`,width:`480px`,onClose:r[19]||=e=>pn(K.value)},{footer:_(()=>[t(c,{onClick:r[17]||=e=>G.value=!1},{default:_(()=>[...r[47]||=[o(`关闭`,-1)]]),_:1})]),default:_(()=>[h(`div`,yt,f($t.value||`正在读取本机已登录的凭据…`),1),Qt.value?(d(),i(`div`,bt,[h(`a`,{class:`el-button el-button--primary`,href:Qt.value,target:`_blank`,rel:`noopener`},` 打开登录页 `,8,xt),r[46]||=h(`div`,{class:`muted-sm mt-8`},`在打开的页面完成登录即可,本页会自动检测(最长等待几分钟)。`,-1)])):m(``,!0),q.value?(d(),e(u,{key:1,type:`error`,"show-icon":``,closable:!1,class:`mt-8`,title:q.value},null,8,[`title`])):(d(),i(`div`,St,`等待登录完成…`))]),_:1},8,[`modelValue`,`title`])])}}}),[[`__scopeId`,`data-v-60925e93`]]);export{wt as default};
|