grix-connector 3.15.3 → 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.
@@ -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 A}from"../files/file-serve.js";import{AUTH_CODE_AGENT_DELETED as p,KICKED_REASON_AGENT_DELETED as S,AgentDeletedError as v,RequestTimeoutError as y}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}}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=A();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};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 y(`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{detectProvider as o,detectProviderFromModel as i,normalizeProviderId as t,queryProviderQuota as d,readKimiProviderSettings as v,resolveKimiProviderSettings as a}from"./providers.js";import{ProviderQuotaService as s,sharedProviderQuotaService as P,resolveQuotaBaseUrl as u}from"./service.js";import{providerQuotaToRateLimits as l,providerQuotaToCodexRateLimits as p}from"./presentation.js";export{s as ProviderQuotaService,o as detectProvider,i as detectProviderFromModel,t as normalizeProviderId,p as providerQuotaToCodexRateLimits,l as providerQuotaToRateLimits,d as queryProviderQuota,v as readKimiProviderSettings,a as resolveKimiProviderSettings,u as resolveQuotaBaseUrl,P as sharedProviderQuotaService};
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 L}from"node:crypto";import{readFileSync as y,writeFileSync as j,mkdirSync as P,rmdirSync as x,statSync as z}from"node:fs";import{homedir as O}from"node:os";import{join as _}from"node:path";const R=new Set(["zhipu","kimi","minimax_cn","minimax_en","deepseek","stepfun","siliconflow_cn","siliconflow_en","openrouter","novita"]),H={zai:"zhipu","z.ai":"zhipu",glm:"zhipu",bigmodel:"zhipu",moonshot:"kimi",minimax:"minimax_cn",siliconflow:"siliconflow_cn"};function U(s){const t=s?.trim().toLowerCase();if(!t)return null;const r=H[t]??t;return R.has(r)?r:null}function B(s){const t=s.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 ie(s){const t=s?.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 f=1e4;function d(s,t,r){return{provider:s,providerLabel:t,planName:null,tiers:[],balance:null,success:!1,error:r}}function m(s){if(typeof s=="number")return s;if(typeof s=="string"){const t=Number(s);return Number.isFinite(t)?t:null}return null}function w(s){if(!Number.isFinite(s)||s<=0)return null;try{return new Date(s).toISOString()}catch{return null}}function v(s){if(typeof s=="string")return s;if(typeof s=="number"){const t=s<1e12?s*1e3:s;return w(t)}return null}function N(){return(process.env.KIMI_CODE_HOME||"").trim()||_(O(),".kimi-code")}function T(s){const t=s||N(),r=300*1e3;let e;try{const a=_(t,"config.toml"),n=y(a,"utf8"),i=n.indexOf('[providers."managed:kimi-code"]');if(i>=0){const c=n.indexOf(`
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};
@@ -1 +1 @@
1
- import{ProxyManager as s}from"./proxy-manager.js";import{ProxyManager as O,ProxyNoRelayRouteError as A}from"./proxy-manager.js";import{relayHostsForClientType as g}from"./relay-hosts.js";import{buildGatewayRoute as N}from"./gateway-route.js";let o=null;function u(r){return o||(o=new s(r)),o}function P(){return o}function p(){o=null}class x extends Error{agentName;code="RELAY_PROXY_UNAVAILABLE";constructor(e){super(`agent "${e}" 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=e,this.name="ProxyRelayUnavailableError"}}const l="localhost,127.0.0.1,::1",y="grix-relay-placeholder";function _(r,e,i){if(o?.isRelayExpectedButUnavailable(e))throw new x(e);const a=o?.getInjectionEnv(e);if(!a)return r;const t={...r,...a};i?.trim().toLowerCase()==="claude"&&!t.ANTHROPIC_API_KEY&&!t.ANTHROPIC_AUTH_TOKEN&&(t.ANTHROPIC_API_KEY=y);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{y as GRIX_RELAY_ANTHROPIC_API_KEY,O as ProxyManager,A as ProxyNoRelayRouteError,x as ProxyRelayUnavailableError,_ as applyProxyEnv,N as buildGatewayRoute,P as getProxyManager,u as initProxyManager,g as relayHostsForClientType,p as resetProxyManagerForTest};
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};
@@ -0,0 +1 @@
1
+ import{join as o}from"node:path";function i(n,t){const{home:e}=t,l=t.cwd?.trim()||void 0,r=t.env??process.env;switch(n){case"claude":return{globalRoot:o(e,".claude","skills"),projectRoot:l?o(l,".claude","skills"):null};case"codex":{const s=r.CODEX_HOME?.trim()||o(e,".codex");return{globalRoot:o(s,"skills"),projectRoot:l?o(l,".codex","skills"):null}}case"gemini":return{globalRoot:o(e,".gemini","skills"),projectRoot:l?o(l,".gemini","skills"):null};case"qwen":return{globalRoot:o(e,".qwen","skills"),projectRoot:l?o(l,".qwen","skills"):null};case"pi":return{globalRoot:o(e,".pi","agent","skills"),projectRoot:l?o(l,".pi","skills"):null};case"kiro":return{globalRoot:o(e,".kiro","skills"),projectRoot:l?o(l,".kiro","skills"):null};case"cursor":return{globalRoot:o(e,".cursor","skills"),projectRoot:l?o(l,".cursor","skills"):null};case"codewhale":return{globalRoot:o(e,".codewhale","skills"),projectRoot:l?o(l,".codewhale","skills"):null};case"opencode":{const s=r.XDG_CONFIG_HOME?.trim()||o(e,".config");return{globalRoot:o(s,"opencode","skills"),projectRoot:null}}case"reasonix":return{globalRoot:o(e,".reasonix","skills"),projectRoot:null};case"kimi":{const s=r.KIMI_CODE_HOME?.trim()||o(e,".kimi-code");return{globalRoot:o(s,"skills"),projectRoot:null}}default:return{globalRoot:null,projectRoot:null}}}export{i as resolveEnableRoots};
@@ -0,0 +1 @@
1
+ import{readFileSync as l}from"node:fs";import{join as m}from"node:path";import{parseSkillFrontmatter as d}from"../../adapter/claude/skill-scanner.js";import{readSkillManifestSync as a}from"./manifest.js";import{resolveEnableRoots as f}from"./enable-roots.js";import{computeEnableScope as c,isSystemSkillEntry as p}from"./skill-enable.js";function u(t,o){try{const r=l(m(t,o,"SKILL.md"),"utf8");return d(r).description??""}catch{return""}}function R(t){const o=a(t.skillsDir),r=f(t.mode,{home:t.home,cwd:t.cwd,env:t.env}),n=[];for(const[i,e]of Object.entries(o.skills)){const s=p(e);n.push({name:i,description:u(t.skillsDir,e.dir),digest:e.digest,dir:e.dir,owner_id:e.owner_id,system:s,enable_scopes:{global:c({name:i,targetRoot:r.globalRoot,system:s,sourceDigest:e.digest}),project:c({name:i,targetRoot:r.projectRoot,system:s,sourceDigest:e.digest})}})}return n}export{R as buildLibrarySkills};
@@ -0,0 +1 @@
1
+ import{readFile as s,writeFile as l}from"node:fs/promises";import{readFileSync as a}from"node:fs";import{join as n}from"node:path";const i=".grix-sync.json";function e(r){try{const t=JSON.parse(r);if(t&&typeof t=="object"&&t.skills)return t}catch{}return null}async function u(r){try{const t=await s(n(r,i),"utf8");return e(t)??{skills:{}}}catch{return{skills:{}}}}function p(r){try{const t=a(n(r,i),"utf8");return e(t)??{skills:{}}}catch{return{skills:{}}}}async function y(r,t){await l(n(r,i),JSON.stringify(t,null,2),"utf8")}export{i as MANIFEST_FILE,u as readSkillManifest,p as readSkillManifestSync,y as writeSkillManifest};
@@ -0,0 +1 @@
1
+ import{existsSync as m,lstatSync as w,readFileSync as D,realpathSync as $}from"node:fs";import{mkdir as I,readFile as N,realpath as k,rm as R,rmdir as M,symlink as A,unlink as C}from"node:fs/promises";import{dirname as O,join as a}from"node:path";import{parseSkillFrontmatter as K,MANAGED_MARKER as h}from"../../adapter/claude/skill-scanner.js";import{computeContentDigest as y}from"./sync-state.js";import{readSkillManifest as x}from"./manifest.js";import{resolveEnableRoots as F}from"./enable-roots.js";class o extends Error{code;constructor(t,r){super(t),this.code=r}}function p(e){return e.trim().toLowerCase().startsWith("grix-")}function j(e){return e?e.system===!0||e.owner_id==="0":!1}function P(e){return e instanceof Error?e.message:String(e)}function T(e){try{const t=D(a(e,"SKILL.md"),"utf8");return y(t)}catch{return null}}function B(e){if(!e.targetRoot)return"unavailable";if(p(e.name)||e.system===!0)return"blocked";const t=a(e.targetRoot,e.name);let r;try{r=w(t)}catch{return"none"}if(r.isSymbolicLink()){let n;try{n=$(t)}catch{return"broken"}return m(a(n,h))?"blocked":"link"}if(r.isDirectory()){if(m(a(t,h)))return"blocked";const n=T(t);return n===null?"conflict":n===e.sourceDigest?"unmanaged":"conflict"}return"conflict"}async function G(e,t){try{const[r,n]=await Promise.all([k(e),k(t)]);return r===n}catch{return!1}}async function U(e,t){await I(O(t),{recursive:!0}),await A(e,t,process.platform==="win32"?"junction":"dir")}async function S(e){process.platform==="win32"?await M(e):await C(e)}const g=new Map;function b(e,t){return`${e}:${t}`}function E(e,t){const n=(g.get(e)??Promise.resolve()).catch(()=>{}).then(t);return g.set(e,n.catch(()=>{})),n}function V(e,t){const r=e.skills[t];if(!r)throw new o(`skill "${t}" not found in library`,"SKILL_NOT_FOUND");return r}async function q(e,t,r){const n=a(e,r.dir);let s;try{s=await N(a(n,"SKILL.md"),"utf8")}catch(c){throw new o(`failed to read library skill "${t}": ${P(c)}`,"SKILL_FILE_MISSING")}const i=K(s);if(i.name!==t)throw new o(`frontmatter name "${i.name}" does not match library skill name "${t}"`,"NAME_MISMATCH");return{sourceDir:n,digest:y(s)}}function L(e,t){const r=F(e.mode,{home:e.home,cwd:e.cwd,env:e.env}),n=t==="global"?r.globalRoot:r.projectRoot;if(!n)throw new o(t==="project"?"project scope unavailable: no session working directory is bound":"skill enable is not supported for this agent mode","SCOPE_UNAVAILABLE");return n}function v(e,t){if(!e.trim())throw new o("name is required","MISSING_SKILL_NAME");if(t!=="global"&&t!=="project")throw new o(`invalid scope: ${t}`,"INVALID_SCOPE")}async function Z(e,t){const r=t.name.trim();v(r,t.scope);const n=t.scope;return E(b(n,r),async()=>{const s=await x(e.skillsDir),i=V(s,r),{sourceDir:c,digest:_}=await q(e.skillsDir,r,i);if(p(r))throw new o(`"${r}" uses the reserved "grix-" prefix and cannot be enabled`,"BLOCKED");const f=j(i);if(f)throw new o(`"${r}" is a platform system skill and cannot be enabled`,"BLOCKED");const d=L(e,n),u=a(d,r),l=B({name:r,targetRoot:d,system:f,sourceDigest:_});if(l==="blocked")throw new o(`"${r}" target slot is managed by the connector`,"BLOCKED");if(l==="conflict")throw new o(`"${r}" already exists locally with different content and cannot be overwritten`,"CONFLICT");if(l==="unmanaged"&&t.force!=="replace_with_link")throw new o(`"${r}" already exists locally as a real directory; pass force="replace_with_link" to overwrite`,"NEEDS_FORCE");if(l==="link"){if(await G(u,c))return{name:r,scope:n,status:"link",path:u,changed:!1};if(t.force!=="replace_link")throw new o(`"${r}" is already linked elsewhere; pass force="replace_link" to overwrite`,"NEEDS_FORCE")}return l==="broken"||l==="link"?await S(u):l==="unmanaged"&&await R(u,{recursive:!0,force:!0}),await U(c,u),{name:r,scope:n,status:"link",path:u,changed:!0}})}async function ee(e,t){const r=t.name.trim();v(r,t.scope);const n=t.scope;return E(b(n,r),async()=>{const s=L(e,n),i=a(s,r);let c;try{c=w(i)}catch{return{name:r,scope:n,path:i,removed:!1}}if(!c.isSymbolicLink())throw new o(`"${r}" at ${i} is a real directory, not a connector-managed link, and cannot be disabled`,"NOT_MANAGED");return await S(i),{name:r,scope:n,path:i,removed:!0}})}export{o as SkillEnableError,B as computeEnableScope,ee as disableSkill,Z as enableSkill,j as isSystemSkillEntry};
@@ -1 +1 @@
1
- import{mkdir as d,readFile as $,writeFile as y,rm as p}from"node:fs/promises";import{join as u}from"node:path";import{createHash as v}from"node:crypto";import{log as a}from"../log/index.js";const S=6e4,m=".grix-sync.json",w=15e3;class _{configs;skillsDir;timer=null;running=!1;rerunPending=!1;stopped=!1;fetchImpl;intervalMs;constructor(t,i,n={}){this.configs=t,this.skillsDir=i,this.intervalMs=n.intervalMs??S,this.fetchImpl=n.fetchImpl??fetch}async start(){this.stopped=!1,await this.syncOnce().catch(t=>a.warn("skill-sync",`initial sync failed: ${o(t)}`)),this.timer=setInterval(()=>{this.syncOnce().catch(t=>a.warn("skill-sync",`sync failed: ${o(t)}`))},this.intervalMs),this.timer.unref?.()}stop(){this.stopped=!0,this.rerunPending=!1,this.timer&&(clearInterval(this.timer),this.timer=null)}triggerSync(){if(this.running){this.rerunPending=!0;return}this.syncOnce().catch(t=>a.warn("skill-sync",`triggered sync failed: ${o(t)}`))}async syncOnce(){if(!this.running){this.running=!0;try{const t=await this.pickReachable();if(!t)return;const{cfg:i,remote:n}=t;await d(this.skillsDir,{recursive:!0});const e=await this.readManifest(),c=new Set(n.map(s=>s.name));for(const s of n){const l=e.skills[s.name];if(l&&l.digest===s.digest)continue;const h=I(s.name);if(!h){a.warn("skill-sync",`skip skill with unsafe name: ${s.name}`);continue}const f=await this.fetchContent(i,s.id);if(f!==null)try{await d(u(this.skillsDir,h),{recursive:!0}),await y(u(this.skillsDir,h,"SKILL.md"),f,"utf8"),e.skills[s.name]={id:s.id,version:s.version,digest:s.digest,dir:h},l&&l.dir!==h&&!this.dirSharedByOthers(e,s.name,l.dir)&&await p(u(this.skillsDir,l.dir),{recursive:!0,force:!0}).catch(()=>{})}catch(g){a.warn("skill-sync",`write skill "${s.name}" failed: ${o(g)}`)}}for(const s of Object.keys(e.skills)){if(c.has(s))continue;const l=e.skills[s];this.dirSharedByOthers(e,s,l.dir)||await p(u(this.skillsDir,l.dir),{recursive:!0,force:!0}).catch(()=>{}),delete e.skills[s]}await this.writeManifest(e)}finally{this.running=!1,this.rerunPending&&(this.rerunPending=!1,(this.timer!==null||!this.stopped)&&this.syncOnce().catch(t=>a.warn("skill-sync",`rerun sync failed: ${o(t)}`)))}}}dirSharedByOthers(t,i,n){return Object.entries(t.skills).some(([e,c])=>e!==i&&c.dir===n)}async pickReachable(){for(const t of this.configs){if(!t.apiKey||!t.wsUrl)continue;const i=await this.fetchList(t);if(i!==null)return{cfg:t,remote:i}}return null}async fetchList(t){const i=`${k(t.wsUrl)}/v1/agent-api/skills`;try{const n=await this.fetchImpl(i,{headers:{Authorization:`Bearer ${t.apiKey}`},signal:AbortSignal.timeout(w)});if(!n.ok)return a.warn("skill-sync",`list returned ${n.status}`),null;const e=await n.json();return e.code!==0?null:e.data?.items??[]}catch(n){return a.warn("skill-sync",`list error: ${o(n)}`),null}}async fetchContent(t,i){const n=`${k(t.wsUrl)}/v1/agent-api/skills/${encodeURIComponent(i)}/content`;try{const e=await this.fetchImpl(n,{headers:{Authorization:`Bearer ${t.apiKey}`},signal:AbortSignal.timeout(w)});if(!e.ok)return a.warn("skill-sync",`content ${i} returned ${e.status}`),null;const c=await e.json();return c.code!==0?null:typeof c.data?.content=="string"?c.data.content:null}catch(e){return a.warn("skill-sync",`content ${i} error: ${o(e)}`),null}}async readManifest(){try{const t=await $(u(this.skillsDir,m),"utf8"),i=JSON.parse(t);if(i&&typeof i=="object"&&i.skills)return i}catch{}return{skills:{}}}async writeManifest(t){await y(u(this.skillsDir,m),JSON.stringify(t,null,2),"utf8")}}function k(r){return new URL(r.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function I(r){const t=r.trim().replace(/[/\\]/g,"_").replace(/[<>:"|?*\u0000-\u001f]/g,"_").replace(/^\.+/,"").replace(/[. ]+$/,"");return!t||t.includes("..")||/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(t)?null:t!==r?`${t}-${v("sha1").update(r).digest("hex").slice(0,8)}`:t}function o(r){return r instanceof Error?r.message:String(r)}export{_ as SkillSyncer};
1
+ import{mkdir as y,writeFile as g,rm as m}from"node:fs/promises";import{join as h}from"node:path";import{createHash as S}from"node:crypto";import{log as r}from"../log/index.js";import{readSkillManifest as $,writeSkillManifest as v}from"./manifest.js";const _=6e4,p=15e3;class L{configs;skillsDir;timer=null;running=!1;rerunPending=!1;stopped=!1;fetchImpl;intervalMs;onSyncSuccessHandler=null;constructor(t,s,n={}){this.configs=t,this.skillsDir=s,this.intervalMs=n.intervalMs??_,this.fetchImpl=n.fetchImpl??fetch}async start(){this.stopped=!1,await this.syncOnce().catch(t=>r.warn("skill-sync",`initial sync failed: ${c(t)}`)),this.timer=setInterval(()=>{this.syncOnce().catch(t=>r.warn("skill-sync",`sync failed: ${c(t)}`))},this.intervalMs),this.timer.unref?.()}stop(){this.stopped=!0,this.rerunPending=!1,this.timer&&(clearInterval(this.timer),this.timer=null)}setOnSyncSuccess(t){this.onSyncSuccessHandler=t}triggerSync(){if(this.running){this.rerunPending=!0;return}this.syncOnce().catch(t=>r.warn("skill-sync",`triggered sync failed: ${c(t)}`))}async syncOnce(){if(!this.running){this.running=!0;try{const t=await this.pickReachable();if(!t)return;const{cfg:s,remote:n}=t;await y(this.skillsDir,{recursive:!0});const e=await $(this.skillsDir),o=new Set(n.map(i=>i.name));for(const i of n){const a=e.skills[i.name],f=i.owner_id==="0";if(a&&a.digest===i.digest){e.skills[i.name]={...a,id:i.id,version:i.version,owner_id:i.owner_id,system:f};continue}const u=O(i.name);if(!u){r.warn("skill-sync",`skip skill with unsafe name: ${i.name}`);continue}const d=await this.fetchContent(s,i.id);if(d!==null)try{await y(h(this.skillsDir,u),{recursive:!0}),await g(h(this.skillsDir,u,"SKILL.md"),d,"utf8"),e.skills[i.name]={id:i.id,version:i.version,digest:i.digest,dir:u,owner_id:i.owner_id,system:f},a&&a.dir!==u&&!this.dirSharedByOthers(e,i.name,a.dir)&&await m(h(this.skillsDir,a.dir),{recursive:!0,force:!0}).catch(()=>{})}catch(k){r.warn("skill-sync",`write skill "${i.name}" failed: ${c(k)}`)}}for(const i of Object.keys(e.skills)){if(o.has(i))continue;const a=e.skills[i];this.dirSharedByOthers(e,i,a.dir)||await m(h(this.skillsDir,a.dir),{recursive:!0,force:!0}).catch(()=>{}),delete e.skills[i]}await v(this.skillsDir,e);try{this.onSyncSuccessHandler?.()}catch(i){r.warn("skill-sync",`onSyncSuccess handler failed: ${c(i)}`)}}finally{this.running=!1,this.rerunPending&&(this.rerunPending=!1,(this.timer!==null||!this.stopped)&&this.syncOnce().catch(t=>r.warn("skill-sync",`rerun sync failed: ${c(t)}`)))}}}dirSharedByOthers(t,s,n){return Object.entries(t.skills).some(([e,o])=>e!==s&&o.dir===n)}async pickReachable(){for(const t of this.configs){if(!t.apiKey||!t.wsUrl)continue;const s=await this.fetchList(t);if(s!==null)return{cfg:t,remote:s}}return null}async fetchList(t){const s=`${w(t.wsUrl)}/v1/agent-api/skills`;try{const n=await this.fetchImpl(s,{headers:{Authorization:`Bearer ${t.apiKey}`},signal:AbortSignal.timeout(p)});if(!n.ok)return r.warn("skill-sync",`list returned ${n.status}`),null;const e=await n.json();return e.code!==0?null:e.data?.items??[]}catch(n){return r.warn("skill-sync",`list error: ${c(n)}`),null}}async fetchContent(t,s){const n=`${w(t.wsUrl)}/v1/agent-api/skills/${encodeURIComponent(s)}/content`;try{const e=await this.fetchImpl(n,{headers:{Authorization:`Bearer ${t.apiKey}`},signal:AbortSignal.timeout(p)});if(!e.ok)return r.warn("skill-sync",`content ${s} returned ${e.status}`),null;const o=await e.json();return o.code!==0?null:typeof o.data?.content=="string"?o.data.content:null}catch(e){return r.warn("skill-sync",`content ${s} error: ${c(e)}`),null}}}function w(l){return new URL(l.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function O(l){const t=l.trim().replace(/[/\\]/g,"_").replace(/[<>:"|?*\u0000-\u001f]/g,"_").replace(/^\.+/,"").replace(/[. ]+$/,"");return!t||t.includes("..")||/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(t)?null:t!==l?`${t}-${S("sha1").update(l).digest("hex").slice(0,8)}`:t}function c(l){return l instanceof Error?l.message:String(l)}export{L as SkillSyncer};
package/dist/grix.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import b from"node:path";import{writeFileSync as M}from"node:fs";import{Manager as q}from"./manager.js";import{ensureGrixDirs as z,initLogger as Q,log as a,installProcessLogRotation as Z,setConsoleOutput as ee}from"./core/log/index.js";import{HealthServer as te,bindPortOrFail as B}from"./core/runtime/index.js";import{writePidFile as re,removePidFile as I,readDaemonPid as oe}from"./core/runtime/index.js";import{resolveRuntimePaths as U}from"./core/config/index.js";import{supportsDirectProviderConfig as ne}from"./core/config/provider-env.js";import{ServiceManager as ae}from"./service/service-manager.js";import{killProcessesByCommandLine as se,isWindowsElevated as ie}from"./service/process-control.js";import{acquireDaemonLock as le,isLockHolderSameProcess as ce,readDaemonLock as de,releaseDaemonLock as k}from"./runtime/daemon-lock.js";import{writeDaemonStatus as F,removeDaemonStatus as pe}from"./runtime/service-state.js";import{AdminServer as me,generateToken as ge,writeTokenFile as fe}from"./core/admin/index.js";import{initSentry as ue,closeSentry as O,reportFatal as C}from"./core/observability/sentry.js";import{initProxyManager as he,getProxyManager as g,relayHostsForClientType as G}from"./core/proxy/index.js";import{stopProxyIfNobodyNeedsIt as ye,disableAllRelayAndRestart as we,setOneAgentRelayAndRestart as Re}from"./core/proxy/relay-orchestration.js";import{setAgentRelayCredential as xe}from"./core/proxy/relay-credential.js";import{applyHermesProfileRelay as Se,clearHermesProfileRelay as Ae,discoverHermesProfiles as ve}from"./core/proxy/hermes-profile-relay.js";import{disableRelayCredential as Ee,enableRelayCredential as Pe,RelayRouteError as j}from"./core/proxy/relay-credential-router.js";import{resolveClientVersion as De}from"./core/util/client-version.js";import{parseCliArgs as be}from"./core/util/cli-args.js";const{command:h,flags:f,unknownFlags:H}=be(process.argv.slice(2));if(H.length>0&&(console.error(`Unknown option${H.length>1?"s":""}: ${H.map(t=>`--${t}`).join(", ")}
3
- Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),f.version&&(console.log(De()),process.exit(0)),f.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
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)),h==="reload"){const t=oe();t||(console.error("reload failed: daemon is not running (no pid file)"),process.exit(1));try{process.kill(t,0)}catch{console.error(`reload failed: daemon process ${t} is not running (stale pid file)`),process.exit(1)}{const p=de(U().daemonLockFile);p&&p.pid===t&&!ce(p)&&(console.error(`reload failed: pid ${t} has been reused by another process (stale pid file)`),process.exit(1))}try{process.kill(t,"SIGHUP"),console.log(JSON.stringify({ok:!0,signaled:t},null,2)),process.exit(0)}catch(p){console.error(`reload failed: ${p instanceof Error?p.message:p}`),process.exit(1)}}const K=["start","stop","restart","status"];if(h&&K.includes(h)){process.platform==="win32"&&["start","stop","restart"].includes(h)&&!ie()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
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 t=U(),p=f["config-dir"]??(f.profile?b.join(t.configDir,f.profile):void 0),w=b.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),l=new ae({cliPath:w,nodePath:process.execPath});try{let d;switch(h){case"start":(await l.status({rootDir:t.rootDir})).installed?d=await l.start({rootDir:t.rootDir}):d=await l.install({rootDir:t.rootDir,configDir:p});break;case"stop":d=await l.stop({rootDir:t.rootDir});break;case"restart":(await l.status({rootDir:t.rootDir})).installed?d=await l.restart({rootDir:t.rootDir}):d=await l.install({rootDir:t.rootDir,configDir:p});break;case"status":d=await l.status({rootDir:t.rootDir});break}console.log(JSON.stringify(d,null,2)),process.exit(0)}catch(d){console.error(`${h} failed: ${d instanceof Error?d.message:d}`),process.exit(1)}}else h&&(console.error(`Unknown command: ${h}
40
- Valid commands: ${K.join(", ")}`),process.exit(1));const c=U(),Ie=f["config-dir"]??(f.profile?`${c.configDir}/${f.profile}`:void 0),s=new q,T=new te,V=ge(),y=new me(V);let L=!1;async function X(t){process.stderr.write(t.message+`
41
- `),a.error("main",t.message.replace(/\n/g," \u2014 ")),await F(c.daemonStatusFile,{state:"failed",pid:process.pid,updated_at:Date.now(),reason:`port_bind_${t.kind}:${t.label}:${t.port}`}).catch(()=>{}),await O(),k(c.daemonLockFile).catch(()=>{}),I(),process.exit(1)}async function $(t){if(L)return;L=!0,a.info("main",`Received ${t}, shutting down...`),T.markShuttingDown();const p=setTimeout(()=>{a.error("main","Shutdown timed out, forcing exit"),k(c.daemonLockFile).catch(()=>{}),I(),process.exit(2)},1e4);try{await s.stop(),await g()?.stop().catch(()=>{}),await y.stop(),await T.stop(),await O(),await k(c.daemonLockFile),await pe(c.daemonStatusFile).catch(()=>{}),clearTimeout(p),I(),a.info("main","Shutdown complete"),process.exit(0)}catch(w){a.error("main",`Shutdown error: ${w}`),k(c.daemonLockFile).catch(()=>{}),I(),process.exit(2)}}async function ke(){z(),Q(),await ue(),Z(c.stdoutLogFile,c.stderrLogFile),ee(!1),process.platform==="win32"&&await se("GrixConnectorDaemon",{platform:"win32"});try{await le(c.daemonLockFile,c.rootDir)}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}re(),a.info("main",`grix-connector starting (PID ${process.pid})`),await F(c.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const t=parseInt(f["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await B({label:"health",port:t,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:r=>T.start(r)});e&&await X(e)}const p=b.join(c.dataDir,"health-port");M(p,String(t),"utf-8"),process.on("SIGINT",()=>$("SIGINT")),process.on("SIGTERM",()=>$("SIGTERM")),process.on("SIGHUP",()=>{L||(a.info("main","Received SIGHUP, reloading config..."),s.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 w="",l=0,d;process.on("uncaughtException",e=>{const r=e instanceof Error?e.stack??e.message:String(e);r===w?(l++,(l<=3||l%100===0)&&a.error("main",`Uncaught exception (x${l}): ${r}`)):(l>3&&a.error("main",`Previous exception repeated ${l} times total`),w=r,l=1,a.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),d||(d=setTimeout(()=>{l>3&&a.error("main",`Previous exception repeated ${l} times total`),w="",l=0,d=void 0},1e4).unref())),!W(e)&&(C(e,"uncaughtException"),$("uncaughtException"))}),process.on("unhandledRejection",e=>{a.error("main",`Unhandled rejection: ${e}`),!W(e)&&(C(e,"unhandledRejection"),$("unhandledRejection"))});const x=he(c.dataDir);await x.clearRuntimeState();const S=x.getRelayAgents();if(S.length>0)try{await x.startIfAnyRelayAgent(),a.info("main",`mitm proxy started for relay agents: ${S.join(", ")} (per-agent; others stay direct)`)}catch(e){a.error("main",`mitm proxy FAILED to start (relay agents: ${S.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(T.setStatusProvider(()=>s.getAgentsStatus()),await s.start(Ie),S.length>0){const e=s.getAgentsStatus();for(const r of S){const m=e.find(n=>n.name===r);if(!m)continue;const o=G(m.clientType);o.length!==0&&await x.setAgentRelayEnabled(r,!0,{relayHosts:o}).catch(n=>{a.warn("main",`failed to reconcile relay hosts for "${r}": ${n}`)})}}const _=parseInt(f["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);y.setAgentHandler({list:()=>s.getAgentsStatus(),add:e=>s.addAgent(e),remove:e=>s.removeAgent(e),restart:e=>s.restartAgent(e),reload:()=>s.reload()}),y.setUpgradeHandler({check:()=>s.checkUpgrade(),trigger:()=>s.triggerUpgrade()}),y.setProbeHandler({probeAll:e=>s.probeAll(e),probeOne:(e,r)=>s.probeOne(e,r)}),y.setInstallHandler({listInstallable:()=>s.listInstallable(),installAgent:e=>s.installAgent(e),getInstallProgress:e=>s.getInstallProgress(e)});let A=null;if(g()){const e=async()=>{const o=g();o&&await ye(o,s)&&a.info("main","no agent uses Grix relay anymore; mitm proxy stopped")};s.setRelayEnvSettledHandler(()=>{e().catch(o=>{a.warn("main",`failed to stop idle mitm proxy: ${o}`)})});const r=()=>{const o=g(),n=o.getRelayAgents(),i=o.getDegradedRelayAgents();return{enabled:n.length>0,relayAgents:n,staleRelayAgents:s.getAgentsWithStaleRelayEnv(),degradedRelayAgents:i,runtime:o.getRuntimeInfo(),config:o.getConfigSnapshot()}},m={status:()=>r(),setAgentRelay:async(o,n)=>{const i=g(),R=s.getAgentsStatus().find(D=>D.name===o);if(R&&ne(R.clientType))return n?a.info("main",`relay enable for native provider agent "${o}" is a no-op; use relay-credential to enable`):await s.setAgentProvider(o,void 0),{...r(),restarted:!n,busy:!1};let E;if(n){if(!R)throw Object.assign(new Error(`Agent "${o}" not found`),{code:"NOT_FOUND"});if(E=G(R.clientType),E.length===0)throw Object.assign(new Error(`client type "${R.clientType??""}" does not support Grix relay`),{code:"UNSUPPORTED_CLIENT_TYPE"})}const{restarted:u,busy:P}=await Re(i,s,{info:D=>a.info("main",D),warn:D=>a.warn("main",D)},{agentName:o,enabled:n,relayHosts:E});return{...r(),restarted:u,busy:P}},setAgentRelayCredential:async(o,n)=>{const i=g(),v=s.getAgentsStatus(),{restarted:R,busy:E}=await xe(i,s,u=>v.find(P=>P.name===u),{info:u=>a.info("main",u),warn:u=>a.warn("main",u)},{localName:o,agentId:n.agentId,virtualKey:n.virtualKey,anthropicBaseUrl:n.anthropicBaseUrl,openaiBaseUrl:n.openaiBaseUrl,model:n.model},(u,P)=>s.setAgentProvider(u,P));return{...r(),restarted:R,busy:E}},disableAll:async()=>{const o=g();await we(o,s,{info:i=>a.info("main",i),warn:i=>a.warn("main",i)});const n=await s.clearDirectProviderConfigs();return n.length>0&&a.info("main",`relay disable-all cleared native provider config for: ${n.join(", ")}`),r()},setRoute:async(o,n)=>{const i=n,v=g();return v.setRoute({routeKey:o,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 v.persistConfig(),r()},deleteRoute:async o=>{const n=g();n.deleteRoute(o),await n.persistConfig()},setDefaultRoute:async o=>{const n=g();return n.setDefaultRouteKey(o),await n.persistConfig(),r()},setInterceptHosts:async o=>{const n=g();return n.setInterceptHosts(o),await n.persistConfig(),r()}};A=m,y.setProxyHandler(m)}const N={listAgents:()=>s.getAgentsStatus(),enableManaged:(e,r,m)=>{if(!A)throw new j(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return A.setAgentRelayCredential(e,{agentId:r,virtualKey:m.virtualKey,anthropicBaseUrl:m.anthropicBaseUrl,openaiBaseUrl:m.openaiBaseUrl,model:m.model})},disableManaged:e=>{if(!A)throw new j(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return A.setAgentRelay(e,!1)},enableHermesProfile:(e,r)=>Se({agentId:e,baseUrl:r.openaiBaseUrl??"",virtualKey:r.virtualKey,model:r.model??""}),disableHermesProfile:e=>Ae(e)};y.setRelayCredentialHandler({listHermesProfiles:()=>ve(),enable:async(e,r)=>{const{target:m,result:o}=await Pe(N,e,r);return{target:m,...o}},disable:async e=>{const{target:r,result:m}=await Ee(N,e);return{target:r,...m}}});{const e=await B({label:"admin",port:_,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:r=>y.start(r)});e&&await X(e)}const J=b.join(c.dataDir,"admin-token"),Y=b.join(c.dataDir,"admin-port");fe(J,V),M(Y,String(_),"utf-8"),await F(c.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),a.info("main","grix-connector ready")}ke().catch(async t=>{a.error("main",`Fatal: ${t}`),C(t,"startup"),await O(),k(c.daemonLockFile).catch(()=>{}),I(),process.exit(1)});const Te=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function W(t){return t instanceof Error&&"code"in t?Te.has(t.code):!1}
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}