grix-connector 3.15.4 → 3.16.0
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/adapter/claude/claude-bridge-server.js +1 -1
- package/dist/adapter/claude/claude-tools.js +1 -1
- package/dist/adapter/claude/claude-worker-client.js +1 -1
- package/dist/adapter/claude/mcp-http-launcher.js +2 -2
- package/dist/adapter/claude/result-timeout.js +1 -1
- package/dist/bridge/bridge.js +10 -10
- package/dist/core/access/allowlist-store.js +1 -1
- package/dist/core/admin/admin-server.js +1 -1
- package/dist/core/aibot/client.js +2 -2
- package/dist/core/aibot/connection-handle.js +1 -1
- package/dist/core/file-ops/list-files.js +1 -1
- package/dist/core/provider-quota/index.js +1 -1
- package/dist/core/provider-quota/providers.js +2 -2
- package/dist/core/proxy/index.js +1 -1
- package/dist/core/proxy/relay-credential-fetch.js +1 -0
- package/dist/grix.js +6 -6
- package/dist/log.js +2 -2
- package/dist/manager.js +2 -2
- package/dist/mcp/stream-http/config.js +1 -1
- package/dist/mcp/stream-http/connection-binding.js +1 -1
- package/dist/mcp/stream-http/security.js +1 -1
- package/dist/mcp/stream-http/tool-executor.js +1 -1
- package/dist/mcp/stream-http/tool-registry.js +1 -1
- package/dist/mcp/stream-http/tool-schemas.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readJSONFile as o,writeJSONFileAtomic as
|
|
1
|
+
import{readJSONFile as o,writeJSONFileAtomic as e}from"../util/json-file.js";function n(t){const r=o(t);return r&&typeof r=="object"&&"owners"in r&&Array.isArray(r.owners)?r.owners.filter(i=>typeof i=="string"&&i.trim().length>0):[]}async function s(t,r){await e(t,{owners:r})}export{n as readAllowlist,s as writeAllowlist};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createServer as u}from"node:http";import{log as c}from"../log/logger.js";class g{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;proxyHandler=null;relayCredentialHandler=null;constructor(e){this.token=e}setAgentHandler(e){this.handler=e}setUpgradeHandler(e){this.upgradeHandler=e}setProbeHandler(e){this.probeHandler=e}setInstallHandler(e){this.installHandler=e}setProxyHandler(e){this.proxyHandler=e}setRelayCredentialHandler(e){this.relayCredentialHandler=e}async start(e){return new Promise((n,t)=>{this.server=u((r,o)=>this.handleRequest(r,o)),this.server.listen(e,"127.0.0.1",()=>{c.info("admin",`Listening on 127.0.0.1:${e}`),n()}),this.server.on("error",t)})}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>e())})}handleRequest(e,n){const t=e.method??"",r=e.url??"";if(r==="/api/agents"&&t==="GET")this.handleList(n);else if(r==="/api/agents"&&t==="POST")this.readBody(e).then(o=>this.handleAdd(n,o)).catch(o=>this.error(n,o));else if(t==="DELETE"&&r.startsWith("/api/agents/")){const o=decodeURIComponent(r.slice(12));this.handleRemove(n,o)}else if(t==="POST"&&r.match(/^\/api\/agents\/[^/]+\/restart$/)){const o=decodeURIComponent(r.slice(12,r.lastIndexOf("/restart")));this.handleRestart(n,o)}else if(r==="/api/reload"&&t==="POST")this.handleReload(n);else if(r==="/api/upgrade"&&t==="GET")this.handleCheckUpgrade(n);else if(r==="/api/upgrade"&&t==="POST")this.handleTriggerUpgrade(n);else if(t==="GET"&&r.startsWith("/api/probe"))this.handleProbe(e,n,r);else if(r==="/api/install"&&t==="GET")this.handleInstallList(n);else if(r==="/api/install"&&t==="POST")this.readBody(e).then(o=>this.handleInstall(n,o)).catch(o=>this.error(n,o));else if(t==="GET"&&r.startsWith("/api/install/")){const o=decodeURIComponent(r.slice(13));this.handleInstallProgress(n,o)}else if(r==="/api/proxy"&&t==="GET")this.handleProxyStatus(n);else if(t==="PUT"&&r.match(/^\/api\/proxy\/agents\/[^/]+\/enabled$/)){const o=decodeURIComponent(r.slice(18,-8));this.readBody(e).then(s=>this.handleProxySetAgentRelay(n,o,s)).catch(s=>this.error(n,s))}else if(t==="PUT"&&r.match(/^\/api\/proxy\/agents\/[^/]+\/relay-credential$/)){const o=decodeURIComponent(r.slice(18,-17));this.readBody(e).then(s=>this.handleProxySetAgentRelayCredential(n,o,s)).catch(s=>this.error(n,s))}else if(r==="/api/proxy/hermes-profiles"&&t==="GET")this.handleHermesProfileList(n);else if(t==="PUT"&&r.match(/^\/api\/proxy\/relay-credential\/[^/]+$/)){const o=decodeURIComponent(r.slice(28));this.readBody(e).then(s=>this.handleRelayCredentialEnable(n,o,s)).catch(s=>this.error(n,s))}else if(t==="DELETE"&&r.match(/^\/api\/proxy\/relay-credential\/[^/]+$/)){const o=decodeURIComponent(r.slice(28));this.handleRelayCredentialDisable(n,o)}else if(t==="PUT"&&r==="/api/proxy/enabled")this.readBody(e).then(o=>this.handleProxySetEnabled(n,o)).catch(o=>this.error(n,o));else if(t==="PUT"&&r==="/api/proxy/default-route")this.readBody(e).then(o=>this.handleProxySetDefaultRoute(n,o)).catch(o=>this.error(n,o));else if(t==="PUT"&&r==="/api/proxy/intercept-hosts")this.readBody(e).then(o=>this.handleProxySetInterceptHosts(n,o)).catch(o=>this.error(n,o));else if(t==="PUT"&&r.match(/^\/api\/proxy\/routes\/[^/]+$/)){const o=decodeURIComponent(r.slice(18));this.readBody(e).then(s=>this.handleProxySetRoute(n,o,s)).catch(s=>this.error(n,s))}else if(t==="DELETE"&&r.startsWith("/api/proxy/routes/")){const o=decodeURIComponent(r.slice(18));this.handleProxyDeleteRoute(n,o)}else this.json(n,404,{error:"not_found"})}ensureRelayCredential(e){return this.relayCredentialHandler?!0:(this.json(e,501,{error:"relay credential handler not configured"}),!1)}handleHermesProfileList(e){this.ensureRelayCredential(e)&&this.relayCredentialHandler.listHermesProfiles().then(n=>this.json(e,200,{profiles:n})).catch(n=>this.error(e,n))}async handleRelayCredentialEnable(e,n,t){if(!this.ensureRelayCredential(e))return;const r=t,o=s=>typeof s=="string"&&s.trim()?s.trim():void 0;try{const s=await this.relayCredentialHandler.enable(n.trim(),{virtualKey:o(r?.virtual_key)??"",anthropicBaseUrl:o(r?.anthropic_base_url),openaiBaseUrl:o(r?.openai_base_url),model:o(r?.model)});this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}handleRelayCredentialDisable(e,n){this.ensureRelayCredential(e)&&this.relayCredentialHandler.disable(n.trim()).then(t=>this.json(e,200,t??{ok:!0})).catch(t=>this.error(e,t))}ensureProxy(e){return this.proxyHandler?!0:(this.json(e,501,{error:"proxy not configured"}),!1)}handleProxyStatus(e){if(this.ensureProxy(e))try{this.json(e,200,this.proxyHandler.status())}catch(n){this.error(e,n)}}async handleProxySetRoute(e,n,t){if(!this.ensureProxy(e))return;const r=t;if(!r||typeof r.targetBaseUrl!="string"||!r.targetBaseUrl.trim()){this.json(e,400,{error:"targetBaseUrl is required"});return}try{new URL(r.targetBaseUrl)}catch{this.json(e,400,{error:"targetBaseUrl must be a valid URL"});return}if(r.headers!==void 0&&!this.isStringRecord(r.headers)){this.json(e,400,{error:"headers must be a string map"});return}if(r.model!==void 0&&typeof r.model!="string"){this.json(e,400,{error:"model must be a string"});return}if(r.modelMap!==void 0&&!this.isStringRecord(r.modelMap)){this.json(e,400,{error:"modelMap must be a string map"});return}if(r.passthrough!==void 0&&typeof r.passthrough!="boolean"){this.json(e,400,{error:"passthrough must be a boolean"});return}if(r.relayOnly!==void 0&&typeof r.relayOnly!="boolean"){this.json(e,400,{error:"relayOnly must be a boolean"});return}if(r.codexResponsesToChat!==void 0&&typeof r.codexResponsesToChat!="boolean"){this.json(e,400,{error:"codexResponsesToChat must be a boolean"});return}if(r.passthrough===!0&&r.codexResponsesToChat===!0){this.json(e,400,{error:"passthrough and codexResponsesToChat are mutually exclusive"});return}try{const o=await this.proxyHandler.setRoute(n,t);this.json(e,200,o??{ok:!0})}catch(o){this.error(e,o)}}handleProxyDeleteRoute(e,n){this.ensureProxy(e)&&this.proxyHandler.deleteRoute(n).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}async handleProxySetAgentRelay(e,n,t){if(!this.ensureProxy(e))return;const r=n.trim();if(!r){this.json(e,400,{error:"agent name is required"});return}const o=t;if(typeof o?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}try{const s=await this.proxyHandler.setAgentRelay(r,o.enabled);this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}async handleProxySetAgentRelayCredential(e,n,t){if(!this.ensureProxy(e))return;const r=n.trim();if(!r){this.json(e,400,{error:"agent name is required",code:"INVALID_ARGUMENT"});return}const o=t;if(typeof o?.agent_id!="string"||!o.agent_id.trim()){this.json(e,400,{error:"agent_id is required",code:"MISSING_AGENT_ID"});return}if(typeof o?.virtual_key!="string"||!o.virtual_key.trim()){this.json(e,400,{error:"virtual_key is required",code:"MISSING_VIRTUAL_KEY"});return}if(o.anthropic_base_url!==void 0&&typeof o.anthropic_base_url!="string"){this.json(e,400,{error:"anthropic_base_url must be a string",code:"INVALID_ARGUMENT"});return}if(o.openai_base_url!==void 0&&typeof o.openai_base_url!="string"){this.json(e,400,{error:"openai_base_url must be a string",code:"INVALID_ARGUMENT"});return}if(o.model!==void 0&&typeof o.model!="string"){this.json(e,400,{error:"model must be a string",code:"INVALID_ARGUMENT"});return}try{const s=await this.proxyHandler.setAgentRelayCredential(r,{agentId:o.agent_id,virtualKey:o.virtual_key,anthropicBaseUrl:typeof o.anthropic_base_url=="string"?o.anthropic_base_url:void 0,openaiBaseUrl:typeof o.openai_base_url=="string"?o.openai_base_url:void 0,model:typeof o.model=="string"?o.model:void 0});this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}async handleProxySetEnabled(e,n){if(!this.ensureProxy(e))return;const t=n;if(typeof t?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}if(t.enabled){this.json(e,400,{error:'relay is per-agent; use PUT /api/proxy/agents/<name>/enabled {"enabled":true}'});return}try{const r=await this.proxyHandler.disableAll();this.json(e,200,r??{ok:!0})}catch(r){this.error(e,r)}}async handleProxySetDefaultRoute(e,n){if(!this.ensureProxy(e))return;const r=n?.routeKey??null;if(r!==null&&typeof r!="string"){this.json(e,400,{error:"routeKey must be a string or null"});return}try{const o=await this.proxyHandler.setDefaultRoute(r);this.json(e,200,o??{ok:!0})}catch(o){this.error(e,o)}}async handleProxySetInterceptHosts(e,n){if(!this.ensureProxy(e))return;const t=n;if(!Array.isArray(t?.hosts)||!t.hosts.every(r=>typeof r=="string")){this.json(e,400,{error:"hosts must be a string array"});return}try{const r=await this.proxyHandler.setInterceptHosts(t.hosts);this.json(e,200,r??{ok:!0})}catch(r){this.error(e,r)}}isStringRecord(e){return typeof e!="object"||e===null||Array.isArray(e)?!1:Object.values(e).every(n=>typeof n=="string")}handleList(e){try{const n=this.handler?.list()??[];this.json(e,200,n)}catch(n){this.error(e,n)}}async handleAdd(e,n){try{const t=await this.handler.add(n);this.json(e,201,t??{ok:!0})}catch(t){this.error(e,t)}}handleRemove(e,n){this.handler.remove(n).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}handleRestart(e,n){this.handler.restart(n).then(()=>{this.json(e,200,{ok:!0})}).catch(t=>this.error(e,t))}handleReload(e){this.handler.reload().then(n=>{this.json(e,200,{ok:!0,result:n})}).catch(n=>this.error(e,n))}handleCheckUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.check().then(n=>{this.json(e,200,n)}).catch(n=>this.error(e,n))}handleTriggerUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.trigger(),this.json(e,200,{ok:!0,message:"upgrade check triggered"})}error(e,n){const t=n;t.code==="NOT_FOUND"?this.json(e,404,{error:t.message??"not found"}):t.code==="PROFILE_NOT_FOUND"?this.json(e,404,{error:t.message,code:t.code}):t.code==="PROXY_UNAVAILABLE"?this.json(e,503,{error:t.message,code:t.code}):t.code==="AMBIGUOUS_AGENT_ID"?this.json(e,409,{error:t.message,code:t.code}):t.code==="HERMES_NOT_FOUND"||t.code==="HERMES_IMPORT_FAILED"||t.code==="SCRIPT_MISSING"||t.code==="SCRIPT_FAILED"||t.code==="CONFIG_WRITE_FAILED"||t.code==="CONFIG_WRITE_VERIFY_FAILED"||t.code==="CONFIG_UNREADABLE"?this.json(e,500,{error:t.message,code:t.code}):t.code==="RELOAD_UNSAFE"||t.code==="NO_RELAY_ROUTE"||t.code==="AGENT_ID_MISMATCH"?this.json(e,409,{error:t.message,code:t.code}):t.code==="UNKNOWN_AGENT"||t.code==="UNSUPPORTED_OS"||t.code==="UNSUPPORTED_CLIENT_TYPE"||t.code==="MISSING_AGENT_ID"||t.code==="MISSING_VIRTUAL_KEY"||t.code==="MISSING_TARGET_BASE_URL"||t.code==="MISSING_MODEL"||t.code==="MISSING_BASE_URL"||t.code==="MISSING_API_KEY"||t.code==="INVALID_BASE_URL"||t.code==="BAD_REQUEST"||t.code==="INVALID_TARGET_BASE_URL"||t.code==="INVALID_ARGUMENT"?this.json(e,400,{error:t.message,code:t.code}):t.code==="ALREADY_INSTALLED"||t.code==="INSTALL_IN_PROGRESS"?this.json(e,409,{error:t.message,code:t.code}):t.code==="INSTALL_FAILED"||t.code==="INSTALL_TIMEOUT"||t.code==="PREFLIGHT_FAILED"||t.code==="VERIFICATION_FAILED"||t.code==="PREREQ_MISSING"||t.code==="PREREQ_INSTALL_FAILED"||t.code==="FALLBACK_EXHAUSTED"||t.code==="ENVIRONMENT_UNSUPPORTED"?this.json(e,500,{error:t.message,code:t.code}):(c.error("admin",`Handler error: ${t.message??n}`),this.json(e,500,{error:t.message??"internal error"}))}json(e,n,t){const r=JSON.stringify(t);e.writeHead(n,{"Content-Type":"application/json"}),e.end(r)}readBody(e){return new Promise((n,t)=>{let r="";e.setEncoding("utf8"),e.on("data",o=>{r+=o}),e.on("end",()=>{try{n(JSON.parse(r))}catch{t(new Error("invalid JSON body"))}}),e.on("error",t)})}handleProbe(e,n,t){if(!this.probeHandler){this.json(n,501,{error:"probe not configured"});return}const r=t.indexOf("?"),o=r>=0?t.slice(0,r):t,s=r>=0?new URLSearchParams(t.slice(r+1)):new URLSearchParams,a={};s.get("conversation")==="true"&&(a.conversation=!0),s.get("fresh")==="true"&&(a.fresh=!0);const l=Number(s.get("timeoutMs"));Number.isFinite(l)&&l>0&&(a.timeoutMs=l);const h=o.match(/^\/api\/probe\/(.+)$/);if(h){const i=decodeURIComponent(h[1]);this.probeHandler.probeOne(i,a).then(d=>{this.json(n,200,d)}).catch(d=>this.error(n,d));return}this.probeHandler.probeAll(a).then(i=>{this.json(n,200,i)}).catch(i=>this.error(n,i))}handleInstallList(e){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const n=this.installHandler.listInstallable();this.json(e,200,n)}catch(n){this.error(e,n)}}handleInstallProgress(e,n){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}const t=this.installHandler.getInstallProgress(n);if(!t){this.json(e,200,{agentType:n,status:"unknown",inProgress:!1,progress:null});return}let r,o,s;switch(t.phase){case"completed":r="done",o="\u5B89\u88C5\u5B8C\u6210";break;case"failed":r="error",s=t.outputTail||"\u5B89\u88C5\u5931\u8D25";break;case"preflight":r="pending",o="\u68C0\u67E5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing_prereq":r="downloading",o=t.currentPrereq?`\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56: ${t.currentPrereq}`:"\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing":r="installing",o=`\u6B63\u5728\u5B89\u88C5 ${n}...`;break;case"verifying":r="installing",o="\u9A8C\u8BC1\u5B89\u88C5...";break;default:r="unknown"}this.json(e,200,{agentType:n,status:r,inProgress:!0,progress:t.elapsedMs?Math.min(.9,t.elapsedMs/3e4):.1,message:o,error:s})}async handleInstall(e,n){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const t=n;if(!t||typeof t.agentType!="string"||!t.agentType){this.json(e,400,{error:"agentType is required"});return}const r=await this.installHandler.installAgent(t);if(r.ok)this.json(e,200,r);else{const o=r.error?.code;o==="UNKNOWN_AGENT"||o==="UNSUPPORTED_OS"?this.json(e,400,r):o==="INSTALL_IN_PROGRESS"?this.json(e,409,r):this.json(e,500,r)}}catch(t){this.error(e,t)}}}export{g as AdminServer};
|
|
1
|
+
import{createServer as u}from"node:http";import{log as c}from"../log/logger.js";import{RelayFetchError as p}from"../proxy/relay-credential-fetch.js";class _{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;proxyHandler=null;relayCredentialHandler=null;constructor(e){this.token=e}setAgentHandler(e){this.handler=e}setUpgradeHandler(e){this.upgradeHandler=e}setProbeHandler(e){this.probeHandler=e}setInstallHandler(e){this.installHandler=e}setProxyHandler(e){this.proxyHandler=e}setRelayCredentialHandler(e){this.relayCredentialHandler=e}async start(e){return new Promise((r,t)=>{this.server=u((n,o)=>this.handleRequest(n,o)),this.server.listen(e,"127.0.0.1",()=>{c.info("admin",`Listening on 127.0.0.1:${e}`),r()}),this.server.on("error",t)})}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>e())})}handleRequest(e,r){const t=e.method??"",n=e.url??"";if(n==="/api/agents"&&t==="GET")this.handleList(r);else if(n==="/api/agents"&&t==="POST")this.readBody(e).then(o=>this.handleAdd(r,o)).catch(o=>this.error(r,o));else if(t==="DELETE"&&n.startsWith("/api/agents/")){const o=decodeURIComponent(n.slice(12));this.handleRemove(r,o)}else if(t==="POST"&&n.match(/^\/api\/agents\/[^/]+\/restart$/)){const o=decodeURIComponent(n.slice(12,n.lastIndexOf("/restart")));this.handleRestart(r,o)}else if(n==="/api/reload"&&t==="POST")this.handleReload(r);else if(n==="/api/upgrade"&&t==="GET")this.handleCheckUpgrade(r);else if(n==="/api/upgrade"&&t==="POST")this.handleTriggerUpgrade(r);else if(t==="GET"&&n.startsWith("/api/probe"))this.handleProbe(e,r,n);else if(n==="/api/install"&&t==="GET")this.handleInstallList(r);else if(n==="/api/install"&&t==="POST")this.readBody(e).then(o=>this.handleInstall(r,o)).catch(o=>this.error(r,o));else if(t==="GET"&&n.startsWith("/api/install/")){const o=decodeURIComponent(n.slice(13));this.handleInstallProgress(r,o)}else if(n==="/api/proxy"&&t==="GET")this.handleProxyStatus(r);else if(t==="PUT"&&n.match(/^\/api\/proxy\/agents\/[^/]+\/enabled$/)){const o=decodeURIComponent(n.slice(18,-8));this.readBody(e).then(s=>this.handleProxySetAgentRelay(r,o,s)).catch(s=>this.error(r,s))}else if(t==="PUT"&&n.match(/^\/api\/proxy\/agents\/[^/]+\/relay-credential$/)){const o=decodeURIComponent(n.slice(18,-17));this.readBody(e).then(s=>this.handleProxySetAgentRelayCredential(r,o,s)).catch(s=>this.error(r,s))}else if(n==="/api/proxy/hermes-profiles"&&t==="GET")this.handleHermesProfileList(r);else if(t==="PUT"&&n.match(/^\/api\/proxy\/relay-credential\/[^/]+$/)){const o=decodeURIComponent(n.slice(28));this.readBody(e).then(s=>this.handleRelayCredentialEnable(r,o,s)).catch(s=>this.error(r,s))}else if(t==="DELETE"&&n.match(/^\/api\/proxy\/relay-credential\/[^/]+$/)){const o=decodeURIComponent(n.slice(28));this.handleRelayCredentialDisable(r,o)}else if(t==="PUT"&&n==="/api/proxy/enabled")this.readBody(e).then(o=>this.handleProxySetEnabled(r,o)).catch(o=>this.error(r,o));else if(t==="PUT"&&n==="/api/proxy/default-route")this.readBody(e).then(o=>this.handleProxySetDefaultRoute(r,o)).catch(o=>this.error(r,o));else if(t==="PUT"&&n==="/api/proxy/intercept-hosts")this.readBody(e).then(o=>this.handleProxySetInterceptHosts(r,o)).catch(o=>this.error(r,o));else if(t==="PUT"&&n.match(/^\/api\/proxy\/routes\/[^/]+$/)){const o=decodeURIComponent(n.slice(18));this.readBody(e).then(s=>this.handleProxySetRoute(r,o,s)).catch(s=>this.error(r,s))}else if(t==="DELETE"&&n.startsWith("/api/proxy/routes/")){const o=decodeURIComponent(n.slice(18));this.handleProxyDeleteRoute(r,o)}else this.json(r,404,{error:"not_found"})}ensureRelayCredential(e){return this.relayCredentialHandler?!0:(this.json(e,501,{error:"relay credential handler not configured"}),!1)}handleHermesProfileList(e){this.ensureRelayCredential(e)&&this.relayCredentialHandler.listHermesProfiles().then(r=>this.json(e,200,{profiles:r})).catch(r=>this.error(e,r))}async handleRelayCredentialEnable(e,r,t){if(!this.ensureRelayCredential(e))return;const n=t,o=s=>typeof s=="string"&&s.trim()?s.trim():void 0;try{const s=await this.relayCredentialHandler.enable(r.trim(),{virtualKey:o(n?.virtual_key)??"",anthropicBaseUrl:o(n?.anthropic_base_url),openaiBaseUrl:o(n?.openai_base_url),model:o(n?.model)});this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}handleRelayCredentialDisable(e,r){this.ensureRelayCredential(e)&&this.relayCredentialHandler.disable(r.trim()).then(t=>this.json(e,200,t??{ok:!0})).catch(t=>this.error(e,t))}ensureProxy(e){return this.proxyHandler?!0:(this.json(e,501,{error:"proxy not configured"}),!1)}handleProxyStatus(e){if(this.ensureProxy(e))try{this.json(e,200,this.proxyHandler.status())}catch(r){this.error(e,r)}}async handleProxySetRoute(e,r,t){if(!this.ensureProxy(e))return;const n=t;if(!n||typeof n.targetBaseUrl!="string"||!n.targetBaseUrl.trim()){this.json(e,400,{error:"targetBaseUrl is required"});return}try{new URL(n.targetBaseUrl)}catch{this.json(e,400,{error:"targetBaseUrl must be a valid URL"});return}if(n.headers!==void 0&&!this.isStringRecord(n.headers)){this.json(e,400,{error:"headers must be a string map"});return}if(n.model!==void 0&&typeof n.model!="string"){this.json(e,400,{error:"model must be a string"});return}if(n.modelMap!==void 0&&!this.isStringRecord(n.modelMap)){this.json(e,400,{error:"modelMap must be a string map"});return}if(n.passthrough!==void 0&&typeof n.passthrough!="boolean"){this.json(e,400,{error:"passthrough must be a boolean"});return}if(n.relayOnly!==void 0&&typeof n.relayOnly!="boolean"){this.json(e,400,{error:"relayOnly must be a boolean"});return}if(n.codexResponsesToChat!==void 0&&typeof n.codexResponsesToChat!="boolean"){this.json(e,400,{error:"codexResponsesToChat must be a boolean"});return}if(n.passthrough===!0&&n.codexResponsesToChat===!0){this.json(e,400,{error:"passthrough and codexResponsesToChat are mutually exclusive"});return}try{const o=await this.proxyHandler.setRoute(r,t);this.json(e,200,o??{ok:!0})}catch(o){this.error(e,o)}}handleProxyDeleteRoute(e,r){this.ensureProxy(e)&&this.proxyHandler.deleteRoute(r).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}async handleProxySetAgentRelay(e,r,t){if(!this.ensureProxy(e))return;const n=r.trim();if(!n){this.json(e,400,{error:"agent name is required"});return}const o=t;if(typeof o?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}if(o.model!==void 0&&typeof o.model!="string"){this.json(e,400,{error:"model must be a string",code:"INVALID_ARGUMENT"});return}try{const s=await this.proxyHandler.setAgentRelay(n,o.enabled,typeof o.model=="string"&&o.model.trim()?o.model.trim():void 0);this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}async handleProxySetAgentRelayCredential(e,r,t){if(!this.ensureProxy(e))return;const n=r.trim();if(!n){this.json(e,400,{error:"agent name is required",code:"INVALID_ARGUMENT"});return}const o=t;if(typeof o?.agent_id!="string"||!o.agent_id.trim()){this.json(e,400,{error:"agent_id is required",code:"MISSING_AGENT_ID"});return}if(typeof o?.virtual_key!="string"||!o.virtual_key.trim()){this.json(e,400,{error:"virtual_key is required",code:"MISSING_VIRTUAL_KEY"});return}if(o.anthropic_base_url!==void 0&&typeof o.anthropic_base_url!="string"){this.json(e,400,{error:"anthropic_base_url must be a string",code:"INVALID_ARGUMENT"});return}if(o.openai_base_url!==void 0&&typeof o.openai_base_url!="string"){this.json(e,400,{error:"openai_base_url must be a string",code:"INVALID_ARGUMENT"});return}if(o.model!==void 0&&typeof o.model!="string"){this.json(e,400,{error:"model must be a string",code:"INVALID_ARGUMENT"});return}try{const s=await this.proxyHandler.setAgentRelayCredential(n,{agentId:o.agent_id,virtualKey:o.virtual_key,anthropicBaseUrl:typeof o.anthropic_base_url=="string"?o.anthropic_base_url:void 0,openaiBaseUrl:typeof o.openai_base_url=="string"?o.openai_base_url:void 0,model:typeof o.model=="string"?o.model:void 0});this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}async handleProxySetEnabled(e,r){if(!this.ensureProxy(e))return;const t=r;if(typeof t?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}if(t.enabled){this.json(e,400,{error:'relay is per-agent; use PUT /api/proxy/agents/<name>/enabled {"enabled":true}'});return}try{const n=await this.proxyHandler.disableAll();this.json(e,200,n??{ok:!0})}catch(n){this.error(e,n)}}async handleProxySetDefaultRoute(e,r){if(!this.ensureProxy(e))return;const n=r?.routeKey??null;if(n!==null&&typeof n!="string"){this.json(e,400,{error:"routeKey must be a string or null"});return}try{const o=await this.proxyHandler.setDefaultRoute(n);this.json(e,200,o??{ok:!0})}catch(o){this.error(e,o)}}async handleProxySetInterceptHosts(e,r){if(!this.ensureProxy(e))return;const t=r;if(!Array.isArray(t?.hosts)||!t.hosts.every(n=>typeof n=="string")){this.json(e,400,{error:"hosts must be a string array"});return}try{const n=await this.proxyHandler.setInterceptHosts(t.hosts);this.json(e,200,n??{ok:!0})}catch(n){this.error(e,n)}}isStringRecord(e){return typeof e!="object"||e===null||Array.isArray(e)?!1:Object.values(e).every(r=>typeof r=="string")}handleList(e){try{const r=this.handler?.list()??[];this.json(e,200,r)}catch(r){this.error(e,r)}}async handleAdd(e,r){try{const t=await this.handler.add(r);this.json(e,201,t??{ok:!0})}catch(t){this.error(e,t)}}handleRemove(e,r){this.handler.remove(r).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}handleRestart(e,r){this.handler.restart(r).then(()=>{this.json(e,200,{ok:!0})}).catch(t=>this.error(e,t))}handleReload(e){this.handler.reload().then(r=>{this.json(e,200,{ok:!0,result:r})}).catch(r=>this.error(e,r))}handleCheckUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.check().then(r=>{this.json(e,200,r)}).catch(r=>this.error(e,r))}handleTriggerUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.trigger(),this.json(e,200,{ok:!0,message:"upgrade check triggered"})}error(e,r){const t=r;if(t.code==="NOT_FOUND")this.json(e,404,{error:t.message??"not found"});else if(t.code==="PROFILE_NOT_FOUND")this.json(e,404,{error:t.message,code:t.code});else if(t.code==="PROXY_UNAVAILABLE")this.json(e,503,{error:t.message,code:t.code});else if(t.code==="AMBIGUOUS_AGENT_ID")this.json(e,409,{error:t.message,code:t.code});else if(t.code==="HERMES_NOT_FOUND"||t.code==="HERMES_IMPORT_FAILED"||t.code==="SCRIPT_MISSING"||t.code==="SCRIPT_FAILED"||t.code==="CONFIG_WRITE_FAILED"||t.code==="CONFIG_WRITE_VERIFY_FAILED"||t.code==="CONFIG_UNREADABLE")this.json(e,500,{error:t.message,code:t.code});else if(t.code==="RELOAD_UNSAFE"||t.code==="NO_RELAY_ROUTE"||t.code==="AGENT_ID_MISMATCH")this.json(e,409,{error:t.message,code:t.code});else if(t.code==="UNKNOWN_AGENT"||t.code==="UNSUPPORTED_OS"||t.code==="UNSUPPORTED_CLIENT_TYPE"||t.code==="MISSING_AGENT_ID"||t.code==="MISSING_VIRTUAL_KEY"||t.code==="MISSING_TARGET_BASE_URL"||t.code==="MISSING_MODEL"||t.code==="MISSING_BASE_URL"||t.code==="MISSING_API_KEY"||t.code==="INVALID_BASE_URL"||t.code==="BAD_REQUEST"||t.code==="INVALID_TARGET_BASE_URL"||t.code==="INVALID_ARGUMENT")this.json(e,400,{error:t.message,code:t.code});else if(t.code==="ALREADY_INSTALLED"||t.code==="INSTALL_IN_PROGRESS")this.json(e,409,{error:t.message,code:t.code});else if(r instanceof p){const n=r.bizCode;r.code==="OFFLINE"?this.json(e,503,{error:t.message,code:"RELAY_WS_OFFLINE"}):r.code==="TIMEOUT"?this.json(e,504,{error:t.message,code:"RELAY_CREDENTIAL_TIMEOUT"}):r.code==="CANCELLED"?this.json(e,409,{error:t.message,code:"RELAY_TOGGLE_CANCELLED"}):r.code==="UNSUPPORTED"?this.json(e,400,{error:t.message,code:"RELAY_UNSUPPORTED"}):this.json(e,502,{error:t.message,code:"RELAY_CREDENTIAL_FAILED",...n?{biz_code:n}:{}})}else t.code==="INSTALL_FAILED"||t.code==="INSTALL_TIMEOUT"||t.code==="PREFLIGHT_FAILED"||t.code==="VERIFICATION_FAILED"||t.code==="PREREQ_MISSING"||t.code==="PREREQ_INSTALL_FAILED"||t.code==="FALLBACK_EXHAUSTED"||t.code==="ENVIRONMENT_UNSUPPORTED"?this.json(e,500,{error:t.message,code:t.code}):(c.error("admin",`Handler error: ${t.message??r}`),this.json(e,500,{error:t.message??"internal error"}))}json(e,r,t){const n=JSON.stringify(t);e.writeHead(r,{"Content-Type":"application/json"}),e.end(n)}readBody(e){return new Promise((r,t)=>{let n="";e.setEncoding("utf8"),e.on("data",o=>{n+=o}),e.on("end",()=>{try{r(JSON.parse(n))}catch{t(new Error("invalid JSON body"))}}),e.on("error",t)})}handleProbe(e,r,t){if(!this.probeHandler){this.json(r,501,{error:"probe not configured"});return}const n=t.indexOf("?"),o=n>=0?t.slice(0,n):t,s=n>=0?new URLSearchParams(t.slice(n+1)):new URLSearchParams,i={};s.get("conversation")==="true"&&(i.conversation=!0),s.get("fresh")==="true"&&(i.fresh=!0);const l=Number(s.get("timeoutMs"));Number.isFinite(l)&&l>0&&(i.timeoutMs=l);const h=o.match(/^\/api\/probe\/(.+)$/);if(h){const a=decodeURIComponent(h[1]);this.probeHandler.probeOne(a,i).then(d=>{this.json(r,200,d)}).catch(d=>this.error(r,d));return}this.probeHandler.probeAll(i).then(a=>{this.json(r,200,a)}).catch(a=>this.error(r,a))}handleInstallList(e){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const r=this.installHandler.listInstallable();this.json(e,200,r)}catch(r){this.error(e,r)}}handleInstallProgress(e,r){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}const t=this.installHandler.getInstallProgress(r);if(!t){this.json(e,200,{agentType:r,status:"unknown",inProgress:!1,progress:null});return}let n,o,s;switch(t.phase){case"completed":n="done",o="\u5B89\u88C5\u5B8C\u6210";break;case"failed":n="error",s=t.outputTail||"\u5B89\u88C5\u5931\u8D25";break;case"preflight":n="pending",o="\u68C0\u67E5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing_prereq":n="downloading",o=t.currentPrereq?`\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56: ${t.currentPrereq}`:"\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing":n="installing",o=`\u6B63\u5728\u5B89\u88C5 ${r}...`;break;case"verifying":n="installing",o="\u9A8C\u8BC1\u5B89\u88C5...";break;default:n="unknown"}this.json(e,200,{agentType:r,status:n,inProgress:!0,progress:t.elapsedMs?Math.min(.9,t.elapsedMs/3e4):.1,message:o,error:s})}async handleInstall(e,r){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const t=r;if(!t||typeof t.agentType!="string"||!t.agentType){this.json(e,400,{error:"agentType is required"});return}const n=await this.installHandler.installAgent(t);if(n.ok)this.json(e,200,n);else{const o=n.error?.code;o==="UNKNOWN_AGENT"||o==="UNSUPPORTED_OS"?this.json(e,400,n):o==="INSTALL_IN_PROGRESS"?this.json(e,409,n):this.json(e,500,n)}}catch(t){this.error(e,t)}}}export{_ as AdminServer};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{EventEmitter as $}from"node:events";import{randomUUID as b}from"node:crypto";import k from"node:os";import m from"ws";import{log as a}from"../log/index.js";import{getMachineName as w}from"../util/index.js";import{detectTailnetIPv4 as E,ensureServerAndGetPort as q,getFileServerHttpsPort as
|
|
2
|
-
`}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||a.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==m.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueReorderResult(e){this.sendPacket("queue_reorder_result",e)}sendEventHoldResult(e){this.sendPacket("event_hold_result",e)}sendQueueEditResult(e){this.sendPacket("queue_edit_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,s=15e3){return new Promise((i,n)=>{const h=b(),c=Math.max(1e3,Math.min(s,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(h),n(new Error(`agent_invoke timeout: ${e}`))},c);this.pendingInvokes.set(h,{resolve:i,reject:n,timer:o}),this.sendPacket("agent_invoke",{invoke_id:h,action:e,params:t,timeout_ms:c})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,s){return new Promise((i,n)=>{const h=++this.seq,c=setTimeout(()=>{this.pendingRequests.delete(h),n(new A(`request timeout: ${e} (expected ${s.expected.join("/")})`))},s.timeoutMs);this.pendingRequests.set(h,{expected:s.expected,resolve:i,reject:n,timer:c}),this.sendPacket(e,t,h)||(this.pendingRequests.delete(h),clearTimeout(c),n(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,s=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:s})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){a.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0,a.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let e=0;for(;this.reconnecting;){const t=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(t*.2*Math.random());if(this.reconnectAttempts++,await new Promise(i=>setTimeout(i,t+s)),!this.reconnecting)return;try{await this.connect();const i=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,a.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${i}`);return}catch(i){if(this.ws){try{this.ws.close()}catch{}this.ws=null}const n=i instanceof Error?i.message:String(i);if(a.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${n}`),i instanceof v){this.agentDeleted=!0,this.reconnecting=!1,a.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:p});return}if(/Auth failed/i.test(n)){if(e++,e>=f.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,a.error("aibot",`reconnect giving up after ${e} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else e=0}}}sendPacket(e,t,s){if(this.ws&&this.ws.readyState===m.OPEN){const i=this.ws.bufferedAmount>f.BACKPRESSURE_THRESHOLD;if(!i||!f.DROPPABLE_COMMANDS.has(e)){if(i&&f.DROPPABLE_COMMANDS.has(e))return!1;const n=s??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const c=t.event_id;if(c&&(this.seqEventMap.set(n,c),this.seqEventMap.size>200)){const o=this.seqEventMap.keys().next().value;o!==void 0&&this.seqEventMap.delete(o)}}const h={cmd:e,seq:n,payload:t};this.packetLog?.logOutboundPacket(e,n,t,"sent");try{const c=this.ws.readyState,o=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(h),d=>{if(e==="event_result"){const r=t;d?a.warn("aibot",`event_result ws send callback failed event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_result ws send callback ok event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="client_stream_chunk"){const r=t;d?a.warn("aibot",`stream_chunk ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`stream_chunk ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} client_msg_id=${r.client_msg_id??""} quoted_message_id=${r.quoted_message_id??""} readyState=${c} bufferedAmount=${o}`)}else if(e==="event_ack"){const r=t;d?a.warn("aibot",`event_ack ws send failed event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_ack ws send ok event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="send_msg"){const r=t;d?a.warn("aibot",`send_msg ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`send_msg ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(d){const r=t;a.warn("aibot",`${e} ws send failed seq=${n} session=${r.session_id??""} event=${r.event_id??""} client_msg_id=${r.client_msg_id??""} readyState=${c} bufferedAmount=${o} err=${d.message}`)}}),!0}catch(c){return this.emitClientError(new Error(`sendPacket failed: ${c}`)),!1}}}if(f.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,s??0,t,"dropped"),!1;if(s!==void 0)return this.packetLog?.logOutboundPacket(e,s,t,"dropped"),!1;if(this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(i=>f.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(i.cmd)),this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,s??0,t,"buffered"),e==="client_stream_chunk"){const i=t;a.info("aibot",`stream_chunk buffered (ws not open) event=${i.event_id??""} session=${i.session_id??""} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,s=this.ackPolicy?.push_ack_timeout_ms??5e3,i=750;for(let n=1;n<=t;n++){const h=this.ws?.readyState??-1,c=this.ws?.bufferedAmount??0;a.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${n}/${t} readyState=${h} bufferedAmount=${c}`);try{const o=await this.sendEventResultRequest(e,s);if(o.cmd==="send_ack"){const r=o.payload;a.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${n}/${t} ack_event=${r.event_id??""} ack_status=${r.status??""}`);return}const d=o.payload;if(a.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${n}/${t} cmd=${o.cmd} code=${d.code??""} msg=${d.msg??""}${d.ref_cmd?` ref_cmd=${d.ref_cmd}`:""}${d.ref_id?` ref_id=${d.ref_id}`:""}`),d.code===4003){a.warn("aibot",`event_result stopping retries: 4003 ownership denied event=${e.event_id}`);return}return}catch(o){const d=o instanceof Error?o.message:String(o);if(a.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${n}/${t} err=${d}`),n===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(r=>setTimeout(r,i*n))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(s=>s.cmd!=="client_stream_chunk"?!0:s.payload?.event_id!==e),this.outboundBuffer.length<t&&a.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){a.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:s}of e){const i=++this.seq;if(t==="client_stream_chunk"&&s&&typeof s=="object"){const h=s.event_id;h&&this.seqEventMap.set(i,h)}const n={cmd:t,seq:i,payload:s};try{this.ws.send(JSON.stringify(n))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((s,i)=>s[0]-i[0]);this.seqEventMap.clear();for(const[s,i]of t.slice(-100))this.seqEventMap.set(s,i)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws)try{this.ws.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.heartbeatFailures=0}).catch(()=>{this.connected&&(this.heartbeatFailures++,!(this.heartbeatFailures<f.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{f as AibotClient};
|
|
1
|
+
import{EventEmitter as $}from"node:events";import{randomUUID as b}from"node:crypto";import k from"node:os";import m from"ws";import{log as a}from"../log/index.js";import{getMachineName as w}from"../util/index.js";import{detectTailnetIPv4 as E,ensureServerAndGetPort as q,getFileServerHttpsPort as y}from"../files/file-serve.js";import{AUTH_CODE_AGENT_DELETED as p,KICKED_REASON_AGENT_DELETED as S,AgentDeletedError as v,RequestTimeoutError as A}from"./errors.js";function R(g){return g.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function P(g){const e=[...g??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e}const T="aibot-agent-api-v1",I=1;class f extends ${static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static BACKPRESSURE_THRESHOLD=64*1024;ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;heartbeatFailures=0;static HEARTBEAT_MAX_FAILURES=2;connected=!1;reconnecting=!1;reconnectAttempts=0;everConnected=!1;agentDeleted=!1;config;packetLog;pendingInvokes=new Map;seqEventMap=new Map;pendingRequests=new Map;outboundBuffer=[];ackPolicy=null;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:P(e.capabilities),localActions:e.localActions??["exec_approve","exec_reject"],skills:e.skills,librarySkills:e.librarySkills}}get isConnected(){return this.connected}async connect(){let e,t,s;const i=(async()=>{try{if(e=await E(),e!==void 0)try{t=await q(e);const n=y();n>0&&(s=n)}catch(n){a.warn("aibot",`file server pre-start failed: ${n}`)}}catch(n){a.warn("aibot",`tailnet detect failed: ${n}`)}})();return new Promise((n,h)=>{const c=new m(this.config.url);this.ws=c;const o=setTimeout(()=>{h(new Error("Auth timeout: no auth_ack received within 15s")),this.cleanupSocket()},15e3),d=++this.seq,r=setTimeout(()=>{this.pendingRequests.delete(d),h(new Error("Auth request timeout")),this.cleanupSocket()},15e3);this.pendingRequests.set(d,{expected:["auth_ack"],resolve:u=>{clearTimeout(o);const l=u.payload;l.code===0?(this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,l.heartbeat_sec&&(this.heartbeatSec=l.heartbeat_sec),l.ack_policy&&(this.ackPolicy=l.ack_policy,a.info("aibot",`ack_policy received: push_ack_timeout_ms=${l.ack_policy.push_ack_timeout_ms??"default"} max_retries=${l.ack_policy.max_retries??"default"} timeout_action=${l.ack_policy.timeout_action??"default"}`)),this.startHeartbeat(),this.flushOutboundBuffer(),this.emit("auth",l),n(l)):l.code===p?(this.agentDeleted=!0,h(new v(`Agent deleted: code=${l.code} msg=${l.msg}`))):h(new Error(`Auth failed: code=${l.code} msg=${l.msg}`))},reject:u=>{clearTimeout(o),h(u)},timer:r}),c.on("open",async()=>{await i;const u={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:T,contract_version:I,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills,library_skills:this.config.librarySkills};this.config.sharedOwnerId&&(u.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(u.client="grix-connector",u.client_version=this.config.clientVersion,u.host_type=this.config.clientType,u.host_version=this.config.clientVersion),this.config.adapterHint&&(u.adapter_hint=this.config.adapterHint),u.host_meta={hostname:w(),platform:k.platform(),arch:k.arch(),os_release:k.release(),...e!==void 0&&{tailnet_ip:e},...t!==void 0&&t>0&&{file_server_port:t},...s!==void 0&&s>0&&{file_server_https_port:s}},this.config.concurrency&&(u.concurrency=this.config.concurrency),this.sendPacket("auth",u,d)}),c.on("message",u=>{if(this.ws!==c)return;let l;try{l=JSON.parse(R(u.toString()))}catch{return}try{this.handlePacket(l)}catch(_){this.emitClientError(new Error(`handlePacket error: ${_}`))}}),c.on("close",(u,l)=>{if(this.ws!==c)return;this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("websocket closed"),this.emit("close",u,l.toString());const _=u!==1e3&&this.everConnected&&!this.agentDeleted;a.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${u} reason=${l.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${_}`),_&&this.attemptReconnect()}),c.on("error",u=>{this.ws===c&&(this.emitClientError(u instanceof Error?u:new Error(String(u))),this.connected||h(u))})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_reorder":{this.emit("queueReorder",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"event_hold":{this.emit("eventHold",e.payload);break}case"queue_edit":{this.emit("queueEdit",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"skill_sync":{this.emit("skillSync",e.payload);break}case"kicked":{const t=e.payload;if(this.emit("kicked",t),this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("kicked"),this.outboundBuffer.length=0,t?.reason===S){if(this.agentDeleted=!0,this.reconnecting=!1,this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}a.error("aibot",`kicked: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}, reconnect disabled`),this.emit("agentDeleted",{source:"kicked",reason:t.reason});break}if(this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}break}case"error":{const t=e.payload,s=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${s?` ${s}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":{const t=e.payload;if(t.code===4003&&e.seq>0){const s=this.seqEventMap.get(e.seq);s&&(this.seqEventMap.delete(e.seq),this.purgeBufferedStreamChunks(s),a.warn("aibot",`stream chunk rejected (4003), purging buffered chunks for event=${s}`),this.emit("streamRejected",s,t.code))}break}case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)||a.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(a.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
|
|
2
|
+
`}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||a.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==m.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueReorderResult(e){this.sendPacket("queue_reorder_result",e)}sendEventHoldResult(e){this.sendPacket("event_hold_result",e)}sendQueueEditResult(e){this.sendPacket("queue_edit_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,s=15e3){return new Promise((i,n)=>{const h=b(),c=Math.max(1e3,Math.min(s,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(h),n(new Error(`agent_invoke timeout: ${e}`))},c);this.pendingInvokes.set(h,{resolve:i,reject:n,timer:o}),this.sendPacket("agent_invoke",{invoke_id:h,action:e,params:t,timeout_ms:c})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,s){return new Promise((i,n)=>{const h=++this.seq,c=setTimeout(()=>{this.pendingRequests.delete(h),n(new A(`request timeout: ${e} (expected ${s.expected.join("/")})`))},s.timeoutMs);this.pendingRequests.set(h,{expected:s.expected,resolve:i,reject:n,timer:c}),this.sendPacket(e,t,h)||(this.pendingRequests.delete(h),clearTimeout(c),n(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async relayCredentialRequest(e,t=15e3){return this.request("relay_credential_request",e,{expected:["relay_credential_result","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,s=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:s})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){a.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0,a.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let e=0;for(;this.reconnecting;){const t=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(t*.2*Math.random());if(this.reconnectAttempts++,await new Promise(i=>setTimeout(i,t+s)),!this.reconnecting)return;try{await this.connect();const i=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,a.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${i}`);return}catch(i){if(this.ws){try{this.ws.close()}catch{}this.ws=null}const n=i instanceof Error?i.message:String(i);if(a.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${n}`),i instanceof v){this.agentDeleted=!0,this.reconnecting=!1,a.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:p});return}if(/Auth failed/i.test(n)){if(e++,e>=f.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,a.error("aibot",`reconnect giving up after ${e} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else e=0}}}sendPacket(e,t,s){if(this.ws&&this.ws.readyState===m.OPEN){const i=this.ws.bufferedAmount>f.BACKPRESSURE_THRESHOLD;if(!i||!f.DROPPABLE_COMMANDS.has(e)){if(i&&f.DROPPABLE_COMMANDS.has(e))return!1;const n=s??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const c=t.event_id;if(c&&(this.seqEventMap.set(n,c),this.seqEventMap.size>200)){const o=this.seqEventMap.keys().next().value;o!==void 0&&this.seqEventMap.delete(o)}}const h={cmd:e,seq:n,payload:t};this.packetLog?.logOutboundPacket(e,n,t,"sent");try{const c=this.ws.readyState,o=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(h),d=>{if(e==="event_result"){const r=t;d?a.warn("aibot",`event_result ws send callback failed event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_result ws send callback ok event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="client_stream_chunk"){const r=t;d?a.warn("aibot",`stream_chunk ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`stream_chunk ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} client_msg_id=${r.client_msg_id??""} quoted_message_id=${r.quoted_message_id??""} readyState=${c} bufferedAmount=${o}`)}else if(e==="event_ack"){const r=t;d?a.warn("aibot",`event_ack ws send failed event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_ack ws send ok event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="send_msg"){const r=t;d?a.warn("aibot",`send_msg ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`send_msg ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(d){const r=t;a.warn("aibot",`${e} ws send failed seq=${n} session=${r.session_id??""} event=${r.event_id??""} client_msg_id=${r.client_msg_id??""} readyState=${c} bufferedAmount=${o} err=${d.message}`)}}),!0}catch(c){return this.emitClientError(new Error(`sendPacket failed: ${c}`)),!1}}}if(f.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,s??0,t,"dropped"),!1;if(s!==void 0)return this.packetLog?.logOutboundPacket(e,s,t,"dropped"),!1;if(this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(i=>f.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(i.cmd)),this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,s??0,t,"buffered"),e==="client_stream_chunk"){const i=t;a.info("aibot",`stream_chunk buffered (ws not open) event=${i.event_id??""} session=${i.session_id??""} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,s=this.ackPolicy?.push_ack_timeout_ms??5e3,i=750;for(let n=1;n<=t;n++){const h=this.ws?.readyState??-1,c=this.ws?.bufferedAmount??0;a.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${n}/${t} readyState=${h} bufferedAmount=${c}`);try{const o=await this.sendEventResultRequest(e,s);if(o.cmd==="send_ack"){const r=o.payload;a.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${n}/${t} ack_event=${r.event_id??""} ack_status=${r.status??""}`);return}const d=o.payload;if(a.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${n}/${t} cmd=${o.cmd} code=${d.code??""} msg=${d.msg??""}${d.ref_cmd?` ref_cmd=${d.ref_cmd}`:""}${d.ref_id?` ref_id=${d.ref_id}`:""}`),d.code===4003){a.warn("aibot",`event_result stopping retries: 4003 ownership denied event=${e.event_id}`);return}return}catch(o){const d=o instanceof Error?o.message:String(o);if(a.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${n}/${t} err=${d}`),n===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(r=>setTimeout(r,i*n))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(s=>s.cmd!=="client_stream_chunk"?!0:s.payload?.event_id!==e),this.outboundBuffer.length<t&&a.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){a.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:s}of e){const i=++this.seq;if(t==="client_stream_chunk"&&s&&typeof s=="object"){const h=s.event_id;h&&this.seqEventMap.set(i,h)}const n={cmd:t,seq:i,payload:s};try{this.ws.send(JSON.stringify(n))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((s,i)=>s[0]-i[0]);this.seqEventMap.clear();for(const[s,i]of t.slice(-100))this.seqEventMap.set(s,i)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws)try{this.ws.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.heartbeatFailures=0}).catch(()=>{this.connected&&(this.heartbeatFailures++,!(this.heartbeatFailures<f.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{f as AibotClient};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
class r{client;_status="ready";connectedAt;listeners=[];authAck=null;constructor(e){this.client=e,this.connectedAt=Date.now(),this.addInternalListener("close",()=>{this._status!=="closing"&&(this._status="reconnecting")}),this.addInternalListener("auth",t=>{this._status="ready",t&&(this.authAck=t)}),this.addInternalListener("disconnected",()=>{this._status!=="closing"&&this._status!=="closed"&&(this._status="reconnecting")})}get status(){return this._status}getStatusSnapshot(){return{status:this._status,connectedAt:this.connectedAt,reconnectAttempts:0}}sendEventAck(e){this.client.sendEventAck(e)}sendStreamChunk(e){this.client.sendStreamChunk(e)}sendMsg(e){this.client.sendMsg(e)}editMsg(e){this.client.editMsg(e)}sendEventResult(e){this.client.sendEventResult(e)}sendLocalActionResult(e){this.client.sendLocalActionResult(e)}sendEventStopAck(e){this.client.sendEventStopAck(e)}sendEventStopResult(e){this.client.sendEventStopResult(e)}sendSessionActivitySet(e){this.client.sendSessionActivitySet(e)}sendCodexEvent(e){this.client.sendCodexEvent(e)}sendUpdateBindingCard(e){this.client.sendUpdateBindingCard(e)}sendSkillsUpdate(e){this.client.sendSkillsUpdate(e)}sendPing(){this.client.sendPing()}sendEventState(e){this.client.sendEventState(e)}sendEventCancelResult(e){this.client.sendEventCancelResult(e)}sendQueueClearResult(e){this.client.sendQueueClearResult(e)}sendQueueReorderResult(e){this.client.sendQueueReorderResult(e)}sendEventHoldResult(e){this.client.sendEventHoldResult(e)}sendQueueEditResult(e){this.client.sendQueueEditResult(e)}sendQueueSnapshot(e){this.client.sendQueueSnapshot(e)}agentInvoke(e,t,s){return this.client.agentInvoke(e,t,s)}sendStreamChunkRequest(e,t){return this.client.sendStreamChunkRequest(e,t)}sendText(e,t){return this.client.sendText(e,t)}sendMedia(e,t){return this.client.sendMedia(e,t)}editMessage(e,t){return this.client.editMessage(e,t)}deleteMessage(e,t,s){return this.client.deleteMessage(e,t,s)}sendEventResultRequest(e,t){return this.client.sendEventResultRequest(e,t)}sendCodexEventReliable(e,t){return this.client.request("codex_event",e,{expected:["send_ack","send_nack","error"],timeoutMs:t??2e4})}onEvent(e){return this.subscribe("event",e)}onLocalAction(e){return this.subscribe("localAction",e)}onStop(e){return this.subscribe("stop",e)}onRevoke(e){return this.subscribe("revoke",e)}onEdit(e){return this.subscribe("edit",e)}onEventCancel(e){return this.subscribe("eventCancel",e)}onQueueClear(e){return this.subscribe("queueClear",e)}onQueueReorder(e){return this.subscribe("queueReorder",e)}onQueueSnapshotQuery(e){return this.subscribe("queueSnapshotQuery",e)}onEventHold(e){return this.subscribe("eventHold",e)}onQueueEdit(e){return this.subscribe("queueEdit",e)}onKicked(e){return this.subscribe("kicked",e)}onAgentDeleted(e){return this.subscribe("agentDeleted",e)}onShareSet(e){return this.subscribe("shareSet",e)}onProfilePush(e){return this.subscribe("profilePush",e)}onSkillSync(e){return this.subscribe("skillSync",e)}onError(e){return this.subscribe("error",e)}onClose(e){return this.subscribe("close",e)}onDisconnected(e){return this.subscribe("disconnected",e)}onStreamRejected(e){return this.subscribe("streamRejected",e)}onReconnected(e){return this.subscribe("auth",e)}disconnect(){this._status="closing",this.removeAllListeners(),this.client.disconnect(),this._status="closed"}sendMcpFrame(e,t){this.client.sendMcpFrame(e,t)}onMcpFrame(e){return this.subscribe("mcpFrame",e)}subscribe(e,t){return this.client.on(e,t),this.listeners.push({event:e,handler:t}),()=>{this.client.removeListener(e,t);const s=this.listeners.findIndex(n=>n.event===e&&n.handler===t);s>=0&&this.listeners.splice(s,1)}}addInternalListener(e,t){this.client.on(e,t),this.listeners.push({event:e,handler:t})}removeAllListeners(){for(const{event:e,handler:t}of this.listeners)this.client.removeListener(e,t);this.listeners.length=0}}export{r as AibotConnectionHandleImpl};
|
|
1
|
+
class r{client;_status="ready";connectedAt;listeners=[];authAck=null;constructor(e){this.client=e,this.connectedAt=Date.now(),this.addInternalListener("close",()=>{this._status!=="closing"&&(this._status="reconnecting")}),this.addInternalListener("auth",t=>{this._status="ready",t&&(this.authAck=t)}),this.addInternalListener("disconnected",()=>{this._status!=="closing"&&this._status!=="closed"&&(this._status="reconnecting")})}get status(){return this._status}getStatusSnapshot(){return{status:this._status,connectedAt:this.connectedAt,reconnectAttempts:0}}sendEventAck(e){this.client.sendEventAck(e)}sendStreamChunk(e){this.client.sendStreamChunk(e)}sendMsg(e){this.client.sendMsg(e)}editMsg(e){this.client.editMsg(e)}sendEventResult(e){this.client.sendEventResult(e)}sendLocalActionResult(e){this.client.sendLocalActionResult(e)}sendEventStopAck(e){this.client.sendEventStopAck(e)}sendEventStopResult(e){this.client.sendEventStopResult(e)}sendSessionActivitySet(e){this.client.sendSessionActivitySet(e)}sendCodexEvent(e){this.client.sendCodexEvent(e)}sendUpdateBindingCard(e){this.client.sendUpdateBindingCard(e)}sendSkillsUpdate(e){this.client.sendSkillsUpdate(e)}sendPing(){this.client.sendPing()}sendEventState(e){this.client.sendEventState(e)}sendEventCancelResult(e){this.client.sendEventCancelResult(e)}sendQueueClearResult(e){this.client.sendQueueClearResult(e)}sendQueueReorderResult(e){this.client.sendQueueReorderResult(e)}sendEventHoldResult(e){this.client.sendEventHoldResult(e)}sendQueueEditResult(e){this.client.sendQueueEditResult(e)}sendQueueSnapshot(e){this.client.sendQueueSnapshot(e)}agentInvoke(e,t,s){return this.client.agentInvoke(e,t,s)}sendStreamChunkRequest(e,t){return this.client.sendStreamChunkRequest(e,t)}sendText(e,t){return this.client.sendText(e,t)}sendMedia(e,t){return this.client.sendMedia(e,t)}editMessage(e,t){return this.client.editMessage(e,t)}deleteMessage(e,t,s){return this.client.deleteMessage(e,t,s)}sendEventResultRequest(e,t){return this.client.sendEventResultRequest(e,t)}sendCodexEventReliable(e,t){return this.client.request("codex_event",e,{expected:["send_ack","send_nack","error"],timeoutMs:t??2e4})}relayCredentialRequest(e,t){return this.client.relayCredentialRequest(e,t)}onEvent(e){return this.subscribe("event",e)}onLocalAction(e){return this.subscribe("localAction",e)}onStop(e){return this.subscribe("stop",e)}onRevoke(e){return this.subscribe("revoke",e)}onEdit(e){return this.subscribe("edit",e)}onEventCancel(e){return this.subscribe("eventCancel",e)}onQueueClear(e){return this.subscribe("queueClear",e)}onQueueReorder(e){return this.subscribe("queueReorder",e)}onQueueSnapshotQuery(e){return this.subscribe("queueSnapshotQuery",e)}onEventHold(e){return this.subscribe("eventHold",e)}onQueueEdit(e){return this.subscribe("queueEdit",e)}onKicked(e){return this.subscribe("kicked",e)}onAgentDeleted(e){return this.subscribe("agentDeleted",e)}onShareSet(e){return this.subscribe("shareSet",e)}onProfilePush(e){return this.subscribe("profilePush",e)}onSkillSync(e){return this.subscribe("skillSync",e)}onError(e){return this.subscribe("error",e)}onClose(e){return this.subscribe("close",e)}onDisconnected(e){return this.subscribe("disconnected",e)}onStreamRejected(e){return this.subscribe("streamRejected",e)}onReconnected(e){return this.subscribe("auth",e)}disconnect(){this._status="closing",this.removeAllListeners(),this.client.disconnect(),this._status="closed"}sendMcpFrame(e,t){this.client.sendMcpFrame(e,t)}onMcpFrame(e){return this.subscribe("mcpFrame",e)}subscribe(e,t){return this.client.on(e,t),this.listeners.push({event:e,handler:t}),()=>{this.client.removeListener(e,t);const s=this.listeners.findIndex(n=>n.event===e&&n.handler===t);s>=0&&this.listeners.splice(s,1)}}addInternalListener(e,t){this.client.on(e,t),this.listeners.push({event:e,handler:t})}removeAllListeners(){for(const{event:e,handler:t}of this.listeners)this.client.removeListener(e,t);this.listeners.length=0}}export{r as AibotConnectionHandleImpl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const
|
|
1
|
+
import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const t of c){if(!p&&t.name.startsWith("."))continue;const i=l(a,t.name),e={id:i,name:t.name,is_directory:t.isDirectory()};try{if(t.isDirectory()){const o=await m(i);e.modified_at=o.mtime.toISOString()}else{const o=await m(i);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(t.name)}}catch{}s.push(e)}return s.sort((t,i)=>t.is_directory!==i.is_directory?t.is_directory?-1:1:t.name.localeCompare(i.name)),s}export{f as listFiles,n as resolveMimeType};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{detectProvider as o,detectProviderFromModel as i,normalizeProviderId as t,queryProviderQuota as d,readKimiProviderSettings as v,resolveKimiProviderSettings as
|
|
1
|
+
import{detectProvider as o,detectProviderFromModel as i,normalizeProviderId as t,queryProviderQuota as d,readKimiProviderSettings as v,readPiProviderSettings as a,resolveKimiProviderSettings as P}from"./providers.js";import{ProviderQuotaService as s,sharedProviderQuotaService as u,resolveQuotaBaseUrl as Q}from"./service.js";import{providerQuotaToRateLimits as p,providerQuotaToCodexRateLimits as S}from"./presentation.js";export{s as ProviderQuotaService,o as detectProvider,i as detectProviderFromModel,t as normalizeProviderId,S as providerQuotaToCodexRateLimits,p as providerQuotaToRateLimits,d as queryProviderQuota,v as readKimiProviderSettings,a as readPiProviderSettings,P as resolveKimiProviderSettings,Q as resolveQuotaBaseUrl,u as sharedProviderQuotaService};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createHash as
|
|
2
|
-
[`,i+1),u=(c>=0?n.slice(i,c):n.slice(i)).match(/^\s*base_url\s*=\s*"([^"]+)"/m);u&&(e=u[1].trim())}}catch{}let o;try{const a=_(t,"credentials","kimi-code.json"),n=JSON.parse(y(a,"utf8")),i=typeof n.access_token=="string"?n.access_token.trim():"",c=typeof n.expires_at=="number"?n.expires_at:null;i&&(!c||Date.now()<c*1e3-r)&&(o=i)}catch{}return{baseUrl:e,apiKey:o}}const D="17e5f671-d194-4dfb-9706-5516cb48c098";function F(){return(process.env.KIMI_CODE_OAUTH_HOST||process.env.KIMI_OAUTH_HOST||"https://auth.kimi.com").replace(/\/+$/,"")}const G=30*1e3;async function se(s){const t=T(s);if(t.apiKey)return t;const r=await C(s);return{baseUrl:t.baseUrl,apiKey:r}}async function C(s){const t=s||N(),r=_(t,"credentials","kimi-code.json");let e;try{e=JSON.parse(y(r,"utf8"))}catch{return}const o=typeof e.refresh_token=="string"?e.refresh_token.trim():"";if(!o)return;const a=_(t,"credentials","kimi-code.lock.lock");if(q(a))try{const n=T(s);if(n.apiKey)return n.apiKey;let i="";try{i=y(_(t,"device_id"),"utf8").trim()}catch{}const c=await fetch(`${F()}/api/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",...i?{"X-Msh-Device-Id":i}:{}},body:new URLSearchParams({client_id:D,grant_type:"refresh_token",refresh_token:o}),signal:AbortSignal.timeout(8e3)}),l=await c.json().catch(()=>({})),u=typeof l.access_token=="string"?l.access_token:"";if(c.status!==200||!u)return T(s).apiKey;const p=Number(l.expires_in),h={access_token:u,refresh_token:typeof l.refresh_token=="string"&&l.refresh_token?l.refresh_token:o,expires_at:Math.floor(Date.now()/1e3)+(Number.isFinite(p)&&p>0?p:900),scope:typeof l.scope=="string"&&l.scope?l.scope:e.scope??"kimi-code",token_type:typeof l.token_type=="string"&&l.token_type?l.token_type:"Bearer",expires_in:Number.isFinite(p)&&p>0?p:900};return j(r,JSON.stringify(h),{mode:384}),u}catch{return}finally{try{x(a)}catch{}}}function q(s){try{return P(s),!0}catch{}try{const t=z(s);return Date.now()-t.mtimeMs<G?!1:(x(s),P(s),!0)}catch{return!1}}async function K(s){const t="zhipu",r="Zhipu GLM";try{const e=await fetch("https://api.z.ai/api/monitor/usage/quota/limit",{method:"GET",headers:{Authorization:s,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(f)});if(e.status===401||e.status===403)return d(t,r,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const c=await e.text().catch(()=>"");return d(t,r,`API error (HTTP ${e.status}): ${c.slice(0,200)}`)}const o=await e.json();if(o.success===!1)return d(t,r,`API error: ${o.msg??"Unknown error"}`);const a=o.data;if(!a)return d(t,r,"Missing data field");const n=typeof a.level=="string"?a.level:null,i=M(a);return{provider:t,providerLabel:r,planName:n,tiers:i,balance:null,success:!0,error:null}}catch(e){return d(t,r,`Network error: ${e instanceof Error?e.message:String(e)}`)}}function M(s){const t=Array.isArray(s.limits)?s.limits:[],r=[];for(const o of t){if(String(o.type??"").toUpperCase()!=="TOKENS_LIMIT")continue;const n=m(o.percentage)??0,i=m(o.nextResetTime)??Number.MAX_SAFE_INTEGER,c=i===Number.MAX_SAFE_INTEGER?null:w(i);r.push({percentage:n,resetMs:i,resetIso:c})}r.sort((o,a)=>o.resetMs-a.resetMs);const e=[];if(r.length>0){const o=r[0];e.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(o.percentage*100)/100,resetsAt:o.resetIso})}if(r.length>1){const o=r[r.length-1];o.resetMs!==r[0].resetMs&&e.push({name:"weekly_limit",label:"Weekly limit",usedPercent:Math.round(o.percentage*100)/100,resetsAt:o.resetIso})}return e}async function Y(s){const t="kimi",r="Kimi";try{const e=await fetch("https://api.kimi.com/coding/v1/usages",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(e.status===401||e.status===403)return d(t,r,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const c=await e.text().catch(()=>"");return d(t,r,`API error (HTTP ${e.status}): ${c.slice(0,200)}`)}const o=await e.json();if(!Array.isArray(o.limits)&&!o.usage)return d(t,r,"Unexpected quota response schema");const a=[],n=Array.isArray(o.limits)?o.limits:[];for(const c of n){const l=c.detail;if(!l)continue;const u=m(l.limit)??1,p=m(l.remaining)??0,h=v(l.resetTime),b=Math.max(0,u-p);a.push({name:"five_hour",label:"5h limit",usedPercent:u>0?Math.round(b/u*1e4)/100:0,resetsAt:h})}const i=o.usage;if(i){const c=m(i.limit)??1,l=m(i.remaining)??0,u=v(i.resetTime),p=Math.max(0,c-l);a.push({name:"weekly_limit",label:"Weekly limit",usedPercent:c>0?Math.round(p/c*1e4)/100:0,resetsAt:u})}return{provider:t,providerLabel:r,planName:null,tiers:a,balance:null,success:!0,error:null}}catch(e){return d(t,r,`Network error: ${e instanceof Error?e.message:String(e)}`)}}async function I(s,t){const r=t?"minimax_cn":"minimax_en",e="MiniMax",o=t?"api.minimaxi.com":"api.minimax.io";try{const a=await fetch(`https://${o}/v1/api/openplatform/coding_plan/remains`,{method:"GET",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},signal:AbortSignal.timeout(f)});if(a.status===401||a.status===403)return d(r,e,`Authentication failed (HTTP ${a.status})`);if(!a.ok){const p=await a.text().catch(()=>"");return d(r,e,`API error (HTTP ${a.status}): ${p.slice(0,200)}`)}const n=await a.json(),i=n.base_resp;if(i&&typeof i.status_code=="number"&&i.status_code!==0)return d(r,e,`API error (code ${i.status_code}): ${i.status_msg??"Unknown"}`);const c=[],u=(Array.isArray(n.model_remains)?n.model_remains:[])[0];if(u){const p=m(u.current_interval_total_count)??0,h=m(u.current_interval_usage_count)??0,b=typeof u.end_time=="number"?u.end_time:null;p>0&&c.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(h/p*1e4)/100,resetsAt:b!==null?w(b):null});const g=m(u.current_weekly_total_count)??0,A=m(u.current_weekly_usage_count)??0,$=typeof u.weekly_end_time=="number"?u.weekly_end_time:null;g>0&&c.push({name:"weekly_limit",label:"Weekly limit",usedPercent:Math.round(A/g*1e4)/100,resetsAt:$!==null?w($):null})}return{provider:r,providerLabel:e,planName:null,tiers:c,balance:null,success:!0,error:null}}catch(a){return d(r,e,`Network error: ${a instanceof Error?a.message:String(a)}`)}}async function Z(s){const t="deepseek",r="DeepSeek";try{const e=await fetch("https://api.deepseek.com/user/balance",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(e.status===401||e.status===403)return d(t,r,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const u=await e.text().catch(()=>"");return d(t,r,`API error (HTTP ${e.status}): ${u.slice(0,200)}`)}const o=await e.json(),a=o.is_available===!0,i=(Array.isArray(o.balance_infos)?o.balance_infos:[])[0];if(!i)return d(t,r,"No balance info returned");const c=String(i.currency??"CNY"),l=m(i.total_balance);return{provider:t,providerLabel:r,planName:null,tiers:[],balance:{remaining:l??0,total:null,used:null,unit:c},success:!0,error:a?null:"Insufficient balance"}}catch(e){return d(t,r,`Network error: ${e instanceof Error?e.message:String(e)}`)}}async function V(s){const t="stepfun",r="StepFun";try{const e=await fetch("https://api.stepfun.com/v1/accounts",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(e.status===401||e.status===403)return d(t,r,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const n=await e.text().catch(()=>"");return d(t,r,`API error (HTTP ${e.status}): ${n.slice(0,200)}`)}const o=await e.json(),a=m(o.balance)??0;return{provider:t,providerLabel:r,planName:null,tiers:[],balance:{remaining:a,total:null,used:null,unit:"CNY"},success:!0,error:null}}catch(e){return d(t,r,`Network error: ${e instanceof Error?e.message:String(e)}`)}}async function E(s,t){const r=t?"siliconflow_cn":"siliconflow_en",e=t?"SiliconFlow":"SiliconFlow (EN)",o=t?"api.siliconflow.cn":"api.siliconflow.com",a=t?"CNY":"USD";try{const n=await fetch(`https://${o}/v1/user/info`,{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(n.status===401||n.status===403)return d(r,e,`Authentication failed (HTTP ${n.status})`);if(!n.ok){const u=await n.text().catch(()=>"");return d(r,e,`API error (HTTP ${n.status}): ${u.slice(0,200)}`)}const c=(await n.json()).data;if(!c)return d(r,e,"Missing data field");const l=m(c.totalBalance)??0;return{provider:r,providerLabel:e,planName:null,tiers:[],balance:{remaining:l,total:null,used:null,unit:a},success:!0,error:null}}catch(n){return d(r,e,`Network error: ${n instanceof Error?n.message:String(n)}`)}}async function W(s){const t="openrouter",r="OpenRouter";try{const e=await fetch("https://openrouter.ai/api/v1/credits",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(e.status===401||e.status===403)return d(t,r,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const l=await e.text().catch(()=>"");return d(t,r,`API error (HTTP ${e.status}): ${l.slice(0,200)}`)}const o=await e.json(),a=o.data??o,n=m(a.total_credits)??0,i=m(a.total_usage)??0,c=n-i;return{provider:t,providerLabel:r,planName:null,tiers:[],balance:{remaining:c,total:n,used:i,unit:"USD"},success:!0,error:null}}catch(e){return d(t,r,`Network error: ${e instanceof Error?e.message:String(e)}`)}}async function X(s){const t="novita",r="Novita AI";try{const e=await fetch("https://api.novita.ai/v3/user/balance",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(e.status===401||e.status===403)return d(t,r,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const n=await e.text().catch(()=>"");return d(t,r,`API error (HTTP ${e.status}): ${n.slice(0,200)}`)}const o=await e.json(),a=(m(o.availableBalance)??0)/1e4;return{provider:t,providerLabel:r,planName:null,tiers:[],balance:{remaining:a,total:null,used:null,unit:"USD"},success:!0,error:null}}catch(e){return d(t,r,`Network error: ${e instanceof Error?e.message:String(e)}`)}}const k=new Map,J=300*1e3;async function Q(s,t){const r=L("sha256").update(t).digest("hex").slice(0,16),e=`${s.trim().replace(/\/+$/,"").toLowerCase()}|${r}`,o=k.get(e);if(o&&Date.now()-o.timestamp<=J){const i=await S(o.providerId,s,t);if(i)return i;k.delete(e)}else o&&k.delete(e);const a=["zhipu","deepseek","kimi","openrouter","stepfun","minimax_cn","siliconflow_cn","novita"],n=await Promise.allSettled(a.map(i=>S(i,s,t)));for(let i=0;i<n.length;i++){const c=n[i];if(c.status==="fulfilled"&&c.value?.success)return k.set(e,{providerId:a[i],timestamp:Date.now()}),c.value}return null}async function S(s,t,r){const e=t.replace(/\/+$/,""),o={Authorization:`Bearer ${r}`,Accept:"application/json"};try{switch(s){case"zhipu":{const a=await fetch(`${e}/api/monitor/usage/quota/limit`,{method:"GET",headers:{...o,Authorization:r,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json();if(n.success===!1)return null;const i=n.data;if(!i)return null;const c=typeof i.level=="string"?i.level:null,l=M(i);return{provider:"zhipu",providerLabel:"Zhipu GLM",planName:c,tiers:l,balance:null,success:!0,error:null}}case"deepseek":{const a=await fetch(`${e}/user/balance`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json(),i=n.is_available===!0,l=(Array.isArray(n.balance_infos)?n.balance_infos:[])[0];if(!l)return null;const u=String(l.currency??"CNY"),p=m(l.total_balance);return{provider:"deepseek",providerLabel:"DeepSeek",planName:null,tiers:[],balance:{remaining:p??0,total:null,used:null,unit:u},success:!0,error:i?null:"Insufficient balance"}}case"kimi":{const a=await fetch(`${e}/coding/v1/usages`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json();if(!Array.isArray(n.limits)&&!n.usage)return null;const i=[],c=Array.isArray(n.limits)?n.limits:[];for(const u of c){const p=u.detail;if(!p)continue;const h=m(p.limit)??1,b=m(p.remaining)??0,g=v(p.resetTime),A=Math.max(0,h-b);i.push({name:"five_hour",label:"5h limit",usedPercent:h>0?Math.round(A/h*1e4)/100:0,resetsAt:g})}const l=n.usage;if(l){const u=m(l.limit)??1,p=m(l.remaining)??0,h=v(l.resetTime),b=Math.max(0,u-p);i.push({name:"weekly_limit",label:"Weekly limit",usedPercent:u>0?Math.round(b/u*1e4)/100:0,resetsAt:h})}return{provider:"kimi",providerLabel:"Kimi",planName:null,tiers:i,balance:null,success:!0,error:null}}case"openrouter":{const a=await fetch(`${e}/api/v1/credits`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json(),i=n.data??n;if(i.total_credits===void 0&&i.total_usage===void 0)return null;const c=m(i.total_credits)??0,l=m(i.total_usage)??0,u=c-l;return{provider:"openrouter",providerLabel:"OpenRouter",planName:null,tiers:[],balance:{remaining:u,total:c,used:l,unit:"USD"},success:!0,error:null}}case"stepfun":{const a=await fetch(`${e}/v1/accounts`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json();if(n.balance===void 0)return null;const i=m(n.balance)??0;return{provider:"stepfun",providerLabel:"StepFun",planName:null,tiers:[],balance:{remaining:i,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"minimax_cn":case"minimax_en":{const a=await fetch(`${e}/v1/api/openplatform/coding_plan/remains`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json(),i=n.base_resp;if(i&&typeof i.status_code=="number"&&i.status_code!==0)return null;const l=(Array.isArray(n.model_remains)?n.model_remains:[])[0];if(!l)return null;const u=[],p=m(l.current_interval_total_count)??0,h=m(l.current_interval_usage_count)??0;return p>0&&u.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(h/p*1e4)/100,resetsAt:null}),{provider:s,providerLabel:"MiniMax",planName:null,tiers:u,balance:null,success:!0,error:null}}case"siliconflow_cn":case"siliconflow_en":{const a=await fetch(`${e}/v1/user/info`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const i=(await a.json()).data;if(!i)return null;const c=m(i.totalBalance)??0;return{provider:s,providerLabel:"SiliconFlow",planName:null,tiers:[],balance:{remaining:c,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"novita":{const a=await fetch(`${e}/v3/user/balance`,{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!a.ok)return null;const n=await a.json();if(n.availableBalance===void 0)return null;const i=(m(n.availableBalance)??0)/1e4;return{provider:"novita",providerLabel:"Novita AI",planName:null,tiers:[],balance:{remaining:i,total:null,used:null,unit:"USD"},success:!0,error:null}}default:return null}}catch{return null}}async function ae(s,t,r){if(!t.trim())return{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:"API key is empty"};const e=U(r),o=B(s);if(o)switch(o.id){case"zhipu":return K(t);case"kimi":return Y(t);case"minimax_cn":return I(t,!0);case"minimax_en":return I(t,!1);case"deepseek":return Z(t);case"stepfun":return V(t);case"siliconflow_cn":return E(t,!0);case"siliconflow_en":return E(t,!1);case"openrouter":return W(t);case"novita":return X(t)}if(e){const n=await S(e,s,t);return n||d(e,e,`Quota API unavailable through base URL: ${s}`)}const a=await Q(s,t);return a||{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:`Could not identify provider for base URL: ${s}`}}export{B as detectProvider,ie as detectProviderFromModel,U as normalizeProviderId,ae as queryProviderQuota,T as readKimiProviderSettings,se as resolveKimiProviderSettings};
|
|
1
|
+
import{createHash as R}from"node:crypto";import{readFileSync as w,writeFileSync as H,mkdirSync as N,rmdirSync as x,statSync as C}from"node:fs";import{homedir as M}from"node:os";import{join as y}from"node:path";const D=new Set(["zhipu","kimi","minimax_cn","minimax_en","deepseek","stepfun","siliconflow_cn","siliconflow_en","openrouter","novita"]),G={zai:"zhipu","z.ai":"zhipu",glm:"zhipu",bigmodel:"zhipu",moonshot:"kimi",minimax:"minimax_cn",siliconflow:"siliconflow_cn"};function P(r){const t=r?.trim().toLowerCase();if(!t)return null;const s=G[t]??t;return D.has(s)?s:null}function E(r){const t=r.toLowerCase();return t.includes("open.bigmodel.cn")||t.includes("bigmodel.cn")||t.includes("api.z.ai")?{id:"zhipu",label:"Zhipu GLM"}:t.includes("api.kimi.com")?{id:"kimi",label:"Kimi"}:t.includes("api.minimaxi.com")?{id:"minimax_cn",label:"MiniMax"}:t.includes("api.minimax.io")?{id:"minimax_en",label:"MiniMax"}:t.includes("api.deepseek.com")?{id:"deepseek",label:"DeepSeek"}:t.includes("api.stepfun.ai")||t.includes("api.stepfun.com")?{id:"stepfun",label:"StepFun"}:t.includes("api.siliconflow.cn")?{id:"siliconflow_cn",label:"SiliconFlow"}:t.includes("api.siliconflow.com")?{id:"siliconflow_en",label:"SiliconFlow"}:t.includes("openrouter.ai")?{id:"openrouter",label:"OpenRouter"}:t.includes("api.novita.ai")?{id:"novita",label:"Novita AI"}:null}function L(r){const t=r?.trim().toLowerCase()??"";return t?/(^|[\/:_-])(glm|zhipu|zai)([\/:_.-]|$)/.test(t)?{id:"zhipu",label:"Zhipu GLM"}:/(^|[\/:_-])(kimi|moonshot)([\/:_.-]|$)/.test(t)?{id:"kimi",label:"Kimi"}:/(^|[\/:_-])deepseek([\/:_.-]|$)/.test(t)?{id:"deepseek",label:"DeepSeek"}:null:null}const h=1e4;function m(r,t,s){return{provider:r,providerLabel:t,planName:null,tiers:[],balance:null,success:!1,error:s}}function p(r){if(typeof r=="number")return r;if(typeof r=="string"){const t=Number(r);return Number.isFinite(t)?t:null}return null}function k(r){if(!Number.isFinite(r)||r<=0)return null;try{return new Date(r).toISOString()}catch{return null}}function T(r){if(typeof r=="string")return r;if(typeof r=="number"){const t=r<1e12?r*1e3:r;return k(t)}return null}function j(){return(process.env.KIMI_CODE_HOME||"").trim()||y(M(),".kimi-code")}function $(r){const t=r||j(),s=300*1e3;let e;try{const o=y(t,"config.toml"),n=w(o,"utf8"),i=n.indexOf('[providers."managed:kimi-code"]');if(i>=0){const l=n.indexOf(`
|
|
2
|
+
[`,i+1),u=(l>=0?n.slice(i,l):n.slice(i)).match(/^\s*base_url\s*=\s*"([^"]+)"/m);u&&(e=u[1].trim())}}catch{}let a;try{const o=y(t,"credentials","kimi-code.json"),n=JSON.parse(w(o,"utf8")),i=typeof n.access_token=="string"?n.access_token.trim():"",l=typeof n.expires_at=="number"?n.expires_at:null;i&&(!l||Date.now()<l*1e3-s)&&(a=i)}catch{}return{baseUrl:e,apiKey:a}}const B="17e5f671-d194-4dfb-9706-5516cb48c098";function F(){return(process.env.KIMI_CODE_OAUTH_HOST||process.env.KIMI_OAUTH_HOST||"https://auth.kimi.com").replace(/\/+$/,"")}const K=30*1e3;async function ce(r){const t=$(r);if(t.apiKey)return t;const s=await q(r);return{baseUrl:t.baseUrl,apiKey:s}}async function q(r){const t=r||j(),s=y(t,"credentials","kimi-code.json");let e;try{e=JSON.parse(w(s,"utf8"))}catch{return}const a=typeof e.refresh_token=="string"?e.refresh_token.trim():"";if(!a)return;const o=y(t,"credentials","kimi-code.lock.lock");if(Y(o))try{const n=$(r);if(n.apiKey)return n.apiKey;let i="";try{i=w(y(t,"device_id"),"utf8").trim()}catch{}const l=await fetch(`${F()}/api/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",...i?{"X-Msh-Device-Id":i}:{}},body:new URLSearchParams({client_id:B,grant_type:"refresh_token",refresh_token:a}),signal:AbortSignal.timeout(8e3)}),c=await l.json().catch(()=>({})),u=typeof c.access_token=="string"?c.access_token:"";if(l.status!==200||!u)return $(r).apiKey;const d=Number(c.expires_in),f={access_token:u,refresh_token:typeof c.refresh_token=="string"&&c.refresh_token?c.refresh_token:a,expires_at:Math.floor(Date.now()/1e3)+(Number.isFinite(d)&&d>0?d:900),scope:typeof c.scope=="string"&&c.scope?c.scope:e.scope??"kimi-code",token_type:typeof c.token_type=="string"&&c.token_type?c.token_type:"Bearer",expires_in:Number.isFinite(d)&&d>0?d:900};return H(s,JSON.stringify(f),{mode:384}),u}catch{return}finally{try{x(o)}catch{}}}function Y(r){try{return N(r),!0}catch{}try{const t=C(r);return Date.now()-t.mtimeMs<K?!1:(x(r),N(r),!0)}catch{return!1}}function V(){return(process.env.PI_CODING_AGENT_DIR||"").trim()||y(M(),".pi","agent")}function Z(r){if(typeof r!="string")return;const t=r.trim();if(!(!t||t.startsWith("!")))return(process.env[t]??"").trim()||t}function le(r,t){const s=t||V();let e;try{e=JSON.parse(w(y(s,"models.json"),"utf8"))}catch{return{baseUrl:void 0,apiKey:void 0}}const a=e.providers;if(!a||typeof a!="object"||Array.isArray(a))return{baseUrl:void 0,apiKey:void 0};const o=[];for(const[u,d]of Object.entries(a)){const f=d&&typeof d=="object"?d:{},b=typeof f.baseUrl=="string"&&f.baseUrl.trim()?f.baseUrl.trim():void 0,_=Z(f.apiKey),g=Array.isArray(f.models)?f.models.map(v=>typeof v?.id=="string"?v.id.trim().toLowerCase():"").filter(Boolean):[],A=P(u)??(b?E(b)?.id??null:null)??g.map(v=>L(v)?.id??null).find(v=>v!==null)??null;o.push({baseUrl:b,apiKey:_,modelIds:g,providerId:A})}const n=o.filter(u=>u.baseUrl&&u.apiKey),i=P(r?.providerId),l=r?.model?.trim().toLowerCase()||void 0;let c;if(i){const u=n.filter(d=>d.providerId===i);c=(l?u.find(d=>d.modelIds.includes(l)):void 0)??u[0]}return!c&&l&&(c=n.find(u=>u.modelIds.includes(l))??n.find(u=>u.providerId!==null&&u.providerId===L(l)?.id)),!c&&!i&&!l&&(c=n[0]),{baseUrl:c?.baseUrl,apiKey:c?.apiKey}}async function W(r){const t="zhipu",s="Zhipu GLM";try{const e=await fetch("https://api.z.ai/api/monitor/usage/quota/limit",{method:"GET",headers:{Authorization:r,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(h)});if(e.status===401||e.status===403)return m(t,s,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const l=await e.text().catch(()=>"");return m(t,s,`API error (HTTP ${e.status}): ${l.slice(0,200)}`)}const a=await e.json();if(a.success===!1)return m(t,s,`API error: ${a.msg??"Unknown error"}`);const o=a.data;if(!o)return m(t,s,"Missing data field");const n=typeof o.level=="string"?o.level:null,i=U(o);return{provider:t,providerLabel:s,planName:n,tiers:i,balance:null,success:!0,error:null}}catch(e){return m(t,s,`Network error: ${e instanceof Error?e.message:String(e)}`)}}function U(r){const t=Array.isArray(r.limits)?r.limits:[],s=[];for(const a of t){if(String(a.type??"").toUpperCase()!=="TOKENS_LIMIT")continue;const n=p(a.percentage)??0,i=p(a.nextResetTime)??Number.MAX_SAFE_INTEGER,l=i===Number.MAX_SAFE_INTEGER?null:k(i);s.push({percentage:n,resetMs:i,resetIso:l})}s.sort((a,o)=>a.resetMs-o.resetMs);const e=[];if(s.length>0){const a=s[0];e.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(a.percentage*100)/100,resetsAt:a.resetIso})}if(s.length>1){const a=s[s.length-1];a.resetMs!==s[0].resetMs&&e.push({name:"weekly_limit",label:"Weekly limit",usedPercent:Math.round(a.percentage*100)/100,resetsAt:a.resetIso})}return e}async function J(r){const t="kimi",s="Kimi";try{const e=await fetch("https://api.kimi.com/coding/v1/usages",{method:"GET",headers:{Authorization:`Bearer ${r}`,Accept:"application/json"},signal:AbortSignal.timeout(h)});if(e.status===401||e.status===403)return m(t,s,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const l=await e.text().catch(()=>"");return m(t,s,`API error (HTTP ${e.status}): ${l.slice(0,200)}`)}const a=await e.json();if(!Array.isArray(a.limits)&&!a.usage)return m(t,s,"Unexpected quota response schema");const o=[],n=Array.isArray(a.limits)?a.limits:[];for(const l of n){const c=l.detail;if(!c)continue;const u=p(c.limit)??1,d=p(c.remaining)??0,f=T(c.resetTime),b=Math.max(0,u-d);o.push({name:"five_hour",label:"5h limit",usedPercent:u>0?Math.round(b/u*1e4)/100:0,resetsAt:f})}const i=a.usage;if(i){const l=p(i.limit)??1,c=p(i.remaining)??0,u=T(i.resetTime),d=Math.max(0,l-c);o.push({name:"weekly_limit",label:"Weekly limit",usedPercent:l>0?Math.round(d/l*1e4)/100:0,resetsAt:u})}return{provider:t,providerLabel:s,planName:null,tiers:o,balance:null,success:!0,error:null}}catch(e){return m(t,s,`Network error: ${e instanceof Error?e.message:String(e)}`)}}async function O(r,t){const s=t?"minimax_cn":"minimax_en",e="MiniMax",a=t?"api.minimaxi.com":"api.minimax.io";try{const o=await fetch(`https://${a}/v1/api/openplatform/coding_plan/remains`,{method:"GET",headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},signal:AbortSignal.timeout(h)});if(o.status===401||o.status===403)return m(s,e,`Authentication failed (HTTP ${o.status})`);if(!o.ok){const d=await o.text().catch(()=>"");return m(s,e,`API error (HTTP ${o.status}): ${d.slice(0,200)}`)}const n=await o.json(),i=n.base_resp;if(i&&typeof i.status_code=="number"&&i.status_code!==0)return m(s,e,`API error (code ${i.status_code}): ${i.status_msg??"Unknown"}`);const l=[],u=(Array.isArray(n.model_remains)?n.model_remains:[])[0];if(u){const d=p(u.current_interval_total_count)??0,f=p(u.current_interval_usage_count)??0,b=typeof u.end_time=="number"?u.end_time:null;d>0&&l.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(f/d*1e4)/100,resetsAt:b!==null?k(b):null});const _=p(u.current_weekly_total_count)??0,g=p(u.current_weekly_usage_count)??0,A=typeof u.weekly_end_time=="number"?u.weekly_end_time:null;_>0&&l.push({name:"weekly_limit",label:"Weekly limit",usedPercent:Math.round(g/_*1e4)/100,resetsAt:A!==null?k(A):null})}return{provider:s,providerLabel:e,planName:null,tiers:l,balance:null,success:!0,error:null}}catch(o){return m(s,e,`Network error: ${o instanceof Error?o.message:String(o)}`)}}async function X(r){const t="deepseek",s="DeepSeek";try{const e=await fetch("https://api.deepseek.com/user/balance",{method:"GET",headers:{Authorization:`Bearer ${r}`,Accept:"application/json"},signal:AbortSignal.timeout(h)});if(e.status===401||e.status===403)return m(t,s,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const u=await e.text().catch(()=>"");return m(t,s,`API error (HTTP ${e.status}): ${u.slice(0,200)}`)}const a=await e.json(),o=a.is_available===!0,i=(Array.isArray(a.balance_infos)?a.balance_infos:[])[0];if(!i)return m(t,s,"No balance info returned");const l=String(i.currency??"CNY"),c=p(i.total_balance);return{provider:t,providerLabel:s,planName:null,tiers:[],balance:{remaining:c??0,total:null,used:null,unit:l},success:!0,error:o?null:"Insufficient balance"}}catch(e){return m(t,s,`Network error: ${e instanceof Error?e.message:String(e)}`)}}async function Q(r){const t="stepfun",s="StepFun";try{const e=await fetch("https://api.stepfun.com/v1/accounts",{method:"GET",headers:{Authorization:`Bearer ${r}`,Accept:"application/json"},signal:AbortSignal.timeout(h)});if(e.status===401||e.status===403)return m(t,s,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const n=await e.text().catch(()=>"");return m(t,s,`API error (HTTP ${e.status}): ${n.slice(0,200)}`)}const a=await e.json(),o=p(a.balance)??0;return{provider:t,providerLabel:s,planName:null,tiers:[],balance:{remaining:o,total:null,used:null,unit:"CNY"},success:!0,error:null}}catch(e){return m(t,s,`Network error: ${e instanceof Error?e.message:String(e)}`)}}async function z(r,t){const s=t?"siliconflow_cn":"siliconflow_en",e=t?"SiliconFlow":"SiliconFlow (EN)",a=t?"api.siliconflow.cn":"api.siliconflow.com",o=t?"CNY":"USD";try{const n=await fetch(`https://${a}/v1/user/info`,{method:"GET",headers:{Authorization:`Bearer ${r}`,Accept:"application/json"},signal:AbortSignal.timeout(h)});if(n.status===401||n.status===403)return m(s,e,`Authentication failed (HTTP ${n.status})`);if(!n.ok){const u=await n.text().catch(()=>"");return m(s,e,`API error (HTTP ${n.status}): ${u.slice(0,200)}`)}const l=(await n.json()).data;if(!l)return m(s,e,"Missing data field");const c=p(l.totalBalance)??0;return{provider:s,providerLabel:e,planName:null,tiers:[],balance:{remaining:c,total:null,used:null,unit:o},success:!0,error:null}}catch(n){return m(s,e,`Network error: ${n instanceof Error?n.message:String(n)}`)}}async function ee(r){const t="openrouter",s="OpenRouter";try{const e=await fetch("https://openrouter.ai/api/v1/credits",{method:"GET",headers:{Authorization:`Bearer ${r}`,Accept:"application/json"},signal:AbortSignal.timeout(h)});if(e.status===401||e.status===403)return m(t,s,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const c=await e.text().catch(()=>"");return m(t,s,`API error (HTTP ${e.status}): ${c.slice(0,200)}`)}const a=await e.json(),o=a.data??a,n=p(o.total_credits)??0,i=p(o.total_usage)??0,l=n-i;return{provider:t,providerLabel:s,planName:null,tiers:[],balance:{remaining:l,total:n,used:i,unit:"USD"},success:!0,error:null}}catch(e){return m(t,s,`Network error: ${e instanceof Error?e.message:String(e)}`)}}async function te(r){const t="novita",s="Novita AI";try{const e=await fetch("https://api.novita.ai/v3/user/balance",{method:"GET",headers:{Authorization:`Bearer ${r}`,Accept:"application/json"},signal:AbortSignal.timeout(h)});if(e.status===401||e.status===403)return m(t,s,`Authentication failed (HTTP ${e.status})`);if(!e.ok){const n=await e.text().catch(()=>"");return m(t,s,`API error (HTTP ${e.status}): ${n.slice(0,200)}`)}const a=await e.json(),o=(p(a.availableBalance)??0)/1e4;return{provider:t,providerLabel:s,planName:null,tiers:[],balance:{remaining:o,total:null,used:null,unit:"USD"},success:!0,error:null}}catch(e){return m(t,s,`Network error: ${e instanceof Error?e.message:String(e)}`)}}const S=new Map,ne=300*1e3;async function re(r,t){const s=R("sha256").update(t).digest("hex").slice(0,16),e=`${r.trim().replace(/\/+$/,"").toLowerCase()}|${s}`,a=S.get(e);if(a&&Date.now()-a.timestamp<=ne){const i=await I(a.providerId,r,t);if(i)return i;S.delete(e)}else a&&S.delete(e);const o=["zhipu","deepseek","kimi","openrouter","stepfun","minimax_cn","siliconflow_cn","novita"],n=await Promise.allSettled(o.map(i=>I(i,r,t)));for(let i=0;i<n.length;i++){const l=n[i];if(l.status==="fulfilled"&&l.value?.success)return S.set(e,{providerId:o[i],timestamp:Date.now()}),l.value}return null}async function I(r,t,s){const e=t.replace(/\/+$/,""),a={Authorization:`Bearer ${s}`,Accept:"application/json"};try{switch(r){case"zhipu":{const o=await fetch(`${e}/api/monitor/usage/quota/limit`,{method:"GET",headers:{...a,Authorization:s,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(h)});if(!o.ok)return null;const n=await o.json();if(n.success===!1)return null;const i=n.data;if(!i)return null;const l=typeof i.level=="string"?i.level:null,c=U(i);return{provider:"zhipu",providerLabel:"Zhipu GLM",planName:l,tiers:c,balance:null,success:!0,error:null}}case"deepseek":{const o=await fetch(`${e}/user/balance`,{method:"GET",headers:a,signal:AbortSignal.timeout(h)});if(!o.ok)return null;const n=await o.json(),i=n.is_available===!0,c=(Array.isArray(n.balance_infos)?n.balance_infos:[])[0];if(!c)return null;const u=String(c.currency??"CNY"),d=p(c.total_balance);return{provider:"deepseek",providerLabel:"DeepSeek",planName:null,tiers:[],balance:{remaining:d??0,total:null,used:null,unit:u},success:!0,error:i?null:"Insufficient balance"}}case"kimi":{const o=await fetch(`${e}/coding/v1/usages`,{method:"GET",headers:a,signal:AbortSignal.timeout(h)});if(!o.ok)return null;const n=await o.json();if(!Array.isArray(n.limits)&&!n.usage)return null;const i=[],l=Array.isArray(n.limits)?n.limits:[];for(const u of l){const d=u.detail;if(!d)continue;const f=p(d.limit)??1,b=p(d.remaining)??0,_=T(d.resetTime),g=Math.max(0,f-b);i.push({name:"five_hour",label:"5h limit",usedPercent:f>0?Math.round(g/f*1e4)/100:0,resetsAt:_})}const c=n.usage;if(c){const u=p(c.limit)??1,d=p(c.remaining)??0,f=T(c.resetTime),b=Math.max(0,u-d);i.push({name:"weekly_limit",label:"Weekly limit",usedPercent:u>0?Math.round(b/u*1e4)/100:0,resetsAt:f})}return{provider:"kimi",providerLabel:"Kimi",planName:null,tiers:i,balance:null,success:!0,error:null}}case"openrouter":{const o=await fetch(`${e}/api/v1/credits`,{method:"GET",headers:a,signal:AbortSignal.timeout(h)});if(!o.ok)return null;const n=await o.json(),i=n.data??n;if(i.total_credits===void 0&&i.total_usage===void 0)return null;const l=p(i.total_credits)??0,c=p(i.total_usage)??0,u=l-c;return{provider:"openrouter",providerLabel:"OpenRouter",planName:null,tiers:[],balance:{remaining:u,total:l,used:c,unit:"USD"},success:!0,error:null}}case"stepfun":{const o=await fetch(`${e}/v1/accounts`,{method:"GET",headers:a,signal:AbortSignal.timeout(h)});if(!o.ok)return null;const n=await o.json();if(n.balance===void 0)return null;const i=p(n.balance)??0;return{provider:"stepfun",providerLabel:"StepFun",planName:null,tiers:[],balance:{remaining:i,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"minimax_cn":case"minimax_en":{const o=await fetch(`${e}/v1/api/openplatform/coding_plan/remains`,{method:"GET",headers:a,signal:AbortSignal.timeout(h)});if(!o.ok)return null;const n=await o.json(),i=n.base_resp;if(i&&typeof i.status_code=="number"&&i.status_code!==0)return null;const c=(Array.isArray(n.model_remains)?n.model_remains:[])[0];if(!c)return null;const u=[],d=p(c.current_interval_total_count)??0,f=p(c.current_interval_usage_count)??0;if(d>0){const b=typeof c.end_time=="number"?c.end_time:null;u.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(f/d*1e4)/100,resetsAt:b!==null?k(b):null})}return{provider:r,providerLabel:"MiniMax",planName:null,tiers:u,balance:null,success:!0,error:null}}case"siliconflow_cn":case"siliconflow_en":{const o=await fetch(`${e}/v1/user/info`,{method:"GET",headers:a,signal:AbortSignal.timeout(h)});if(!o.ok)return null;const i=(await o.json()).data;if(!i)return null;const l=p(i.totalBalance)??0;return{provider:r,providerLabel:"SiliconFlow",planName:null,tiers:[],balance:{remaining:l,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"novita":{const o=await fetch(`${e}/v3/user/balance`,{method:"GET",headers:a,signal:AbortSignal.timeout(h)});if(!o.ok)return null;const n=await o.json();if(n.availableBalance===void 0)return null;const i=(p(n.availableBalance)??0)/1e4;return{provider:"novita",providerLabel:"Novita AI",planName:null,tiers:[],balance:{remaining:i,total:null,used:null,unit:"USD"},success:!0,error:null}}default:return null}}catch{return null}}async function ue(r,t,s){if(!t.trim())return{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:"API key is empty"};const e=P(s),a=E(r);if(a)switch(a.id){case"zhipu":return W(t);case"kimi":return J(t);case"minimax_cn":return O(t,!0);case"minimax_en":return O(t,!1);case"deepseek":return X(t);case"stepfun":return Q(t);case"siliconflow_cn":return z(t,!0);case"siliconflow_en":return z(t,!1);case"openrouter":return ee(t);case"novita":return te(t)}if(e){const n=await I(e,r,t);return n||m(e,e,`Quota API unavailable through base URL: ${r}`)}const o=await re(r,t);return o||{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:`Could not identify provider for base URL: ${r}`}}export{E as detectProvider,L as detectProviderFromModel,P as normalizeProviderId,ue as queryProviderQuota,$ as readKimiProviderSettings,le as readPiProviderSettings,ce as resolveKimiProviderSettings};
|
package/dist/core/proxy/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ProxyManager as s}from"./proxy-manager.js";import{ProxyManager as O,ProxyNoRelayRouteError as
|
|
1
|
+
import{ProxyManager as s}from"./proxy-manager.js";import{ProxyManager as O,ProxyNoRelayRouteError as g}from"./proxy-manager.js";import{RelayFetchError as A,RelayToggleCoordinator as d,deriveGatewayBaseUrls as h,fetchRelayCredentialWithRetry as I}from"./relay-credential-fetch.js";import{relayHostsForClientType as T}from"./relay-hosts.js";import{buildGatewayRoute as m}from"./gateway-route.js";let e=null;function u(r){return e||(e=new s(r)),e}function p(){return e}function P(){e=null}class y extends Error{agentName;code="RELAY_PROXY_UNAVAILABLE";constructor(o){super(`agent "${o}" has Grix relay enabled but the local MITM proxy is not running; refusing to start it (it would silently fall back to your own account). Fix the proxy or turn relay off.`),this.agentName=o,this.name="ProxyRelayUnavailableError"}}const l="localhost,127.0.0.1,::1",x="grix-relay-placeholder";function R(r,o,i){if(e?.isRelayExpectedButUnavailable(o))throw new y(o);const a=e?.getInjectionEnv(o);if(!a)return r;const t={...r,...a};i?.trim().toLowerCase()==="claude"&&!t.ANTHROPIC_API_KEY&&!t.ANTHROPIC_AUTH_TOKEN&&(t.ANTHROPIC_API_KEY=x);const n=r?.NO_PROXY??r?.no_proxy??process.env.NO_PROXY??process.env.no_proxy;return t.NO_PROXY=n&&n.trim()?`${n},${l}`:l,t}export{x as GRIX_RELAY_ANTHROPIC_API_KEY,O as ProxyManager,g as ProxyNoRelayRouteError,y as ProxyRelayUnavailableError,A as RelayFetchError,d as RelayToggleCoordinator,R as applyProxyEnv,m as buildGatewayRoute,h as deriveGatewayBaseUrls,I as fetchRelayCredentialWithRetry,p as getProxyManager,u as initProxyManager,T as relayHostsForClientType,P as resetProxyManagerForTest};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isRequestTimeoutError as l}from"../aibot/errors.js";class o extends Error{code;bizCode;constructor(e,t,r){super(e),this.code=t,this.bizCode=r,this.name="RelayFetchError"}}const d=15e3,h=4004;async function f(n,e){let t=null;for(let r=0;r<2;r++){let i;try{i=await n(e,d)}catch(s){if(l(s)){t=s;continue}throw new o(`relay credential request not sent: ${s instanceof Error?s.message:String(s)}`,"OFFLINE")}return p(i)}throw new o(`relay credential request timed out after 2 attempts: ${t instanceof Error?t.message:String(t)}`,"TIMEOUT")}function p(n){if(n.cmd==="error"){const r=n.payload??{};throw r.code===h?new o("server does not support relay_credential_request","UNSUPPORTED"):new o(`server error: ${r.msg??`code=${r.code}`}`,"FAILED",String(r.code??""))}const e=n.payload??{};if(e.status!=="ok")throw new o(e.error_msg||"relay credential issuing failed","FAILED",e.error_code);const t=String(e.api_key??"").trim();if(!t)throw new o("relay credential result missing api_key","FAILED");return{apiKey:t,anthropicBaseUrl:e.anthropic_base_url?.trim()||void 0,openaiBaseUrl:e.openai_base_url?.trim()||void 0,model:e.model?.trim()||void 0}}class w{chains=new Map;epochs=new Map;cancelWaiters=new Map;epoch(e){return this.epochs.get(e)??0}cancel(e){this.epochs.set(e,this.epoch(e)+1);const t=this.cancelWaiters.get(e);if(t)for(const r of t)r.notify()}assertCurrent(e,t){if(this.epoch(e)!==t)throw new o("relay enable cancelled by a later disable","CANCELLED")}waitCancelled(e,t){if(this.epoch(e)!==t)return{promise:Promise.resolve(),dispose:()=>{}};let r=this.cancelWaiters.get(e);r||(r=new Set,this.cancelWaiters.set(e,r));const i=r,s={epoch:t,notify:()=>{}},a=new Promise(c=>{s.notify=()=>{i.delete(s),i.size===0&&this.cancelWaiters.delete(e),c()}});return i.add(s),{promise:a,dispose:()=>{i.delete(s),i.size===0&&this.cancelWaiters.delete(e)}}}runExclusive(e,t){const i=(this.chains.get(e)??Promise.resolve()).catch(()=>{}).then(t),s=i.then(()=>{},()=>{});return this.chains.set(e,s),s.then(()=>{this.chains.get(e)===s&&this.chains.delete(e)}),i}}function y(n){const e=new URL(n.replace(/^wss:/,"https:").replace(/^ws:/,"http:"));return{anthropicBaseUrl:`${e.origin}/anthropic/v1`,openaiBaseUrl:`${e.origin}/openai/v1`}}export{o as RelayFetchError,w as RelayToggleCoordinator,y as deriveGatewayBaseUrls,f as fetchRelayCredentialWithRetry};
|
package/dist/grix.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),
|
|
2
|
+
import I from"node:path";import{writeFileSync as W}from"node:fs";import{Manager as de}from"./manager.js";import{ensureGrixDirs as pe,initLogger as me,log as a,installProcessLogRotation as fe,setConsoleOutput as ue}from"./core/log/index.js";import{HealthServer as ge,bindPortOrFail as X}from"./core/runtime/index.js";import{writePidFile as he,removePidFile as k,readDaemonPid as ye}from"./core/runtime/index.js";import{resolveRuntimePaths as F}from"./core/config/index.js";import{supportsDirectProviderConfig as H}from"./core/config/provider-env.js";import{ServiceManager as we}from"./service/service-manager.js";import{killProcessesByCommandLine as Re,isWindowsElevated as Ae}from"./service/process-control.js";import{acquireDaemonLock as Se,isLockHolderSameProcess as xe,readDaemonLock as Ee,releaseDaemonLock as T}from"./runtime/daemon-lock.js";import{writeDaemonStatus as L,removeDaemonStatus as ve}from"./runtime/service-state.js";import{AdminServer as be,generateToken as Pe,writeTokenFile as De}from"./core/admin/index.js";import{initSentry as Ie,closeSentry as _,reportFatal as B}from"./core/observability/sentry.js";import{initProxyManager as ke,getProxyManager as g,relayHostsForClientType as J}from"./core/proxy/index.js";import{stopProxyIfNobodyNeedsIt as Te,disableAllRelayAndRestart as Ce,setOneAgentRelayAndRestart as Y}from"./core/proxy/relay-orchestration.js";import{setAgentRelayCredential as q,RelayCredentialError as Ue}from"./core/proxy/relay-credential.js";import{RelayFetchError as z,RelayToggleCoordinator as $e}from"./core/proxy/relay-credential-fetch.js";import{applyHermesProfileRelay as Oe,clearHermesProfileRelay as Fe,discoverHermesProfiles as He}from"./core/proxy/hermes-profile-relay.js";import{disableRelayCredential as Le,enableRelayCredential as _e,RelayRouteError as Q}from"./core/proxy/relay-credential-router.js";import{resolveClientVersion as Be}from"./core/util/client-version.js";import{parseCliArgs as Me}from"./core/util/cli-args.js";const{command:w,flags:h,unknownFlags:M}=Me(process.argv.slice(2));if(M.length>0&&(console.error(`Unknown option${M.length>1?"s":""}: ${M.map(r=>`--${r}`).join(", ")}
|
|
3
|
+
Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),h.version&&(console.log(Be()),process.exit(0)),h.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
|
|
4
4
|
|
|
5
5
|
Usage: grix-connector <command> [options]
|
|
6
6
|
|
|
@@ -34,8 +34,8 @@ Examples:
|
|
|
34
34
|
grix-connector start # Start as system service
|
|
35
35
|
grix-connector status # Check service status
|
|
36
36
|
grix-connector restart # Restart the service
|
|
37
|
-
`),process.exit(0)),
|
|
37
|
+
`),process.exit(0)),w==="reload"){const r=ye();r||(console.error("reload failed: daemon is not running (no pid file)"),process.exit(1));try{process.kill(r,0)}catch{console.error(`reload failed: daemon process ${r} is not running (stale pid file)`),process.exit(1)}{const m=Ee(F().daemonLockFile);m&&m.pid===r&&!xe(m)&&(console.error(`reload failed: pid ${r} has been reused by another process (stale pid file)`),process.exit(1))}try{process.kill(r,"SIGHUP"),console.log(JSON.stringify({ok:!0,signaled:r},null,2)),process.exit(0)}catch(m){console.error(`reload failed: ${m instanceof Error?m.message:m}`),process.exit(1)}}const Z=["start","stop","restart","status"];if(w&&Z.includes(w)){process.platform==="win32"&&["start","stop","restart"].includes(w)&&!Ae()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
|
|
38
38
|
using Startup folder auto-start instead. For full Task Scheduler integration,
|
|
39
|
-
right-click the terminal and select "Run as administrator".`);const
|
|
40
|
-
Valid commands: ${
|
|
41
|
-
`),a.error("main",
|
|
39
|
+
right-click the terminal and select "Run as administrator".`);const r=F(),m=h["config-dir"]??(h.profile?I.join(r.configDir,h.profile):void 0),A=I.resolve(process.argv[1]||`${r.rootDir}/dist/grix.js`),c=new we({cliPath:A,nodePath:process.execPath});try{let p;switch(w){case"start":(await c.status({rootDir:r.rootDir})).installed?p=await c.start({rootDir:r.rootDir}):p=await c.install({rootDir:r.rootDir,configDir:m});break;case"stop":p=await c.stop({rootDir:r.rootDir});break;case"restart":(await c.status({rootDir:r.rootDir})).installed?p=await c.restart({rootDir:r.rootDir}):p=await c.install({rootDir:r.rootDir,configDir:m});break;case"status":p=await c.status({rootDir:r.rootDir});break}console.log(JSON.stringify(p,null,2)),process.exit(0)}catch(p){console.error(`${w} failed: ${p instanceof Error?p.message:p}`),process.exit(1)}}else w&&(console.error(`Unknown command: ${w}
|
|
40
|
+
Valid commands: ${Z.join(", ")}`),process.exit(1));const d=F(),Ne=h["config-dir"]??(h.profile?`${d.configDir}/${h.profile}`:void 0),o=new de,$=new ge,ee=Pe(),R=new be(ee);let N=!1;async function te(r){process.stderr.write(r.message+`
|
|
41
|
+
`),a.error("main",r.message.replace(/\n/g," \u2014 ")),await L(d.daemonStatusFile,{state:"failed",pid:process.pid,updated_at:Date.now(),reason:`port_bind_${r.kind}:${r.label}:${r.port}`}).catch(()=>{}),await _(),T(d.daemonLockFile).catch(()=>{}),k(),process.exit(1)}async function O(r){if(N)return;N=!0,a.info("main",`Received ${r}, shutting down...`),$.markShuttingDown();const m=setTimeout(()=>{a.error("main","Shutdown timed out, forcing exit"),T(d.daemonLockFile).catch(()=>{}),k(),process.exit(2)},1e4);try{await o.stop(),await g()?.stop().catch(()=>{}),await R.stop(),await $.stop(),await _(),await T(d.daemonLockFile),await ve(d.daemonStatusFile).catch(()=>{}),clearTimeout(m),k(),a.info("main","Shutdown complete"),process.exit(0)}catch(A){a.error("main",`Shutdown error: ${A}`),T(d.daemonLockFile).catch(()=>{}),k(),process.exit(2)}}async function Ge(){pe(),me(),await Ie(),fe(d.stdoutLogFile,d.stderrLogFile),ue(!1),process.platform==="win32"&&await Re("GrixConnectorDaemon",{platform:"win32"});try{await Se(d.daemonLockFile,d.rootDir)}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}he(),a.info("main",`grix-connector starting (PID ${process.pid})`),await L(d.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const r=parseInt(h["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await X({label:"health",port:r,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:n=>$.start(n)});e&&await te(e)}const m=I.join(d.dataDir,"health-port");W(m,String(r),"utf-8"),process.on("SIGINT",()=>O("SIGINT")),process.on("SIGTERM",()=>O("SIGTERM")),process.on("SIGHUP",()=>{N||(a.info("main","Received SIGHUP, reloading config..."),o.reload().then(e=>a.info("main",`reload done: ${JSON.stringify(e)}`)).catch(e=>a.error("main",`reload failed: ${e instanceof Error?e.message:e}`)))});let A="",c=0,p;process.on("uncaughtException",e=>{const n=e instanceof Error?e.stack??e.message:String(e);n===A?(c++,(c<=3||c%100===0)&&a.error("main",`Uncaught exception (x${c}): ${n}`)):(c>3&&a.error("main",`Previous exception repeated ${c} times total`),A=n,c=1,a.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),p||(p=setTimeout(()=>{c>3&&a.error("main",`Previous exception repeated ${c} times total`),A="",c=0,p=void 0},1e4).unref())),!re(e)&&(B(e,"uncaughtException"),O("uncaughtException"))}),process.on("unhandledRejection",e=>{a.error("main",`Unhandled rejection: ${e}`),!re(e)&&(B(e,"unhandledRejection"),O("unhandledRejection"))});const E=ke(d.dataDir);await E.clearRuntimeState();const v=E.getRelayAgents();if(v.length>0)try{await E.startIfAnyRelayAgent(),a.info("main",`mitm proxy started for relay agents: ${v.join(", ")} (per-agent; others stay direct)`)}catch(e){a.error("main",`mitm proxy FAILED to start (relay agents: ${v.join(", ")}): ${e}. These agents will REFUSE to start (they must not silently fall back to your own account). Fix the proxy or turn relay off for them. Other agents are unaffected.`)}else a.info("main",'mitm proxy idle (no agent has Grix relay enabled; enable: PUT /api/proxy/agents/<name>/enabled {"enabled":true})');if($.setStatusProvider(()=>o.getAgentsStatus()),await o.start(Ne),v.length>0){const e=o.getAgentsStatus();for(const n of v){const l=e.find(t=>t.name===n);if(!l)continue;const S=J(l.clientType);S.length!==0&&await E.setAgentRelayEnabled(n,!0,{relayHosts:S}).catch(t=>{a.warn("main",`failed to reconcile relay hosts for "${n}": ${t}`)})}}const G=parseInt(h["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);R.setAgentHandler({list:()=>o.getAgentsStatus(),add:e=>o.addAgent(e),remove:e=>o.removeAgent(e),restart:e=>o.restartAgent(e),reload:()=>o.reload()}),R.setUpgradeHandler({check:()=>o.checkUpgrade(),trigger:()=>o.triggerUpgrade()}),R.setProbeHandler({probeAll:e=>o.probeAll(e),probeOne:(e,n)=>o.probeOne(e,n)}),R.setInstallHandler({listInstallable:()=>o.listInstallable(),installAgent:e=>o.installAgent(e),getInstallProgress:e=>o.getInstallProgress(e)});let b=null;if(g()){const e=async()=>{const t=g();t&&await Te(t,o)&&a.info("main","no agent uses Grix relay anymore; mitm proxy stopped")};o.setRelayEnvSettledHandler(()=>{e().catch(t=>{a.warn("main",`failed to stop idle mitm proxy: ${t}`)})});const n=()=>{const t=g(),s=t.getRelayAgents(),i=t.getDegradedRelayAgents();return{enabled:s.length>0,relayAgents:s,staleRelayAgents:o.getAgentsWithStaleRelayEnv(),degradedRelayAgents:i,runtime:t.getRuntimeInfo(),config:t.getConfigSnapshot()}},l=new $e,S={status:()=>n(),setAgentRelay:async(t,s,i)=>{const y=g(),P={info:u=>a.info("main",u),warn:u=>a.warn("main",u)},D=u=>o.getAgentsStatus().find(x=>x.name===u),f=D(t);if(!s)return l.cancel(t),l.runExclusive(t,async()=>{if(f&&H(f.clientType))return await o.setAgentProvider(t,void 0),{...n(),restarted:!0,busy:!1};const{restarted:u,busy:x}=await Y(y,o,P,{agentName:t,enabled:!1});return{...n(),restarted:u,busy:x}});if(!f)throw Object.assign(new Error(`Agent "${t}" not found`),{code:"NOT_FOUND"});if(!H(f.clientType)&&J(f.clientType).length===0)throw Object.assign(new Error(`client type "${f.clientType??""}" does not support Grix relay`),{code:"UNSUPPORTED_CLIENT_TYPE"});return l.runExclusive(t,async()=>{const u=l.epoch(t),x=D(t);if(!x)throw Object.assign(new Error(`Agent "${t}" not found`),{code:"NOT_FOUND"});const ae=H(x.clientType),K=String(x.agentId??"").trim();if(!K)throw new Ue(`local agent "${t}" has no agent_id registered`,"MISSING_AGENT_ID");const V=l.waitCancelled(t,u);let U;try{U=await Promise.race([o.fetchRelayCredential(t,{model:i}),V.promise.then(()=>{throw new z("relay enable cancelled by a later disable","CANCELLED")})])}finally{V.dispose()}l.assertCurrent(t,u);const{restarted:se,busy:ie}=await q(y,o,D,P,{localName:t,agentId:K,virtualKey:U.apiKey,anthropicBaseUrl:U.anthropicBaseUrl,openaiBaseUrl:U.openaiBaseUrl,model:i},(le,ce)=>o.setAgentProvider(le,ce));if(l.epoch(t)!==u)throw ae?await o.setAgentProvider(t,void 0):await Y(y,o,P,{agentName:t,enabled:!1}),new z("relay enable cancelled by a later disable","CANCELLED");return{...n(),restarted:se,busy:ie}})},setAgentRelayCredential:async(t,s)=>{const i=g(),y=o.getAgentsStatus(),{restarted:P,busy:D}=await q(i,o,f=>y.find(C=>C.name===f),{info:f=>a.info("main",f),warn:f=>a.warn("main",f)},{localName:t,agentId:s.agentId,virtualKey:s.virtualKey,anthropicBaseUrl:s.anthropicBaseUrl,openaiBaseUrl:s.openaiBaseUrl,model:s.model},(f,C)=>o.setAgentProvider(f,C));return{...n(),restarted:P,busy:D}},disableAll:async()=>{const t=g();for(const i of o.getAgentsStatus())l.cancel(i.name);await Ce(t,o,{info:i=>a.info("main",i),warn:i=>a.warn("main",i)});const s=await o.clearDirectProviderConfigs();return s.length>0&&a.info("main",`relay disable-all cleared native provider config for: ${s.join(", ")}`),n()},setRoute:async(t,s)=>{const i=s,y=g();return y.setRoute({routeKey:t,targetBaseUrl:i.targetBaseUrl,...i.headers?{headers:i.headers}:{},...i.model?{model:i.model}:{},...i.modelMap?{modelMap:i.modelMap}:{},...i.passthrough?{passthrough:!0}:{},...i.relayOnly?{relayOnly:!0}:{},...i.codexResponsesToChat?{codexResponsesToChat:!0}:{}}),await y.persistConfig(),n()},deleteRoute:async t=>{const s=g();s.deleteRoute(t),await s.persistConfig()},setDefaultRoute:async t=>{const s=g();return s.setDefaultRouteKey(t),await s.persistConfig(),n()},setInterceptHosts:async t=>{const s=g();return s.setInterceptHosts(t),await s.persistConfig(),n()}};b=S,R.setProxyHandler(S)}const j={listAgents:()=>o.getAgentsStatus(),enableManaged:(e,n,l)=>{if(!b)throw new Q(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return b.setAgentRelayCredential(e,{agentId:n,virtualKey:l.virtualKey,anthropicBaseUrl:l.anthropicBaseUrl,openaiBaseUrl:l.openaiBaseUrl,model:l.model})},disableManaged:e=>{if(!b)throw new Q(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return b.setAgentRelay(e,!1)},enableHermesProfile:(e,n)=>Oe({agentId:e,baseUrl:n.openaiBaseUrl??"",virtualKey:n.virtualKey,model:n.model??""}),disableHermesProfile:e=>Fe(e)};R.setRelayCredentialHandler({listHermesProfiles:()=>He(),enable:async(e,n)=>{const{target:l,result:S}=await _e(j,e,n);return{target:l,...S}},disable:async e=>{const{target:n,result:l}=await Le(j,e);return{target:n,...l}}});{const e=await X({label:"admin",port:G,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:n=>R.start(n)});e&&await te(e)}const oe=I.join(d.dataDir,"admin-token"),ne=I.join(d.dataDir,"admin-port");De(oe,ee),W(ne,String(G),"utf-8"),await L(d.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),a.info("main","grix-connector ready")}Ge().catch(async r=>{a.error("main",`Fatal: ${r}`),B(r,"startup"),await _(),T(d.daemonLockFile).catch(()=>{}),k(),process.exit(1)});const je=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function re(r){return r instanceof Error&&"code"in r?je.has(r.code):!1}
|
package/dist/log.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as
|
|
2
|
-
`)},error(o,r,...
|
|
1
|
+
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as t}from"node:path";import{homedir as m}from"node:os";const i=t(m(),".grix"),s={base:i,config:t(i,"config"),log:t(i,"log"),data:t(i,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=t(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...n){const e=`${c()} [${o}] ${r}${n.length?" "+n.map(String).join(" "):""}`;console.log(e),a?.write(e+`
|
|
2
|
+
`)},error(o,r,...n){const e=`${c()} [${o}] ERROR ${r}${n.length?" "+n.map(String).join(" "):""}`;console.error(e),a?.write(e+`
|
|
3
3
|
`)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
|