grix-connector 3.5.1 → 3.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/adapter/claude/claude-bridge-server.js +1 -1
  2. package/dist/adapter/claude/claude-tools.js +1 -1
  3. package/dist/adapter/claude/claude-worker-client.js +1 -1
  4. package/dist/adapter/claude/mcp-http-launcher.js +2 -2
  5. package/dist/adapter/claude/result-timeout.js +1 -1
  6. package/dist/adapter/codex/codex-bridge.js +8 -8
  7. package/dist/bridge/bridge.js +4 -4
  8. package/dist/core/access/allowlist-store.js +1 -1
  9. package/dist/core/admin/admin-server.js +1 -1
  10. package/dist/core/file-ops/list-files.js +1 -1
  11. package/dist/core/installer/registry.js +1 -1
  12. package/dist/core/proxy/compat/anthropic/forwarder.js +1 -1
  13. package/dist/core/proxy/compat/codex-anthropic/forwarder.js +1 -1
  14. package/dist/core/proxy/compat/codex-gemini/forwarder.js +1 -1
  15. package/dist/core/proxy/compat/codex-openai/forwarder.js +1 -1
  16. package/dist/core/proxy/compat/gemini/forwarder.js +1 -1
  17. package/dist/core/proxy/gateway-route.js +1 -0
  18. package/dist/core/proxy/index.js +1 -1
  19. package/dist/core/proxy/proxy-manager.js +5 -5
  20. package/dist/core/proxy/relay-credential.js +1 -0
  21. package/dist/core/proxy/relay-hosts.js +1 -1
  22. package/dist/core/proxy/relay-orchestration.js +1 -1
  23. package/dist/core/proxy/routing/registry.js +1 -1
  24. package/dist/core/proxy/routing/resolver.js +1 -1
  25. package/dist/core/proxy/server/mitm-proxy.js +5 -3
  26. package/dist/grix.js +6 -6
  27. package/dist/log.js +2 -2
  28. package/dist/mcp/stream-http/config.js +1 -1
  29. package/dist/mcp/stream-http/connection-binding.js +1 -1
  30. package/dist/mcp/stream-http/tool-executor.js +1 -1
  31. package/dist/mcp/stream-http/tool-registry.js +1 -1
  32. package/dist/mcp/stream-http/tool-schemas.js +1 -1
  33. package/dist/service/platform-adapter.js +8 -8
  34. package/package.json +1 -1
@@ -1 +1 @@
1
- import{readJSONFile as o,writeJSONFileAtomic as e}from"../util/json-file.js";function n(t){const r=o(t);return r&&typeof r=="object"&&"owners"in r&&Array.isArray(r.owners)?r.owners.filter(i=>typeof i=="string"&&i.trim().length>0):[]}async function s(t,r){await e(t,{owners:r})}export{n as readAllowlist,s as writeAllowlist};
1
+ import{readJSONFile as o,writeJSONFileAtomic as i}from"../util/json-file.js";function n(t){const r=o(t);return r&&typeof r=="object"&&"owners"in r&&Array.isArray(r.owners)?r.owners.filter(e=>typeof e=="string"&&e.trim().length>0):[]}async function s(t,r){await i(t,{owners:r})}export{n as readAllowlist,s as writeAllowlist};
@@ -1 +1 @@
1
- import{createServer as u}from"node:http";import{log as c}from"../log/logger.js";class f{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;proxyHandler=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}async start(e){return new Promise((n,t)=>{this.server=u((r,s)=>this.handleRequest(r,s)),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(s=>this.handleAdd(n,s)).catch(s=>this.error(n,s));else if(t==="DELETE"&&r.startsWith("/api/agents/")){const s=decodeURIComponent(r.slice(12));this.handleRemove(n,s)}else if(t==="POST"&&r.match(/^\/api\/agents\/[^/]+\/restart$/)){const s=decodeURIComponent(r.slice(12,r.lastIndexOf("/restart")));this.handleRestart(n,s)}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(s=>this.handleInstall(n,s)).catch(s=>this.error(n,s));else if(t==="GET"&&r.startsWith("/api/install/")){const s=decodeURIComponent(r.slice(13));this.handleInstallProgress(n,s)}else if(r==="/api/proxy"&&t==="GET")this.handleProxyStatus(n);else if(t==="PUT"&&r.match(/^\/api\/proxy\/agents\/[^/]+\/enabled$/)){const s=decodeURIComponent(r.slice(18,-8));this.readBody(e).then(o=>this.handleProxySetAgentRelay(n,s,o)).catch(o=>this.error(n,o))}else if(t==="PUT"&&r==="/api/proxy/enabled")this.readBody(e).then(s=>this.handleProxySetEnabled(n,s)).catch(s=>this.error(n,s));else if(t==="PUT"&&r==="/api/proxy/default-route")this.readBody(e).then(s=>this.handleProxySetDefaultRoute(n,s)).catch(s=>this.error(n,s));else if(t==="PUT"&&r==="/api/proxy/intercept-hosts")this.readBody(e).then(s=>this.handleProxySetInterceptHosts(n,s)).catch(s=>this.error(n,s));else if(t==="PUT"&&r.match(/^\/api\/proxy\/routes\/[^/]+$/)){const s=decodeURIComponent(r.slice(18));this.readBody(e).then(o=>this.handleProxySetRoute(n,s,o)).catch(o=>this.error(n,o))}else if(t==="DELETE"&&r.startsWith("/api/proxy/routes/")){const s=decodeURIComponent(r.slice(18));this.handleProxyDeleteRoute(n,s)}else this.json(n,404,{error:"not_found"})}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 s=await this.proxyHandler.setRoute(n,t);this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}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 s=t;if(typeof s?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}try{const o=await this.proxyHandler.setAgentRelay(r,s.enabled);this.json(e,200,o??{ok:!0})}catch(o){this.error(e,o)}}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 s=await this.proxyHandler.setDefaultRoute(r);this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}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==="RELOAD_UNSAFE"||t.code==="NO_RELAY_ROUTE"?this.json(e,409,{error:t.message,code:t.code}):t.code==="UNKNOWN_AGENT"||t.code==="UNSUPPORTED_OS"?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",s=>{r+=s}),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("?"),s=r>=0?t.slice(0,r):t,o=r>=0?new URLSearchParams(t.slice(r+1)):new URLSearchParams,a={};o.get("conversation")==="true"&&(a.conversation=!0),o.get("fresh")==="true"&&(a.fresh=!0);const l=Number(o.get("timeoutMs"));Number.isFinite(l)&&l>0&&(a.timeoutMs=l);const d=s.match(/^\/api\/probe\/(.+)$/);if(d){const i=decodeURIComponent(d[1]);this.probeHandler.probeOne(i,a).then(h=>{this.json(n,200,h)}).catch(h=>this.error(n,h));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,s,o;switch(t.phase){case"completed":r="done",s="\u5B89\u88C5\u5B8C\u6210";break;case"failed":r="error",o=t.outputTail||"\u5B89\u88C5\u5931\u8D25";break;case"preflight":r="pending",s="\u68C0\u67E5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing_prereq":r="downloading",s=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",s=`\u6B63\u5728\u5B89\u88C5 ${n}...`;break;case"verifying":r="installing",s="\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:s,error:o})}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 s=r.error?.code;s==="UNKNOWN_AGENT"||s==="UNSUPPORTED_OS"?this.json(e,400,r):s==="INSTALL_IN_PROGRESS"?this.json(e,409,r):this.json(e,500,r)}}catch(t){this.error(e,t)}}}export{f as AdminServer};
1
+ import{createServer as u}from"node:http";import{log as c}from"../log/logger.js";class f{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;proxyHandler=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}async start(e){return new Promise((n,t)=>{this.server=u((r,s)=>this.handleRequest(r,s)),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(s=>this.handleAdd(n,s)).catch(s=>this.error(n,s));else if(t==="DELETE"&&r.startsWith("/api/agents/")){const s=decodeURIComponent(r.slice(12));this.handleRemove(n,s)}else if(t==="POST"&&r.match(/^\/api\/agents\/[^/]+\/restart$/)){const s=decodeURIComponent(r.slice(12,r.lastIndexOf("/restart")));this.handleRestart(n,s)}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(s=>this.handleInstall(n,s)).catch(s=>this.error(n,s));else if(t==="GET"&&r.startsWith("/api/install/")){const s=decodeURIComponent(r.slice(13));this.handleInstallProgress(n,s)}else if(r==="/api/proxy"&&t==="GET")this.handleProxyStatus(n);else if(t==="PUT"&&r.match(/^\/api\/proxy\/agents\/[^/]+\/enabled$/)){const s=decodeURIComponent(r.slice(18,-8));this.readBody(e).then(o=>this.handleProxySetAgentRelay(n,s,o)).catch(o=>this.error(n,o))}else if(t==="PUT"&&r.match(/^\/api\/proxy\/agents\/[^/]+\/relay-credential$/)){const s=decodeURIComponent(r.slice(18,-17));this.readBody(e).then(o=>this.handleProxySetAgentRelayCredential(n,s,o)).catch(o=>this.error(n,o))}else if(t==="PUT"&&r==="/api/proxy/enabled")this.readBody(e).then(s=>this.handleProxySetEnabled(n,s)).catch(s=>this.error(n,s));else if(t==="PUT"&&r==="/api/proxy/default-route")this.readBody(e).then(s=>this.handleProxySetDefaultRoute(n,s)).catch(s=>this.error(n,s));else if(t==="PUT"&&r==="/api/proxy/intercept-hosts")this.readBody(e).then(s=>this.handleProxySetInterceptHosts(n,s)).catch(s=>this.error(n,s));else if(t==="PUT"&&r.match(/^\/api\/proxy\/routes\/[^/]+$/)){const s=decodeURIComponent(r.slice(18));this.readBody(e).then(o=>this.handleProxySetRoute(n,s,o)).catch(o=>this.error(n,o))}else if(t==="DELETE"&&r.startsWith("/api/proxy/routes/")){const s=decodeURIComponent(r.slice(18));this.handleProxyDeleteRoute(n,s)}else this.json(n,404,{error:"not_found"})}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 s=await this.proxyHandler.setRoute(n,t);this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}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 s=t;if(typeof s?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}try{const o=await this.proxyHandler.setAgentRelay(r,s.enabled);this.json(e,200,o??{ok:!0})}catch(o){this.error(e,o)}}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 s=t;if(typeof s?.agent_id!="string"||!s.agent_id.trim()){this.json(e,400,{error:"agent_id is required",code:"MISSING_AGENT_ID"});return}if(typeof s?.virtual_key!="string"||!s.virtual_key.trim()){this.json(e,400,{error:"virtual_key is required",code:"MISSING_VIRTUAL_KEY"});return}if(s.anthropic_base_url!==void 0&&typeof s.anthropic_base_url!="string"){this.json(e,400,{error:"anthropic_base_url must be a string",code:"INVALID_ARGUMENT"});return}if(s.openai_base_url!==void 0&&typeof s.openai_base_url!="string"){this.json(e,400,{error:"openai_base_url must be a string",code:"INVALID_ARGUMENT"});return}try{const o=await this.proxyHandler.setAgentRelayCredential(r,{agentId:s.agent_id,virtualKey:s.virtual_key,anthropicBaseUrl:typeof s.anthropic_base_url=="string"?s.anthropic_base_url:void 0,openaiBaseUrl:typeof s.openai_base_url=="string"?s.openai_base_url:void 0});this.json(e,200,o??{ok:!0})}catch(o){this.error(e,o)}}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 s=await this.proxyHandler.setDefaultRoute(r);this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}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==="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==="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",s=>{r+=s}),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("?"),s=r>=0?t.slice(0,r):t,o=r>=0?new URLSearchParams(t.slice(r+1)):new URLSearchParams,a={};o.get("conversation")==="true"&&(a.conversation=!0),o.get("fresh")==="true"&&(a.fresh=!0);const l=Number(o.get("timeoutMs"));Number.isFinite(l)&&l>0&&(a.timeoutMs=l);const d=s.match(/^\/api\/probe\/(.+)$/);if(d){const i=decodeURIComponent(d[1]);this.probeHandler.probeOne(i,a).then(h=>{this.json(n,200,h)}).catch(h=>this.error(n,h));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,s,o;switch(t.phase){case"completed":r="done",s="\u5B89\u88C5\u5B8C\u6210";break;case"failed":r="error",o=t.outputTail||"\u5B89\u88C5\u5931\u8D25";break;case"preflight":r="pending",s="\u68C0\u67E5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing_prereq":r="downloading",s=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",s=`\u6B63\u5728\u5B89\u88C5 ${n}...`;break;case"verifying":r="installing",s="\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:s,error:o})}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 s=r.error?.code;s==="UNKNOWN_AGENT"||s==="UNSUPPORTED_OS"?this.json(e,400,r):s==="INSTALL_IN_PROGRESS"?this.json(e,409,r):this.json(e,500,r)}}catch(t){this.error(e,t)}}}export{f as AdminServer};
@@ -1 +1 @@
1
- import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const t of c){if(!p&&t.name.startsWith("."))continue;const i=l(a,t.name),e={id:i,name:t.name,is_directory:t.isDirectory()};try{if(t.isDirectory()){const o=await m(i);e.modified_at=o.mtime.toISOString()}else{const o=await m(i);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(t.name)}}catch{}s.push(e)}return s.sort((t,i)=>t.is_directory!==i.is_directory?t.is_directory?-1:1:t.name.localeCompare(i.name)),s}export{f as listFiles,n as resolveMimeType};
1
+ import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const i of c){if(!p&&i.name.startsWith("."))continue;const t=l(a,i.name),e={id:t,name:i.name,is_directory:i.isDirectory()};try{if(i.isDirectory()){const o=await m(t);e.modified_at=o.mtime.toISOString()}else{const o=await m(t);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(i.name)}}catch{}s.push(e)}return s.sort((i,t)=>i.is_directory!==t.is_directory?i.is_directory?-1:1:i.name.localeCompare(t.name)),s}export{f as listFiles,n as resolveMimeType};
@@ -1 +1 @@
1
- function e(i,s){return{command:`npm install -g ${i}`,mode:"npm",npmPackage:i,timeoutMs:s?.timeoutMs??12e4,prerequisites:["node","npm"],minNodeVersion:s?.minNodeVersion}}function n(i,s){return{command:i,mode:"shell",timeoutMs:s?.timeoutMs??12e4,prerequisites:s?.prerequisites}}function c(i,s,o){return{command:i,mode:"exec",execArgs:s,timeoutMs:o?.timeoutMs??6e4,prerequisites:o?.prerequisites,skipVerification:o?.skipVerification}}const l={claude:{cliBinary:"claude",macos:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),linux:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),windows:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"})},codex:{cliBinary:"codex",macos:{...n("curl -fsSL https://chatgpt.com/codex/install.sh | sh",{prerequisites:["curl"]}),fallback:e("@openai/codex")},linux:{...n("curl -fsSL https://chatgpt.com/codex/install.sh | sh",{prerequisites:["curl"]}),fallback:e("@openai/codex")},windows:{...n('powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"'),fallback:e("@openai/codex")}},gemini:{cliBinary:"gemini",macos:{...e("@google/gemini-cli",{minNodeVersion:"18.0"}),fallback:n("npx @google/gemini-cli --yes",{timeoutMs:12e4})},linux:e("@google/gemini-cli",{minNodeVersion:"18.0"}),windows:e("@google/gemini-cli",{minNodeVersion:"18.0"})},qwen:{cliBinary:"qwen",macos:{...n("curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash",{prerequisites:["curl"]}),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},linux:{...n("curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash",{prerequisites:["curl"]}),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},windows:{...n(`powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat' -OutFile (Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path $env:TEMP 'install-qwen.bat')"`),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})}},cursor:{cliBinary:"agent",macos:n("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),linux:n("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),windows:n(`powershell -ExecutionPolicy ByPass -c "irm 'https://cursor.com/install?win32=true' | iex"`)},copilot:{cliBinary:"copilot",macos:e("@github/copilot",{minNodeVersion:"22.0"}),linux:e("@github/copilot",{minNodeVersion:"22.0"}),windows:e("@github/copilot",{minNodeVersion:"22.0"})},kiro:{cliBinary:"kiro-cli",macos:n("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),windows:null},openclaw:{cliBinary:"openclaw",macos:e("openclaw@latest",{minNodeVersion:"22.0"}),linux:e("openclaw@latest",{minNodeVersion:"22.0"}),windows:e("openclaw@latest",{minNodeVersion:"22.0"})},reasonix:{cliBinary:"reasonix",macos:e("reasonix"),linux:e("reasonix"),windows:e("reasonix")},pi:{cliBinary:"pi",macos:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),linux:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),windows:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"})},agy:{cliBinary:"agy",macos:n("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),windows:n('powershell -ExecutionPolicy ByPass -c "irm https://antigravity.google/cli/install.ps1 | iex"')},hermes:{cliBinary:"hermes",macos:null,linux:null,windows:null},codewhale:{cliBinary:"codewhale",macos:e("codewhale"),linux:e("codewhale"),windows:e("codewhale")},opencode:{cliBinary:"opencode",macos:{...n("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:e("opencode-ai")},linux:{...n("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:e("opencode-ai")},windows:e("opencode-ai")},openhuman:{cliBinary:"openhuman-core",macos:n("curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash",{prerequisites:["curl"]}),windows:null}};function u(){switch(process.platform){case"darwin":return"macos";case"linux":return"linux";case"win32":return"windows";default:return"linux"}}function r(i){return i.mode==="exec"&&i.execArgs&&i.execArgs.length>0?[i.command,...i.execArgs].join(" "):i.command}function m(i,s){const o=l[i];return o?o[s]:null}function p(i){return l[i]?.cliBinary??null}function a(i,s){const o=l[i];if(!o)return null;const t=o[s];return{agentType:i,cliBinary:o.cliBinary,supported:t!==null,installCommand:t?r(t):null,prerequisites:t?.prerequisites}}function d(i){return Object.keys(l).sort().map(s=>a(s,i)).filter(s=>s!==null)}function h(i){return i in l}export{u as detectPlatformOS,r as formatInstallCommand,a as getAgentInstallInfo,d as getAllAgentInstallInfo,p as getCliBinary,m as getInstallCommand,h as isKnownAgent};
1
+ function e(i,s){return{command:`npm install -g ${i}`,mode:"npm",npmPackage:i,timeoutMs:s?.timeoutMs??12e4,prerequisites:["node","npm"],minNodeVersion:s?.minNodeVersion}}function n(i,s){return{command:i,mode:"shell",timeoutMs:s?.timeoutMs??12e4,prerequisites:s?.prerequisites}}function c(i,s,o){return{command:i,mode:"exec",execArgs:s,timeoutMs:o?.timeoutMs??6e4,prerequisites:o?.prerequisites,skipVerification:o?.skipVerification}}const l={claude:{cliBinary:"claude",macos:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),linux:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"}),windows:e("@anthropic-ai/claude-code",{minNodeVersion:"18.0"})},codex:{cliBinary:"codex",macos:e("@openai/codex"),linux:e("@openai/codex"),windows:e("@openai/codex")},gemini:{cliBinary:"gemini",macos:{...e("@google/gemini-cli",{minNodeVersion:"18.0"}),fallback:n("npx @google/gemini-cli --yes",{timeoutMs:12e4})},linux:e("@google/gemini-cli",{minNodeVersion:"18.0"}),windows:e("@google/gemini-cli",{minNodeVersion:"18.0"})},qwen:{cliBinary:"qwen",macos:{...n("curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash",{prerequisites:["curl"]}),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},linux:{...n("curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash",{prerequisites:["curl"]}),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})},windows:{...n(`powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat' -OutFile (Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path $env:TEMP 'install-qwen.bat')"`),fallback:e("@qwen-code/qwen-code@latest",{minNodeVersion:"22.0"})}},cursor:{cliBinary:"agent",macos:n("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),linux:n("curl https://cursor.com/install -fsS | bash",{prerequisites:["curl"]}),windows:n(`powershell -ExecutionPolicy ByPass -c "irm 'https://cursor.com/install?win32=true' | iex"`)},copilot:{cliBinary:"copilot",macos:e("@github/copilot",{minNodeVersion:"22.0"}),linux:e("@github/copilot",{minNodeVersion:"22.0"}),windows:e("@github/copilot",{minNodeVersion:"22.0"})},kiro:{cliBinary:"kiro-cli",macos:n("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://cli.kiro.dev/install | bash",{prerequisites:["curl"]}),windows:null},openclaw:{cliBinary:"openclaw",macos:e("openclaw@latest",{minNodeVersion:"22.0"}),linux:e("openclaw@latest",{minNodeVersion:"22.0"}),windows:e("openclaw@latest",{minNodeVersion:"22.0"})},reasonix:{cliBinary:"reasonix",macos:e("reasonix"),linux:e("reasonix"),windows:e("reasonix")},pi:{cliBinary:"pi",macos:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),linux:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"}),windows:e("@earendil-works/pi-coding-agent",{minNodeVersion:"22.19"})},agy:{cliBinary:"agy",macos:n("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://antigravity.google/cli/install.sh | bash",{prerequisites:["curl"]}),windows:n('powershell -ExecutionPolicy ByPass -c "irm https://antigravity.google/cli/install.ps1 | iex"')},hermes:{cliBinary:"hermes",macos:null,linux:null,windows:null},codewhale:{cliBinary:"codewhale",macos:e("codewhale"),linux:e("codewhale"),windows:e("codewhale")},opencode:{cliBinary:"opencode",macos:{...n("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:e("opencode-ai")},linux:{...n("curl -fsSL https://opencode.ai/install | bash",{prerequisites:["curl"]}),fallback:e("opencode-ai")},windows:e("opencode-ai")},openhuman:{cliBinary:"openhuman-core",macos:n("curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash",{prerequisites:["curl"]}),linux:n("curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash",{prerequisites:["curl"]}),windows:null}};function u(){switch(process.platform){case"darwin":return"macos";case"linux":return"linux";case"win32":return"windows";default:return"linux"}}function r(i){return i.mode==="exec"&&i.execArgs&&i.execArgs.length>0?[i.command,...i.execArgs].join(" "):i.command}function m(i,s){const o=l[i];return o?o[s]:null}function d(i){return l[i]?.cliBinary??null}function a(i,s){const o=l[i];if(!o)return null;const t=o[s];return{agentType:i,cliBinary:o.cliBinary,supported:t!==null,installCommand:t?r(t):null,prerequisites:t?.prerequisites}}function p(i){return Object.keys(l).sort().map(s=>a(s,i)).filter(s=>s!==null)}function w(i){return i in l}export{u as detectPlatformOS,r as formatInstallCommand,a as getAgentInstallInfo,p as getAllAgentInstallInfo,d as getCliBinary,m as getInstallCommand,w as isKnownAgent};
@@ -1 +1 @@
1
- import J from"node:http";import T from"node:https";import{captureBuffer as C}from"../../capture/index.js";import{buildRequestOptions as b}from"../../utils/index.js";import{resolveMappedModel as A}from"../model-override.js";import{parseSseFrames as H,parseSseData as O}from"../codex-openai/index.js";import{translateAnthropicMessagesToChatCompletions as W,translateChatCompletionToAnthropicMessage as E,AnthropicStreamEventTranslator as D,encodeAnthropicSseEvent as P,resolveAnthropicChatCompatPath as j,stripAnthropicProxyHeaders as z}from"./translator.js";function M(e){return e instanceof Error?e:new Error(String(e))}async function L(e){const f=[];for await(const l of e.request){const g=Buffer.isBuffer(l)?l:Buffer.from(l);C(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},g),f.push(g)}return Buffer.concat(f)}async function Y(e,f){const l=await L(e);return await new Promise((g,N)=>{const k=e.target.protocol==="https:"?T:J;let p=!1,u=0,s=null;const d=r=>{if(p)return;p=!0;const a=M(r);s&&!s.destroyed&&s.destroy(),e.captureWriter.append({type:"http_request_error",requestId:e.requestId,connectionId:e.connectionId,error:a.message}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-f,responseBytes:u,error:a.message}),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||e.response.end(e.response.headersSent?void 0:a.message),N(a)},m=()=>{p||(p=!0,e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-f,responseBytes:u}),g(u))};let h,S;try{const r=l.toString("utf8"),a=r.trim().length>0?JSON.parse(r):{},q=e.modelOverride||e.modelMapOverride?{...a,model:A(typeof a.model=="string"?a.model:"",e.modelMapOverride,e.modelOverride)}:a,_=W(q);h=Buffer.from(JSON.stringify(_),"utf8"),S={...z(e.request.headers),"content-type":"application/json","content-length":String(h.length)},delete S["content-encoding"]}catch(r){d(r);return}s=k.request(b({target:e.target,method:e.request.method??"POST",path:j(e.path),headers:S,headerOverrides:e.headerOverrides}),r=>{e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:r.statusCode??0,statusMessage:r.statusMessage??"",headers:r.headers});const a=r.statusCode??502,q=String(r.headers["content-type"]??""),_=a>=200&&a<300&&q.toLowerCase().includes("text/event-stream"),v=a>=200&&a<300&&q.toLowerCase().includes("application/json");if(!_&&!v){e.response.writeHead(a,r.headers),r.on("data",n=>{u+=n.length,C(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},n),e.response.write(n)}),r.on("end",()=>{e.response.end(),m()}),r.on("error",d);return}const x=h.length>0&&JSON.parse(h.toString("utf8")).stream===!0;if(v&&!x){const n=[];r.on("data",t=>{n.push(t),u+=t.length}),r.on("end",()=>{try{const t=Buffer.concat(n).toString("utf8"),o=t.trim().length>0?JSON.parse(t):{},c=E(o),i=Buffer.from(JSON.stringify(c),"utf8");u+=i.length,e.response.writeHead(200,{"content-type":"application/json","content-length":String(i.length)}),e.response.end(i),m()}catch(t){d(t)}}),r.on("error",d);return}e.response.writeHead(200,{"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"});const w=new D;let y="";const I=n=>{for(const t of n){const o=P(t.type,t);C(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},o),u+=o.length,e.response.write(o)}};if(v){const n=[];r.on("data",t=>{n.push(t),u+=t.length}),r.on("end",()=>{try{const t=Buffer.concat(n).toString("utf8"),o=t.trim().length>0?JSON.parse(t):{},c=E(o);I([{type:"message_start",message:c},...c.content.map((i,B)=>[{type:"content_block_start",index:B,content_block:i},{type:"content_block_stop",index:B}]).flat(),{type:"message_delta",delta:{stop_reason:c.stop_reason,stop_sequence:null},usage:c.usage},{type:"message_stop"}]),e.response.end(),m()}catch(t){d(t)}}),r.on("error",d);return}r.on("data",n=>{try{y+=n.toString("utf8");const t=H(y);y=t.remaining;for(const o of t.frames){const c=O(o);if(!c||c==="[DONE]")continue;const i=w.consumeChatCompletionChunk(JSON.parse(c));I(i)}}catch(t){r.destroy(M(t)),d(t)}}),r.on("end",()=>{try{if(y.trim().length>0){const t=O(y);if(t&&t!=="[DONE]"){const o=w.consumeChatCompletionChunk(JSON.parse(t));I(o)}}const n=w.finalize();I(n),e.response.end(),m()}catch(n){d(n)}}),r.on("error",d)}),s.on("error",d),e.request.on("aborted",()=>{s.destroy()}),e.response.on("close",()=>{s.destroyed||s.destroy()}),s.write(h),s.end()})}export{Y as forwardAnthropicChatCompatRequest};
1
+ import J from"node:http";import T from"node:https";import{captureBuffer as C}from"../../capture/index.js";import{buildRequestOptions as b}from"../../utils/index.js";import{resolveMappedModel as A}from"../model-override.js";import{parseSseFrames as H,parseSseData as O}from"../codex-openai/index.js";import{translateAnthropicMessagesToChatCompletions as W,translateChatCompletionToAnthropicMessage as E,AnthropicStreamEventTranslator as D,encodeAnthropicSseEvent as P,resolveAnthropicChatCompatPath as j,stripAnthropicProxyHeaders as z}from"./translator.js";function M(e){return e instanceof Error?e:new Error(String(e))}async function L(e){const f=[];for await(const l of e.request){const g=Buffer.isBuffer(l)?l:Buffer.from(l);C(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},g),f.push(g)}return Buffer.concat(f)}async function Y(e,f){const l=await L(e);return await new Promise((g,N)=>{const k=e.target.protocol==="https:"?T:J;let p=!1,u=0,s=null;const c=r=>{if(p)return;p=!0;const a=M(r);s&&!s.destroyed&&s.destroy(),e.captureWriter.append({type:"http_request_error",requestId:e.requestId,connectionId:e.connectionId,error:a.message}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-f,responseBytes:u,error:a.message}),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||e.response.end(e.response.headersSent?void 0:a.message),N(a)},m=()=>{p||(p=!0,e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-f,responseBytes:u}),g(u))};let h,S;try{const r=l.toString("utf8"),a=r.trim().length>0?JSON.parse(r):{},q=e.modelOverride||e.modelMapOverride?{...a,model:A(typeof a.model=="string"?a.model:"",e.modelMapOverride,e.modelOverride)}:a,_=W(q);h=Buffer.from(JSON.stringify(_),"utf8"),S={...z(e.request.headers),"content-type":"application/json","content-length":String(h.length),"accept-encoding":"identity"},delete S["content-encoding"]}catch(r){c(r);return}s=k.request(b({target:e.target,method:e.request.method??"POST",path:j(e.path),headers:S,headerOverrides:e.headerOverrides}),r=>{e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:r.statusCode??0,statusMessage:r.statusMessage??"",headers:r.headers});const a=r.statusCode??502,q=String(r.headers["content-type"]??""),_=a>=200&&a<300&&q.toLowerCase().includes("text/event-stream"),v=a>=200&&a<300&&q.toLowerCase().includes("application/json");if(!_&&!v){e.response.writeHead(a,r.headers),r.on("data",n=>{u+=n.length,C(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},n),e.response.write(n)}),r.on("end",()=>{e.response.end(),m()}),r.on("error",c);return}const x=h.length>0&&JSON.parse(h.toString("utf8")).stream===!0;if(v&&!x){const n=[];r.on("data",t=>{n.push(t),u+=t.length}),r.on("end",()=>{try{const t=Buffer.concat(n).toString("utf8"),o=t.trim().length>0?JSON.parse(t):{},d=E(o),i=Buffer.from(JSON.stringify(d),"utf8");u+=i.length,e.response.writeHead(200,{"content-type":"application/json","content-length":String(i.length)}),e.response.end(i),m()}catch(t){c(t)}}),r.on("error",c);return}e.response.writeHead(200,{"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"});const w=new D;let y="";const I=n=>{for(const t of n){const o=P(t.type,t);C(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},o),u+=o.length,e.response.write(o)}};if(v){const n=[];r.on("data",t=>{n.push(t),u+=t.length}),r.on("end",()=>{try{const t=Buffer.concat(n).toString("utf8"),o=t.trim().length>0?JSON.parse(t):{},d=E(o);I([{type:"message_start",message:d},...d.content.map((i,B)=>[{type:"content_block_start",index:B,content_block:i},{type:"content_block_stop",index:B}]).flat(),{type:"message_delta",delta:{stop_reason:d.stop_reason,stop_sequence:null},usage:d.usage},{type:"message_stop"}]),e.response.end(),m()}catch(t){c(t)}}),r.on("error",c);return}r.on("data",n=>{try{y+=n.toString("utf8");const t=H(y);y=t.remaining;for(const o of t.frames){const d=O(o);if(!d||d==="[DONE]")continue;const i=w.consumeChatCompletionChunk(JSON.parse(d));I(i)}}catch(t){r.destroy(M(t)),c(t)}}),r.on("end",()=>{try{if(y.trim().length>0){const t=O(y);if(t&&t!=="[DONE]"){const o=w.consumeChatCompletionChunk(JSON.parse(t));I(o)}}const n=w.finalize();I(n),e.response.end(),m()}catch(n){c(n)}}),r.on("error",c)}),s.on("error",c),e.request.on("aborted",()=>{s.destroy()}),e.response.on("close",()=>{s.destroyed||s.destroy()}),s.write(h),s.end()})}export{Y as forwardAnthropicChatCompatRequest};
@@ -1 +1 @@
1
- import N from"node:http";import J from"node:https";import{captureBuffer as T}from"../../capture/index.js";import{buildRequestOptions as W}from"../../utils/index.js";import{encodeResponsesSseEvent as g,parseSseData as A,parseSseFrames as D}from"../codex-openai/index.js";import{applyCodexModelOverrideToRequestBody as b}from"../model-override.js";import{AnthropicMessagesEventTranslator as z,resolveCodexAnthropicCompatPath as L,stripCodexAnthropicCompatHeaders as P,translateAnthropicMessageToResponsesEvents as F,translateResponsesRequestToAnthropicMessages as j}from"./translator.js";function O(e){return e instanceof Error?e:new Error(String(e))}async function K(e){const n=[];for await(const a of e.request){const c=Buffer.isBuffer(a)?a:Buffer.from(a);T(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},c),n.push(c)}return Buffer.concat(n)}function m(e,n){return T(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},n),e.response.write(n),n.length}function x(e,n,a){e.events.emitResponseHeaders({requestId:e.requestId,status:n,headers:a})}function X(e){const n=e.authorization;return(Array.isArray(n)?n[0]:n)?.match(/^Bearer\s+(.+)$/i)?.[1]?.trim()??""}function $(e,n){const a={...P(e),"content-length":String(n)};if(delete a["content-encoding"],a["anthropic-version"]||(a["anthropic-version"]="2023-06-01"),!a["x-api-key"]&&!a["X-Api-Key"]){const c=X(e);c&&(a["x-api-key"]=c)}return a}function G(e){return e.target.host.toLowerCase()==="api.deepseek.com"}async function te(e,n){const a=await K(e);return await new Promise((c,k)=>{const H=e.target.protocol==="https:"?J:N;let q=!1,d=0,i=null;const u=t=>{if(q)return;q=!0;const s=O(t);i&&!i.destroyed&&i.destroy(),e.captureWriter.append({type:"http_request_error",requestId:e.requestId,connectionId:e.connectionId,error:s.message}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-n,responseBytes:d,error:s.message}),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||e.response.end(e.response.headersSent?void 0:s.message),k(s)},p=()=>{q||(q=!0,e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-n,responseBytes:d}),c(d))};let v,B,y={};try{const t=a.toString("utf8");y=b(t.trim().length>0?JSON.parse(t):{},e.request.headers);const s=j(y,{omitToolChoice:G(e)});v=Buffer.from(JSON.stringify(s),"utf8"),B=$(e.request.headers,v.length)}catch(t){u(t);return}i=H.request(W({target:e.target,method:e.request.method??"POST",path:L(e.path),headers:B,headerOverrides:e.headerOverrides}),t=>{e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:t.statusCode??0,statusMessage:t.statusMessage??"",headers:t.headers});const s=t.statusCode??502,C=String(t.headers["content-type"]??""),_=s>=200&&s<300&&C.toLowerCase().includes("text/event-stream"),S=s>=200&&s<300&&C.toLowerCase().includes("application/json");if(!_&&!S){x(e,s,t.headers),e.response.writeHead(s,t.headers),t.on("data",o=>{d+=m(e,o)}),t.on("end",()=>{e.response.end(),p()}),t.on("error",u);return}const E={"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"};if(x(e,200,E),e.response.writeHead(200,E),S){const o=[];t.on("data",r=>{o.push(r)}),t.on("end",()=>{try{const r=Buffer.concat(o).toString("utf8"),l=r.trim().length>0?JSON.parse(r):{},f=F(l,y);for(const w of f)d+=m(e,g(w));e.response.end(),p()}catch(r){u(r)}}),t.on("error",u);return}const I=new z(y);let h="";t.on("data",o=>{try{h+=o.toString("utf8");const r=D(h);h=r.remaining;for(const l of r.frames){const f=A(l);if(!f||f==="[DONE]")continue;const w=I.consumeAnthropicEvent(JSON.parse(f));for(const M of w)d+=m(e,g(M))}}catch(r){t.destroy(O(r)),u(r)}}),t.on("end",()=>{try{if(h.trim().length>0){const r=A(h);if(r&&r!=="[DONE]"){const l=I.consumeAnthropicEvent(JSON.parse(r));for(const f of l)d+=m(e,g(f))}}const o=I.finalize();for(const r of o)d+=m(e,g(r));e.response.end(),p()}catch(o){u(o)}}),t.on("error",u)}),i.on("error",u),i.write(v),i.end()})}export{te as forwardCodexAnthropicCompatRequest};
1
+ import N from"node:http";import J from"node:https";import{captureBuffer as T}from"../../capture/index.js";import{buildRequestOptions as W}from"../../utils/index.js";import{encodeResponsesSseEvent as g,parseSseData as A,parseSseFrames as D}from"../codex-openai/index.js";import{applyCodexModelOverrideToRequestBody as b}from"../model-override.js";import{AnthropicMessagesEventTranslator as z,resolveCodexAnthropicCompatPath as L,stripCodexAnthropicCompatHeaders as P,translateAnthropicMessageToResponsesEvents as F,translateResponsesRequestToAnthropicMessages as j}from"./translator.js";function O(e){return e instanceof Error?e:new Error(String(e))}async function K(e){const n=[];for await(const a of e.request){const c=Buffer.isBuffer(a)?a:Buffer.from(a);T(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},c),n.push(c)}return Buffer.concat(n)}function m(e,n){return T(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},n),e.response.write(n),n.length}function x(e,n,a){e.events.emitResponseHeaders({requestId:e.requestId,status:n,headers:a})}function X(e){const n=e.authorization;return(Array.isArray(n)?n[0]:n)?.match(/^Bearer\s+(.+)$/i)?.[1]?.trim()??""}function $(e,n){const a={...P(e),"content-length":String(n),"accept-encoding":"identity"};if(delete a["content-encoding"],a["anthropic-version"]||(a["anthropic-version"]="2023-06-01"),!a["x-api-key"]&&!a["X-Api-Key"]){const c=X(e);c&&(a["x-api-key"]=c)}return a}function G(e){return e.target.host.toLowerCase()==="api.deepseek.com"}async function te(e,n){const a=await K(e);return await new Promise((c,k)=>{const H=e.target.protocol==="https:"?J:N;let y=!1,d=0,i=null;const u=t=>{if(y)return;y=!0;const s=O(t);i&&!i.destroyed&&i.destroy(),e.captureWriter.append({type:"http_request_error",requestId:e.requestId,connectionId:e.connectionId,error:s.message}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-n,responseBytes:d,error:s.message}),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||e.response.end(e.response.headersSent?void 0:s.message),k(s)},p=()=>{y||(y=!0,e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-n,responseBytes:d}),c(d))};let v,B,q={};try{const t=a.toString("utf8");q=b(t.trim().length>0?JSON.parse(t):{},e.request.headers);const s=j(q,{omitToolChoice:G(e)});v=Buffer.from(JSON.stringify(s),"utf8"),B=$(e.request.headers,v.length)}catch(t){u(t);return}i=H.request(W({target:e.target,method:e.request.method??"POST",path:L(e.path),headers:B,headerOverrides:e.headerOverrides}),t=>{e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:t.statusCode??0,statusMessage:t.statusMessage??"",headers:t.headers});const s=t.statusCode??502,C=String(t.headers["content-type"]??""),_=s>=200&&s<300&&C.toLowerCase().includes("text/event-stream"),S=s>=200&&s<300&&C.toLowerCase().includes("application/json");if(!_&&!S){x(e,s,t.headers),e.response.writeHead(s,t.headers),t.on("data",o=>{d+=m(e,o)}),t.on("end",()=>{e.response.end(),p()}),t.on("error",u);return}const E={"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"};if(x(e,200,E),e.response.writeHead(200,E),S){const o=[];t.on("data",r=>{o.push(r)}),t.on("end",()=>{try{const r=Buffer.concat(o).toString("utf8"),l=r.trim().length>0?JSON.parse(r):{},f=F(l,q);for(const w of f)d+=m(e,g(w));e.response.end(),p()}catch(r){u(r)}}),t.on("error",u);return}const I=new z(q);let h="";t.on("data",o=>{try{h+=o.toString("utf8");const r=D(h);h=r.remaining;for(const l of r.frames){const f=A(l);if(!f||f==="[DONE]")continue;const w=I.consumeAnthropicEvent(JSON.parse(f));for(const M of w)d+=m(e,g(M))}}catch(r){t.destroy(O(r)),u(r)}}),t.on("end",()=>{try{if(h.trim().length>0){const r=A(h);if(r&&r!=="[DONE]"){const l=I.consumeAnthropicEvent(JSON.parse(r));for(const f of l)d+=m(e,g(f))}}const o=I.finalize();for(const r of o)d+=m(e,g(r));e.response.end(),p()}catch(o){u(o)}}),t.on("error",u)}),i.on("error",u),i.write(v),i.end()})}export{te as forwardCodexAnthropicCompatRequest};
@@ -1 +1 @@
1
- import*as D from"node:http";import*as b from"node:https";import{captureBuffer as _}from"../../capture/index.js";import{buildRequestOptions as P}from"../../utils/index.js";import{CodexResponsesEventTranslator as z,encodeResponsesSseEvent as p,parseSseData as G,parseSseFrames as A}from"../codex-openai/index.js";import{applyCodexModelOverrideToRequestBody as L}from"../model-override.js";import{applyGeminiApiKeyHeader as R,resolveCodexGeminiCompatPath as j,stripCodexGeminiCompatHeaders as F,translateGeminiGenerateContentResponseToResponsesEvents as K,translateGeminiStreamChunkToResponsesEvents as H,translateResponsesRequestToGeminiGenerateContent as Q}from"./translator.js";function M(e){return e instanceof Error?e:new Error(String(e))}async function U(e){const a=[];for await(const u of e.request){const q=Buffer.isBuffer(u)?u:Buffer.from(u);_(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},q),a.push(q)}return Buffer.concat(a)}function m(e,a){return _(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},a),e.response.write(a),a.length}function N(e,a,u){e.events.emitResponseHeaders({requestId:e.requestId,status:a,headers:u})}function V(e){e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId})}async function te(e,a){const u=await U(e);return await new Promise((q,k)=>{const J=e.target.protocol==="https:"?b:D;let y=!1,o=0,I=!1,d=null;const c=t=>{if(y)return;y=!0;const r=M(t);d&&!d.destroyed&&d.destroy(),e.captureWriter.append({type:"http_request_error",requestId:e.requestId,connectionId:e.connectionId,error:r.message}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-a,responseBytes:o,error:r.message}),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||e.response.end(e.response.headersSent?void 0:r.message),k(r)},v=()=>{y||(y=!0,V(e),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-a,responseBytes:o}),q(o))};let S,C,O,f="";try{const t=u.toString("utf8"),r=L(t.trim().length>0?JSON.parse(t):{},e.request.headers);f=typeof r.model=="string"?r.model:"";const g=r.stream===!0,w=Q(r);S=Buffer.from(JSON.stringify(w),"utf8"),O=j(e.path,f,g),C={...R(F(e.request.headers)),"content-length":String(S.length)},delete C["content-encoding"]}catch(t){c(t);return}d=J.request(P({target:e.target,method:e.request.method??"POST",path:O,headers:C,headerOverrides:e.headerOverrides}),t=>{e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:t.statusCode??0,statusMessage:t.statusMessage??"",headers:t.headers});const r=t.statusCode??502,g=String(t.headers["content-type"]??""),w=r>=200&&r<300&&g.toLowerCase().includes("text/event-stream"),T=r>=200&&r<300&&g.toLowerCase().includes("application/json");if(!w&&!T){N(e,r,t.headers),e.response.writeHead(r,t.headers),t.on("data",n=>{o+=m(e,n)}),t.on("end",()=>{I=!0,e.response.end(),v()}),t.on("error",c);return}const x={"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"};if(N(e,200,x),e.response.writeHead(200,x),T){const n=[];t.on("data",s=>{n.push(s)}),t.on("end",()=>{try{I=!0;const s=Buffer.concat(n).toString("utf8"),l=s.trim().length>0?JSON.parse(s):{},h=K(l,{model:f});for(const B of h)o+=m(e,p(B));e.response.end(),v()}catch(s){c(s)}}),t.on("error",c);return}const E=new z({model:f});let i="";t.on("data",n=>{try{i+=n.toString("utf8");const s=A(i);i=s.remaining;for(const l of s.frames){const h=G(l);if(!h||h==="[DONE]")continue;const B=H(E,JSON.parse(h),{model:f});for(const W of B)o+=m(e,p(W))}}catch(s){t.destroy(M(s)),c(s)}}),t.on("end",()=>{try{if(I=!0,i.trim().length>0){const n=G(i);if(n&&n!=="[DONE]"){const s=H(E,JSON.parse(n),{model:f});for(const l of s)o+=m(e,p(l))}}for(const n of E.finalize())o+=m(e,p(n));e.response.end(),v()}catch(n){c(n)}}),t.on("error",c)}),d.on("error",c),e.request.on("aborted",()=>{d.destroy()}),e.response.on("close",()=>{!I&&d&&!d.destroyed&&d.destroy()}),d.write(S),d.end()})}export{te as forwardCodexGeminiCompatRequest};
1
+ import*as D from"node:http";import*as b from"node:https";import{captureBuffer as _}from"../../capture/index.js";import{buildRequestOptions as P}from"../../utils/index.js";import{CodexResponsesEventTranslator as z,encodeResponsesSseEvent as p,parseSseData as G,parseSseFrames as A}from"../codex-openai/index.js";import{applyCodexModelOverrideToRequestBody as L}from"../model-override.js";import{applyGeminiApiKeyHeader as R,resolveCodexGeminiCompatPath as j,stripCodexGeminiCompatHeaders as F,translateGeminiGenerateContentResponseToResponsesEvents as K,translateGeminiStreamChunkToResponsesEvents as H,translateResponsesRequestToGeminiGenerateContent as Q}from"./translator.js";function M(e){return e instanceof Error?e:new Error(String(e))}async function U(e){const a=[];for await(const u of e.request){const q=Buffer.isBuffer(u)?u:Buffer.from(u);_(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},q),a.push(q)}return Buffer.concat(a)}function m(e,a){return _(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},a),e.response.write(a),a.length}function N(e,a,u){e.events.emitResponseHeaders({requestId:e.requestId,status:a,headers:u})}function V(e){e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId})}async function te(e,a){const u=await U(e);return await new Promise((q,k)=>{const J=e.target.protocol==="https:"?b:D;let y=!1,o=0,g=!1,d=null;const c=t=>{if(y)return;y=!0;const r=M(t);d&&!d.destroyed&&d.destroy(),e.captureWriter.append({type:"http_request_error",requestId:e.requestId,connectionId:e.connectionId,error:r.message}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-a,responseBytes:o,error:r.message}),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||e.response.end(e.response.headersSent?void 0:r.message),k(r)},v=()=>{y||(y=!0,V(e),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-a,responseBytes:o}),q(o))};let S,C,O,f="";try{const t=u.toString("utf8"),r=L(t.trim().length>0?JSON.parse(t):{},e.request.headers);f=typeof r.model=="string"?r.model:"";const I=r.stream===!0,w=Q(r);S=Buffer.from(JSON.stringify(w),"utf8"),O=j(e.path,f,I),C={...R(F(e.request.headers)),"content-length":String(S.length),"accept-encoding":"identity"},delete C["content-encoding"]}catch(t){c(t);return}d=J.request(P({target:e.target,method:e.request.method??"POST",path:O,headers:C,headerOverrides:e.headerOverrides}),t=>{e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:t.statusCode??0,statusMessage:t.statusMessage??"",headers:t.headers});const r=t.statusCode??502,I=String(t.headers["content-type"]??""),w=r>=200&&r<300&&I.toLowerCase().includes("text/event-stream"),T=r>=200&&r<300&&I.toLowerCase().includes("application/json");if(!w&&!T){N(e,r,t.headers),e.response.writeHead(r,t.headers),t.on("data",n=>{o+=m(e,n)}),t.on("end",()=>{g=!0,e.response.end(),v()}),t.on("error",c);return}const x={"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"};if(N(e,200,x),e.response.writeHead(200,x),T){const n=[];t.on("data",s=>{n.push(s)}),t.on("end",()=>{try{g=!0;const s=Buffer.concat(n).toString("utf8"),l=s.trim().length>0?JSON.parse(s):{},h=K(l,{model:f});for(const B of h)o+=m(e,p(B));e.response.end(),v()}catch(s){c(s)}}),t.on("error",c);return}const E=new z({model:f});let i="";t.on("data",n=>{try{i+=n.toString("utf8");const s=A(i);i=s.remaining;for(const l of s.frames){const h=G(l);if(!h||h==="[DONE]")continue;const B=H(E,JSON.parse(h),{model:f});for(const W of B)o+=m(e,p(W))}}catch(s){t.destroy(M(s)),c(s)}}),t.on("end",()=>{try{if(g=!0,i.trim().length>0){const n=G(i);if(n&&n!=="[DONE]"){const s=H(E,JSON.parse(n),{model:f});for(const l of s)o+=m(e,p(l))}}for(const n of E.finalize())o+=m(e,p(n));e.response.end(),v()}catch(n){c(n)}}),t.on("error",c)}),d.on("error",c),e.request.on("aborted",()=>{d.destroy()}),e.response.on("close",()=>{!g&&d&&!d.destroyed&&d.destroy()}),d.write(S),d.end()})}export{te as forwardCodexGeminiCompatRequest};
@@ -1 +1 @@
1
- import W from"node:http";import b from"node:https";import{captureBuffer as x}from"../../capture/index.js";import{buildRequestOptions as P}from"../../utils/index.js";import{applyCodexModelOverrideToRequestBody as z}from"../model-override.js";import{CodexResponsesEventTranslator as _,encodeResponsesSseEvent as g,parseSseData as k,parseSseFrames as L,resolveCodexOpenaiChatCompatPath as A,stripCodexOpenaiChatCompatHeaders as F,translateResponsesRequestToChatCompletions as j}from"./translator.js";function H(e){return e instanceof Error?e:new Error(String(e))}async function G(e){const a=[];for await(const l of e.request){const m=Buffer.isBuffer(l)?l:Buffer.from(l);x(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},m),a.push(m)}return Buffer.concat(a)}function h(e,a){return x(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},a),e.response.write(a),a.length}function K(e){e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId})}function M(e,a,l){e.events.emitResponseHeaders({requestId:e.requestId,status:a,headers:l})}function Q(e){return e.target.host.toLowerCase()==="api.deepseek.com"}async function R(e,a){const l=await G(e);return await new Promise((m,N)=>{const D=e.target.protocol==="https:"?b:W;let q=!1,c=0,y=!1,s=null;const u=t=>{if(q)return;q=!0;const n=H(t);s&&!s.destroyed&&s.destroy(),e.captureWriter.append({type:"http_request_error",requestId:e.requestId,connectionId:e.connectionId,error:n.message}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-a,responseBytes:c,error:n.message}),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||(e.response.headersSent?e.response.end():e.response.end(n.message)),N(n)},I=()=>{q||(q=!0,K(e),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-a,responseBytes:c}),m(c))};let p,v,C={};try{const t=l.toString("utf8");C=z(t.trim().length>0?JSON.parse(t):{},e.request.headers);const n=j(C,{omitToolChoice:Q(e)});p=Buffer.from(JSON.stringify(n),"utf8"),v={...F(e.request.headers),"content-length":String(p.length)},delete v["content-encoding"]}catch(t){u(t);return}s=D.request(P({target:e.target,method:e.request.method??"POST",path:A(e.path),headers:v,headerOverrides:e.headerOverrides}),t=>{e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:t.statusCode??0,statusMessage:t.statusMessage??"",headers:t.headers});const n=t.statusCode??502,E=String(t.headers["content-type"]??""),J=n>=200&&n<300&&E.toLowerCase().includes("text/event-stream"),O=n>=200&&n<300&&E.toLowerCase().includes("application/json");if(!J&&!O){M(e,n,t.headers),e.response.writeHead(n,t.headers),t.on("data",d=>{c+=h(e,d)}),t.on("end",()=>{y=!0,e.response.end(),I()}),t.on("error",u);return}const T={"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"};if(M(e,200,T),e.response.writeHead(200,T),O){const d=[];t.on("data",r=>{d.push(r)}),t.on("end",()=>{try{y=!0;const r=Buffer.concat(d).toString("utf8"),o=r.trim().length>0?JSON.parse(r):{},f=new _({responseId:String(o.id??""),model:String(o.model??""),createdAt:typeof o.created=="number"?o.created:Math.floor(Date.now()/1e3),requestBody:C}),S=[...f.consumeChatCompletion(o),...f.finalize()];for(const B of S)c+=h(e,g(B));e.response.end(),I()}catch(r){u(r)}}),t.on("error",u);return}const w=new _({requestBody:C});let i="";t.on("data",d=>{try{i+=d.toString("utf8");const r=L(i);i=r.remaining;for(const o of r.frames){const f=k(o);if(!f||f==="[DONE]")continue;const S=w.consumeChatCompletionChunk(JSON.parse(f));for(const B of S)c+=h(e,g(B))}}catch(r){t.destroy(H(r)),u(r)}}),t.on("end",()=>{try{if(y=!0,i.trim().length>0){const r=k(i);if(r&&r!=="[DONE]"){const o=w.consumeChatCompletionChunk(JSON.parse(r));for(const f of o)c+=h(e,g(f))}}const d=w.finalize();for(const r of d)c+=h(e,g(r));e.response.end(),I()}catch(d){u(d)}}),t.on("error",u)}),s.on("error",t=>{u(t)}),e.request.on("aborted",()=>{s.destroy()}),e.response.on("close",()=>{!y&&!s.destroyed&&s.destroy()}),s.write(p),s.end()})}export{R as forwardCodexOpenaiChatCompatRequest};
1
+ import J from"node:http";import W from"node:https";import C from"node:zlib";import{captureBuffer as _}from"../../capture/index.js";import{buildRequestOptions as P}from"../../utils/index.js";import{applyCodexModelOverrideToRequestBody as L}from"../model-override.js";import{logProxyRuntimeStderr as $}from"../../server/logger.js";import{CodexResponsesEventTranslator as z,encodeResponsesSseEvent as I,parseSseData as b,parseSseFrames as A,resolveCodexOpenaiChatCompatPath as F,stripCodexOpenaiChatCompatHeaders as j,translateResponsesRequestToChatCompletions as R}from"./translator.js";function k(e){return e instanceof Error?e:new Error(String(e))}function G(e,s){const a=String(s["content-encoding"]??"").toLowerCase().trim();if(!a||a==="identity")return e;try{switch(a){case"gzip":case"x-gzip":return C.gunzipSync(e);case"deflate":return C.inflateSync(e);case"br":return C.brotliDecompressSync(e);case"zstd":return C.zstdDecompressSync(e);default:return e}}catch{return e}}async function K(e){const s=[];for await(const a of e.request){const m=Buffer.isBuffer(a)?a:Buffer.from(a);_(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},m),s.push(m)}return Buffer.concat(s)}function h(e,s){return _(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},s),e.response.write(s),s.length}function Q(e){e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId})}function D(e,s,a){e.events.emitResponseHeaders({requestId:e.requestId,status:s,headers:a})}function U(e){return e.target.host.toLowerCase()==="api.deepseek.com"}async function se(e,s){const a=await K(e);return await new Promise((m,H)=>{const M=e.target.protocol==="https:"?W:J;let q=!1,u=0,g=!1,o=null;const f=t=>{if(q)return;q=!0;const n=k(t),p=o?"upstream-response":"request-parse";$("error",`codex-openai chat compat request ${e.requestId} failed at ${p}: ${n.message}`),o&&!o.destroyed&&o.destroy(),e.captureWriter.append({type:"http_request_error",requestId:e.requestId,connectionId:e.connectionId,error:n.message}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-s,responseBytes:u,error:n.message}),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||(e.response.headersSent?e.response.end():e.response.end(n.message)),H(n)},S=()=>{q||(q=!0,Q(e),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-s,responseBytes:u}),m(u))};let v,w,y={};try{const t=G(a,e.request.headers).toString("utf8");y=L(t.trim().length>0?JSON.parse(t):{},e.request.headers);const n=R(y,{omitToolChoice:U(e)});v=Buffer.from(JSON.stringify(n),"utf8"),w={...j(e.request.headers),"content-length":String(v.length),"accept-encoding":"identity"},delete w["content-encoding"]}catch(t){f(t);return}o=M.request(P({target:e.target,method:e.request.method??"POST",path:F(e.path),headers:w,headerOverrides:e.headerOverrides}),t=>{e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:t.statusCode??0,statusMessage:t.statusMessage??"",headers:t.headers});const n=t.statusCode??502,p=String(t.headers["content-type"]??""),N=n>=200&&n<300&&p.toLowerCase().includes("text/event-stream"),T=n>=200&&n<300&&p.toLowerCase().includes("application/json");if(!N&&!T){D(e,n,t.headers),e.response.writeHead(n,t.headers),t.on("data",d=>{u+=h(e,d)}),t.on("end",()=>{g=!0,e.response.end(),S()}),t.on("error",f);return}const B={"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"};if(D(e,200,B),e.response.writeHead(200,B),T){const d=[];t.on("data",r=>{d.push(r)}),t.on("end",()=>{try{g=!0;const r=Buffer.concat(d).toString("utf8"),c=r.trim().length>0?JSON.parse(r):{},i=new z({responseId:String(c.id??""),model:String(c.model??""),createdAt:typeof c.created=="number"?c.created:Math.floor(Date.now()/1e3),requestBody:y}),O=[...i.consumeChatCompletion(c),...i.finalize()];for(const x of O)u+=h(e,I(x));e.response.end(),S()}catch(r){f(r)}}),t.on("error",f);return}const E=new z({requestBody:y});let l="";t.on("data",d=>{try{l+=d.toString("utf8");const r=A(l);l=r.remaining;for(const c of r.frames){const i=b(c);if(!i||i==="[DONE]")continue;const O=E.consumeChatCompletionChunk(JSON.parse(i));for(const x of O)u+=h(e,I(x))}}catch(r){t.destroy(k(r)),f(r)}}),t.on("end",()=>{try{if(g=!0,l.trim().length>0){const r=b(l);if(r&&r!=="[DONE]"){const c=E.consumeChatCompletionChunk(JSON.parse(r));for(const i of c)u+=h(e,I(i))}}const d=E.finalize();for(const r of d)u+=h(e,I(r));e.response.end(),S()}catch(d){f(d)}}),t.on("error",f)}),o.on("error",t=>{f(t)}),e.request.on("aborted",()=>{o.destroy()}),e.response.on("close",()=>{!g&&!o.destroyed&&o.destroy()}),o.write(v),o.end()})}export{se as forwardCodexOpenaiChatCompatRequest};
@@ -1 +1 @@
1
- import x from"node:http";import M from"node:https";import{captureBuffer as H}from"../../capture/index.js";import{buildRequestOptions as P}from"../../utils/index.js";import{parseSseFrames as b,parseSseData as _}from"../codex-openai/index.js";import{GeminiChatCompletionStreamTranslator as z,encodeGeminiStreamSseEvent as j,resolveGeminiOpenaiChatCompatPath as F,stripGeminiOpenaiChatCompatHeaders as L,translateChatCompletionToGeminiGenerateContentResponse as A,translateGeminiRequestToChatCompletions as K}from"./translator.js";function N(e){return e instanceof Error?e:new Error(String(e))}async function Q(e){const a=[];for await(const u of e.request){const q=Buffer.isBuffer(u)?u:Buffer.from(u);H(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},q),a.push(q)}return Buffer.concat(a)}function E(e,a){return H(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},a),e.response.write(a),a.length}function U(e){e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId})}function O(e,a,u){e.events.emitResponseHeaders({requestId:e.requestId,status:a,headers:u})}async function ee(e,a){const u=await Q(e);return await new Promise((q,G)=>{const k=e.target.protocol==="https:"?M:x;let y=!1,i=0,C=!1,s=null;const c=t=>{if(y)return;y=!0;const r=N(t);s&&!s.destroyed&&s.destroy(),e.captureWriter.append({type:"http_request_error",requestId:e.requestId,connectionId:e.connectionId,error:r.message}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-a,responseBytes:i,error:r.message}),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||(e.response.headersSent?e.response.end():e.response.end(r.message)),G(r)},p=()=>{y||(y=!0,U(e),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-a,responseBytes:i}),q(i))};let S,w;try{const t=u.toString("utf8"),r=t.trim().length>0?JSON.parse(t):{},g=K(r,e.path);S=Buffer.from(JSON.stringify(g),"utf8"),w={...L(e.request.headers),"content-length":String(S.length)},delete w["content-encoding"]}catch(t){c(t);return}s=k.request(P({target:e.target,method:e.request.method??"POST",path:F(e.path),headers:w,headerOverrides:e.headerOverrides}),t=>{e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:t.statusCode??0,statusMessage:t.statusMessage??"",headers:t.headers});const r=t.statusCode??502,g=String(t.headers["content-type"]??""),v=r>=200&&r<300&&g.toLowerCase().includes("text/event-stream"),J=r>=200&&r<300&&g.toLowerCase().includes("application/json");if(!v&&!J){O(e,r,t.headers),e.response.writeHead(r,t.headers),t.on("data",n=>{i+=E(e,n)}),t.on("end",()=>{C=!0,e.response.end(),p()}),t.on("error",c);return}if(v){const n=new z;let I=!1;const f=d=>{for(const o of d)i+=E(e,j(o))},l=()=>{I||(I=!0,f(n.finalize()))},h={"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"};O(e,200,h),e.response.writeHead(200,h);let m="";t.on("data",d=>{try{m+=d.toString("utf8");const o=b(m);m=o.remaining;for(const D of o.frames){const B=_(D);if(!B)continue;if(B==="[DONE]"){l();continue}const W=n.consumeChatCompletionChunk(JSON.parse(B));f(W)}}catch(o){t.destroy(N(o)),c(o)}}),t.on("end",()=>{try{if(C=!0,m.trim().length>0){const d=_(m);if(d&&d!=="[DONE]"){const o=n.consumeChatCompletionChunk(JSON.parse(d));f(o)}else d==="[DONE]"&&l()}l(),e.response.end(),p()}catch(d){c(d)}}),t.on("error",c);return}const T=[];t.on("data",n=>{T.push(n)}),t.on("end",()=>{try{C=!0;const n=Buffer.concat(T).toString("utf8"),I=n.trim().length>0?JSON.parse(n):{},f=A(I),l=Buffer.from(JSON.stringify(f),"utf8"),h={"content-type":"application/json; charset=utf-8"};O(e,200,h),e.response.writeHead(200,h),i+=E(e,l),e.response.end(),p()}catch(n){c(n)}}),t.on("error",c)}),s.on("error",t=>{c(t)}),e.request.on("aborted",()=>{s.destroy()}),e.response.on("close",()=>{!C&&!s.destroyed&&s.destroy()}),s.write(S),s.end()})}export{ee as forwardGeminiOpenaiChatCompatRequest};
1
+ import x from"node:http";import M from"node:https";import{captureBuffer as H}from"../../capture/index.js";import{buildRequestOptions as P}from"../../utils/index.js";import{parseSseFrames as b,parseSseData as _}from"../codex-openai/index.js";import{GeminiChatCompletionStreamTranslator as z,encodeGeminiStreamSseEvent as j,resolveGeminiOpenaiChatCompatPath as F,stripGeminiOpenaiChatCompatHeaders as L,translateChatCompletionToGeminiGenerateContentResponse as A,translateGeminiRequestToChatCompletions as K}from"./translator.js";function N(e){return e instanceof Error?e:new Error(String(e))}async function Q(e){const a=[];for await(const u of e.request){const y=Buffer.isBuffer(u)?u:Buffer.from(u);H(e.captureWriter,{type:"http_request_chunk",requestId:e.requestId,connectionId:e.connectionId},y),a.push(y)}return Buffer.concat(a)}function E(e,a){return H(e.captureWriter,{type:"http_response_chunk",requestId:e.requestId,connectionId:e.connectionId},a),e.response.write(a),a.length}function U(e){e.captureWriter.append({type:"http_response_end",requestId:e.requestId,connectionId:e.connectionId})}function O(e,a,u){e.events.emitResponseHeaders({requestId:e.requestId,status:a,headers:u})}async function ee(e,a){const u=await Q(e);return await new Promise((y,G)=>{const k=e.target.protocol==="https:"?M:x;let q=!1,i=0,g=!1,s=null;const c=t=>{if(q)return;q=!0;const r=N(t);s&&!s.destroyed&&s.destroy(),e.captureWriter.append({type:"http_request_error",requestId:e.requestId,connectionId:e.connectionId,error:r.message}),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-a,responseBytes:i,error:r.message}),e.response.headersSent||e.response.writeHead(502),e.response.writableEnded||(e.response.headersSent?e.response.end():e.response.end(r.message)),G(r)},p=()=>{q||(q=!0,U(e),e.events.emitRequestEnd({requestId:e.requestId,durationMs:Date.now()-a,responseBytes:i}),y(i))};let S,w;try{const t=u.toString("utf8"),r=t.trim().length>0?JSON.parse(t):{},C=K(r,e.path);S=Buffer.from(JSON.stringify(C),"utf8"),w={...L(e.request.headers),"content-length":String(S.length),"accept-encoding":"identity"},delete w["content-encoding"]}catch(t){c(t);return}s=k.request(P({target:e.target,method:e.request.method??"POST",path:F(e.path),headers:w,headerOverrides:e.headerOverrides}),t=>{e.captureWriter.append({type:"http_response_start",requestId:e.requestId,connectionId:e.connectionId,statusCode:t.statusCode??0,statusMessage:t.statusMessage??"",headers:t.headers});const r=t.statusCode??502,C=String(t.headers["content-type"]??""),v=r>=200&&r<300&&C.toLowerCase().includes("text/event-stream"),J=r>=200&&r<300&&C.toLowerCase().includes("application/json");if(!v&&!J){O(e,r,t.headers),e.response.writeHead(r,t.headers),t.on("data",n=>{i+=E(e,n)}),t.on("end",()=>{g=!0,e.response.end(),p()}),t.on("error",c);return}if(v){const n=new z;let I=!1;const f=d=>{for(const o of d)i+=E(e,j(o))},l=()=>{I||(I=!0,f(n.finalize()))},h={"content-type":"text/event-stream; charset=utf-8","cache-control":"no-cache",connection:"keep-alive"};O(e,200,h),e.response.writeHead(200,h);let m="";t.on("data",d=>{try{m+=d.toString("utf8");const o=b(m);m=o.remaining;for(const D of o.frames){const B=_(D);if(!B)continue;if(B==="[DONE]"){l();continue}const W=n.consumeChatCompletionChunk(JSON.parse(B));f(W)}}catch(o){t.destroy(N(o)),c(o)}}),t.on("end",()=>{try{if(g=!0,m.trim().length>0){const d=_(m);if(d&&d!=="[DONE]"){const o=n.consumeChatCompletionChunk(JSON.parse(d));f(o)}else d==="[DONE]"&&l()}l(),e.response.end(),p()}catch(d){c(d)}}),t.on("error",c);return}const T=[];t.on("data",n=>{T.push(n)}),t.on("end",()=>{try{g=!0;const n=Buffer.concat(T).toString("utf8"),I=n.trim().length>0?JSON.parse(n):{},f=A(I),l=Buffer.from(JSON.stringify(f),"utf8"),h={"content-type":"application/json; charset=utf-8"};O(e,200,h),e.response.writeHead(200,h),i+=E(e,l),e.response.end(),p()}catch(n){c(n)}}),t.on("error",c)}),s.on("error",t=>{c(t)}),e.request.on("aborted",()=>{s.destroy()}),e.response.on("close",()=>{!g&&!s.destroyed&&s.destroy()}),s.write(S),s.end()})}export{ee as forwardGeminiOpenaiChatCompatRequest};
@@ -0,0 +1 @@
1
+ function t(e,o,a,r={}){return{routeKey:`grix-gateway-${e}`,targetBaseUrl:o,headers:{Authorization:`Bearer ${a}`},...r.model?{model:r.model}:{},...e==="claude"?{passthrough:!0}:{},...e==="codex"?{codexResponsesToChat:!0}:{},relayOnly:!0}}export{t as buildGatewayRoute};
@@ -1 +1 @@
1
- import{ProxyManager as l}from"./proxy-manager.js";import{ProxyManager as g,ProxyNoRelayRouteError as O}from"./proxy-manager.js";import{relayHostForClientType as E}from"./relay-hosts.js";let o=null;function y(r){return o||(o=new l(r)),o}function c(){return o}function u(){o=null}class s 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 i="localhost,127.0.0.1,::1";function p(r,e){if(o?.isRelayExpectedButUnavailable(e))throw new s(e);const n=o?.getInjectionEnv(e);if(!n)return r;const a={...r,...n},t=r?.NO_PROXY??r?.no_proxy??process.env.NO_PROXY??process.env.no_proxy;return a.NO_PROXY=t&&t.trim()?`${t},${i}`:i,a}export{g as ProxyManager,O as ProxyNoRelayRouteError,s as ProxyRelayUnavailableError,p as applyProxyEnv,c as getProxyManager,y as initProxyManager,E as relayHostForClientType,u as resetProxyManagerForTest};
1
+ import{ProxyManager as l}from"./proxy-manager.js";import{ProxyManager as g,ProxyNoRelayRouteError as R}from"./proxy-manager.js";import{relayHostsForClientType as E}from"./relay-hosts.js";import{buildGatewayRoute as d}from"./gateway-route.js";let o=null;function y(r){return o||(o=new l(r)),o}function u(){return o}function c(){o=null}class s 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 i="localhost,127.0.0.1,::1";function p(r,e){if(o?.isRelayExpectedButUnavailable(e))throw new s(e);const n=o?.getInjectionEnv(e);if(!n)return r;const a={...r,...n},t=r?.NO_PROXY??r?.no_proxy??process.env.NO_PROXY??process.env.no_proxy;return a.NO_PROXY=t&&t.trim()?`${t},${i}`:i,a}export{g as ProxyManager,R as ProxyNoRelayRouteError,s as ProxyRelayUnavailableError,p as applyProxyEnv,d as buildGatewayRoute,u as getProxyManager,y as initProxyManager,E as relayHostsForClientType,c as resetProxyManagerForTest};
@@ -1,7 +1,7 @@
1
- import{existsSync as w,readFileSync as x}from"node:fs";import{mkdir as p,rm as m,writeFile as c}from"node:fs/promises";import{join as o}from"node:path";import{rootCertificates as A}from"node:tls";import{DynamicCertificateAuthority as I}from"./crypto/index.js";import{TrafficCaptureWriter as E,TrafficEventEmitter as H}from"./capture/index.js";import{HostInterceptList as D,RuntimeRouteRegistry as P}from"./routing/index.js";import{AcpTrafficProxyServer as C}from"./server/index.js";import{logProxyRuntimeStderr as i}from"./server/logger.js";class l{filePath="";append(){}close(){return Promise.resolve()}}class S extends Error{agentName;host;code="NO_RELAY_ROUTE";constructor(t,e){super(`no usable relay route for agent "${t}" (host ${e}); provision a gateway key first`),this.agentName=t,this.host=e,this.name="ProxyNoRelayRouteError"}}const h="proxy-state.json",d="routes.json",a="grix-gateway";class k{proxyDataDir;certificateAuthority=null;captureWriter=new l;runtimeRoutes=new P;interceptHosts=new D;server=null;runtimeInfo=null;relayAgents=new Map;configExtras={};persistQueue=Promise.resolve();configLoaded=!1;lifecycleQueue=Promise.resolve();enqueueLifecycle(t){const e=this.lifecycleQueue.then(t,t);return this.lifecycleQueue=e.catch(()=>{}),e}constructor(t){this.proxyDataDir=o(t,"proxy"),this.loadConfigFromDisk()}loadConfigFromDisk(){if(this.configLoaded)return;this.configLoaded=!0;const t=this.loadRoutesFile();t&&(this.applyRoutesFile(t),t.capture!==void 0&&(this.configExtras.capture=t.capture),t.mitmPort!==void 0&&(this.configExtras.mitmPort=t.mitmPort),t.interceptHosts&&this.interceptHosts.setHosts(t.interceptHosts),this.relayAgents.size>0&&this.syncInterceptHosts())}async start(t={}){return this.enqueueLifecycle(()=>this.startInternal(t))}async startInternal(t={}){if(this.runtimeInfo)return this.runtimeInfo;await p(this.proxyDataDir,{recursive:!0});const e=t.captureEnabled??this.configExtras.capture??process.env.GRIX_PROXY_CAPTURE==="1";this.captureWriter=e?await E.create(this.proxyDataDir,{onCleanupError:(g,u)=>{const R=u instanceof Error?u.message:String(u);i("warn",`stale capture cleanup skipped ${g}: ${R}`)}}):new l,this.certificateAuthority=new I(this.proxyDataDir);const r=await this.certificateAuthority.ensureRootCertificate(),s=await this.writeCaBundle(r.certPem);t.interceptHosts&&this.interceptHosts.setHosts(t.interceptHosts),this.server=new C(this.captureWriter,this.certificateAuthority,new H,this.runtimeRoutes,this.interceptHosts);const n=t.port??this.configExtras.mitmPort??0,{port:y}=await this.server.start(n,"127.0.0.1");return this.runtimeInfo={mitmPort:y,proxyUrl:`http://127.0.0.1:${y}`,caCertPath:r.certPath,caBundlePath:s,captureFilePath:this.captureWriter.filePath,interceptHosts:this.interceptHosts.getHosts()},await this.writeStateFile(this.runtimeInfo),this.runtimeInfo}async setAgentRelayEnabled(t,e,r={}){return this.enqueueLifecycle(()=>this.setAgentRelayEnabledInternal(t,e,r))}async setAgentRelayEnabledInternal(t,e,r={}){const s=t.trim();if(s){if(e){const n=r.relayHost?.trim();if(!n)throw new Error(`relayHost is required to enable relay for agent "${s}"`);if(!this.runtimeRoutes.hasUsableHostRoute(n))throw new S(s,n);await this.startInternal(),this.relayAgents.set(s,n)}else this.relayAgents.delete(s);this.syncInterceptHosts(),await this.persistConfig()}}syncInterceptHosts(){this.interceptHosts.setHosts([...new Set(this.relayAgents.values())]),this.runtimeInfo&&(this.runtimeInfo={...this.runtimeInfo,interceptHosts:this.interceptHosts.getHosts()},this.writeStateFile(this.runtimeInfo).catch(t=>{i("warn",`failed to write proxy-state.json: ${t}`)}))}isAgentRelayEnabled(t){return this.relayAgents.has(t.trim())}getRelayAgents(){return[...this.relayAgents.keys()]}isRelayExpectedButUnavailable(t){return this.isAgentRelayEnabled(t)&&!this.runtimeInfo}getDegradedRelayAgents(){return this.runtimeInfo?[]:[...this.relayAgents.keys()]}async startIfAnyRelayAgent(){this.relayAgents.size!==0&&await this.start()}async stopIfUnused(t){return this.enqueueLifecycle(async()=>!this.runtimeInfo||this.relayAgents.size>0||t?.()?!1:(await this.stopInternal(),!0))}async disableAllAgentRelay(){return this.enqueueLifecycle(async()=>{const t=[...this.relayAgents.keys()];return this.relayAgents.clear(),this.syncInterceptHosts(),await this.persistConfig(),t})}async stop(){return this.enqueueLifecycle(()=>this.stopInternal())}async stopInternal(){this.server&&(await this.server.stop().catch(()=>{}),this.server=null),await this.captureWriter.close().catch(()=>{}),this.captureWriter=new l,this.runtimeInfo=null,await m(o(this.proxyDataDir,h),{force:!0}).catch(()=>{})}async clearRuntimeState(){this.runtimeInfo||await m(o(this.proxyDataDir,h),{force:!0}).catch(()=>{})}getRuntimeInfo(){return this.runtimeInfo}getMitmPort(){return this.runtimeInfo?.mitmPort??null}getCaCertPath(){return this.runtimeInfo?.caCertPath??null}getCaBundlePath(){return this.runtimeInfo?.caBundlePath??null}setRoute(t){this.runtimeRoutes.setRoute(t)}deleteRoute(t){this.runtimeRoutes.deleteRoute(t)}setDefaultRouteKey(t){this.runtimeRoutes.setDefaultRouteKey(t)}setHostDefaultRoute(t,e){this.runtimeRoutes.setHostDefaultRoute(t,e)}getInterceptHosts(){return this.interceptHosts.getHosts()}setInterceptHosts(t){this.interceptHosts.setHosts(t),this.runtimeInfo&&(this.runtimeInfo={...this.runtimeInfo,interceptHosts:this.interceptHosts.getHosts()},this.writeStateFile(this.runtimeInfo).catch(e=>{i("warn",`failed to write proxy-state.json: ${e}`)}))}getConfigSnapshot(){const t={routes:this.runtimeRoutes.listRoutes(),interceptHosts:this.interceptHosts.getHosts(),relayAgents:Object.fromEntries(this.relayAgents)},e=this.runtimeRoutes.getDefaultRouteKey();e&&(t.defaultRouteKey=e);const r=this.runtimeRoutes.getHostDefaultRoutes();return Object.keys(r).length>0&&(t.hostDefaultRoutes=r),this.configExtras.capture!==void 0&&(t.capture=this.configExtras.capture),this.configExtras.mitmPort!==void 0&&(t.mitmPort=this.configExtras.mitmPort),t}async persistConfig(){const t=this.getConfigSnapshot(),e=this.persistQueue.then(()=>this.writeConfigFile(t));return this.persistQueue=e.catch(()=>{}),e}async writeConfigFile(t){await p(this.proxyDataDir,{recursive:!0});const e=o(this.proxyDataDir,d);await c(e,`${JSON.stringify(t,null,2)}
2
- `,"utf8")}getInjectionEnv(t){return!this.isAgentRelayEnabled(t)||!this.runtimeInfo?null:{HTTPS_PROXY:this.runtimeInfo.proxyUrl,NODE_EXTRA_CA_CERTS:this.runtimeInfo.caCertPath,SSL_CERT_FILE:this.runtimeInfo.caBundlePath}}async writeCaBundle(t){const e=o(this.proxyDataDir,"ca-bundle.pem"),r=A.join(`
1
+ import{existsSync as x,readFileSync as A}from"node:fs";import{mkdir as d,rm as g,writeFile as f}from"node:fs/promises";import{join as u}from"node:path";import{rootCertificates as H}from"node:tls";import{DynamicCertificateAuthority as P}from"./crypto/index.js";import{TrafficCaptureWriter as D,TrafficEventEmitter as C}from"./capture/index.js";import{HostInterceptList as S,RuntimeRouteRegistry as T}from"./routing/index.js";import{AcpTrafficProxyServer as _}from"./server/index.js";import{logProxyRuntimeStderr as n}from"./server/logger.js";function R(o){const t=Array.isArray(o)?o:[o],e=new Set,r=[];for(const s of t){const i=String(s??"").trim();i&&!e.has(i)&&(e.add(i),r.push(i))}return r}function L(o){return R([...o.relayHosts??[],...o.relayHost?[o.relayHost]:[]])}class y{filePath="";append(){}close(){return Promise.resolve()}}class $ extends Error{agentName;host;code="NO_RELAY_ROUTE";constructor(t,e){super(`no usable relay route for agent "${t}" (host ${e}); provision a gateway key first`),this.agentName=t,this.host=e,this.name="ProxyNoRelayRouteError"}}class w extends Error{constructor(t){super(t),this.name="ProxyLifecycleTimeoutError"}}const p="proxy-state.json",E="routes.json",l="grix-gateway";class m{proxyDataDir;certificateAuthority=null;captureWriter=new y;runtimeRoutes=new T;interceptHosts=new S;server=null;runtimeInfo=null;relayAgents=new Map;configExtras={};persistQueue=Promise.resolve();configLoaded=!1;lifecycleQueue=Promise.resolve();static LIFECYCLE_OP_TIMEOUT_MS=3e4;withLifecycleTimeout(t){return new Promise((e,r)=>{const s=setTimeout(()=>{r(new w(`proxy lifecycle operation timed out after ${m.LIFECYCLE_OP_TIMEOUT_MS}ms`))},m.LIFECYCLE_OP_TIMEOUT_MS);t().then(i=>{clearTimeout(s),e(i)},i=>{clearTimeout(s),r(i)})})}enqueueLifecycle(t){const e=()=>this.withLifecycleTimeout(t),r=this.lifecycleQueue.then(e,e);return r.catch(s=>{s instanceof w&&n("error",`proxy lifecycle operation timed out: ${s.message}`)}),this.lifecycleQueue=r.catch(()=>{}),r}constructor(t){this.proxyDataDir=u(t,"proxy"),this.loadConfigFromDisk()}loadConfigFromDisk(){if(this.configLoaded)return;this.configLoaded=!0;const t=this.loadRoutesFile();t&&(this.applyRoutesFile(t),t.capture!==void 0&&(this.configExtras.capture=t.capture),t.mitmPort!==void 0&&(this.configExtras.mitmPort=t.mitmPort),t.interceptHosts&&this.interceptHosts.setHosts(t.interceptHosts),this.relayAgents.size>0&&this.syncInterceptHosts())}async start(t={}){return this.enqueueLifecycle(()=>this.startInternal(t))}async startInternal(t={}){if(this.runtimeInfo)return this.runtimeInfo;await d(this.proxyDataDir,{recursive:!0});const e=t.captureEnabled??this.configExtras.capture??process.env.GRIX_PROXY_CAPTURE==="1";this.captureWriter=e?await D.create(this.proxyDataDir,{onCleanupError:(a,h)=>{const I=h instanceof Error?h.message:String(h);n("warn",`stale capture cleanup skipped ${a}: ${I}`)}}):new y,this.certificateAuthority=new P(this.proxyDataDir);const r=await this.certificateAuthority.ensureRootCertificate(),s=await this.writeCaBundle(r.certPem);t.interceptHosts&&this.interceptHosts.setHosts(t.interceptHosts),this.server=new _(this.captureWriter,this.certificateAuthority,new C,this.runtimeRoutes,this.interceptHosts);const i=t.port??this.configExtras.mitmPort??0,{port:c}=await this.server.start(i,"127.0.0.1");return this.runtimeInfo={mitmPort:c,proxyUrl:`http://127.0.0.1:${c}`,caCertPath:r.certPath,caBundlePath:s,captureFilePath:this.captureWriter.filePath,interceptHosts:this.interceptHosts.getHosts()},await this.writeStateFile(this.runtimeInfo),this.runtimeInfo}async setAgentRelayEnabled(t,e,r={}){return this.enqueueLifecycle(()=>this.setAgentRelayEnabledInternal(t,e,r))}async setAgentRelayEnabledInternal(t,e,r={}){const s=t.trim();if(s){if(e){const i=L(r);if(i.length===0)throw new Error(`relayHost(s) required to enable relay for agent "${s}"`);const c=i.map(a=>this.runtimeRoutes.getUsableRouteKeyForHost(a)).find(a=>a!==null);if(!c)throw new $(s,i[0]);for(const a of i)this.runtimeRoutes.hasUsableHostRoute(a)||this.runtimeRoutes.setHostDefaultRoute(a,c);await this.startInternal(),this.relayAgents.set(s,i)}else this.relayAgents.delete(s);this.syncInterceptHosts(),await this.persistConfig()}}syncInterceptHosts(){this.interceptHosts.setHosts(new Set([...this.relayAgents.values()].flat())),this.runtimeInfo&&(this.runtimeInfo={...this.runtimeInfo,interceptHosts:this.interceptHosts.getHosts()},this.writeStateFile(this.runtimeInfo).catch(t=>{n("warn",`failed to write proxy-state.json: ${t}`)}))}isAgentRelayEnabled(t){return this.relayAgents.has(t.trim())}getRelayAgents(){return[...this.relayAgents.keys()]}isRelayExpectedButUnavailable(t){return this.isAgentRelayEnabled(t)&&!this.runtimeInfo}getDegradedRelayAgents(){return this.runtimeInfo?[]:[...this.relayAgents.keys()]}async startIfAnyRelayAgent(){this.relayAgents.size!==0&&await this.start()}async stopIfUnused(t){return this.enqueueLifecycle(async()=>!this.runtimeInfo||this.relayAgents.size>0||t?.()?!1:(await this.stopInternal(),!0))}async disableAllAgentRelay(){return this.enqueueLifecycle(async()=>{const t=[...this.relayAgents.keys()];return this.relayAgents.clear(),this.syncInterceptHosts(),await this.persistConfig(),t})}async stop(){return this.enqueueLifecycle(()=>this.stopInternal())}async stopInternal(){this.server&&(await this.server.stop().catch(()=>{}),this.server=null),await this.captureWriter.close().catch(()=>{}),this.captureWriter=new y,this.runtimeInfo=null,await g(u(this.proxyDataDir,p),{force:!0}).catch(()=>{})}async clearRuntimeState(){this.runtimeInfo||await g(u(this.proxyDataDir,p),{force:!0}).catch(()=>{})}getRuntimeInfo(){return this.runtimeInfo}getMitmPort(){return this.runtimeInfo?.mitmPort??null}getCaCertPath(){return this.runtimeInfo?.caCertPath??null}getCaBundlePath(){return this.runtimeInfo?.caBundlePath??null}setRoute(t){this.runtimeRoutes.setRoute(t)}deleteRoute(t){this.runtimeRoutes.deleteRoute(t)}setDefaultRouteKey(t){this.runtimeRoutes.setDefaultRouteKey(t)}setHostDefaultRoute(t,e){this.runtimeRoutes.setHostDefaultRoute(t,e)}getInterceptHosts(){return this.interceptHosts.getHosts()}setInterceptHosts(t){this.interceptHosts.setHosts(t),this.runtimeInfo&&(this.runtimeInfo={...this.runtimeInfo,interceptHosts:this.interceptHosts.getHosts()},this.writeStateFile(this.runtimeInfo).catch(e=>{n("warn",`failed to write proxy-state.json: ${e}`)}))}getConfigSnapshot(){const t={routes:this.runtimeRoutes.listRoutes(),interceptHosts:this.interceptHosts.getHosts(),relayAgents:Object.fromEntries(this.relayAgents)},e=this.runtimeRoutes.getDefaultRouteKey();e&&(t.defaultRouteKey=e);const r=this.runtimeRoutes.getHostDefaultRoutes();return Object.keys(r).length>0&&(t.hostDefaultRoutes=r),this.configExtras.capture!==void 0&&(t.capture=this.configExtras.capture),this.configExtras.mitmPort!==void 0&&(t.mitmPort=this.configExtras.mitmPort),t}async persistConfig(){const t=this.getConfigSnapshot(),e=this.persistQueue.then(()=>this.writeConfigFile(t));return this.persistQueue=e.catch(()=>{}),e}async writeConfigFile(t){await d(this.proxyDataDir,{recursive:!0});const e=u(this.proxyDataDir,E);await f(e,`${JSON.stringify(t,null,2)}
2
+ `,"utf8")}getInjectionEnv(t){return!this.isAgentRelayEnabled(t)||!this.runtimeInfo?null:{HTTPS_PROXY:this.runtimeInfo.proxyUrl,NODE_EXTRA_CA_CERTS:this.runtimeInfo.caCertPath,SSL_CERT_FILE:this.runtimeInfo.caBundlePath}}async writeCaBundle(t){const e=u(this.proxyDataDir,"ca-bundle.pem"),r=H.join(`
3
3
  `),s=t.endsWith(`
4
4
  `)?t:`${t}
5
- `;return await c(e,`${r}
6
- ${s}`,"utf8"),e}loadRoutesFile(){const t=o(this.proxyDataDir,d);if(!w(t))return null;try{return JSON.parse(x(t,"utf8"))}catch(e){const r=e instanceof Error?e.message:String(e);return i("warn",`proxy routes file ignored (${t}): ${r}`),null}}applyRoutesFile(t){if(Array.isArray(t.relayAgents))i("warn","relayAgents in routes.json is an outdated array format (no host info): discarding it. Re-enable Grix relay per agent in the desktop app.");else for(const[e,r]of Object.entries(t.relayAgents??{})){const s=e.trim(),n=String(r??"").trim();s&&n&&this.relayAgents.set(s,n)}this.relayAgents.size>0&&i("info",`proxy relay agents restored: ${[...this.relayAgents.keys()].join(", ")}`),t.relayAgents===void 0&&(t.routes??[]).some(e=>e.routeKey.startsWith("grix-gateway"))&&i("warn","found legacy Grix relay config without per-agent list: relay is now per-agent and starts OFF. Re-enable it per agent in the desktop app (Settings \u2192 Grix relay).");for(const e of t.routes??[]){if(e.routeKey===a){i("info",`proxy legacy route dropped: ${a}`);continue}try{this.runtimeRoutes.handleControlLine(JSON.stringify(e.type==="delete_route"?e:{type:"set_route",routeKey:e.routeKey,targetBaseUrl:e.targetBaseUrl,...e.headers?{headers:e.headers}:{},...e.model?{model:e.model}:{},...e.modelMap?{modelMap:e.modelMap}:{},...e.passthrough?{passthrough:!0}:{},...e.relayOnly?{relayOnly:!0}:{},...e.codexResponsesToChat?{codexResponsesToChat:!0}:{}})),i("info",`proxy route loaded: ${e.routeKey} \u2192 ${e.targetBaseUrl}`)}catch(r){const s=r instanceof Error?r.message:String(r);i("warn",`proxy route "${e.routeKey}" skipped: ${s}`)}}t.defaultRouteKey&&t.defaultRouteKey!==a?(this.runtimeRoutes.setDefaultRouteKey(t.defaultRouteKey),i("info",`proxy default route key: ${t.defaultRouteKey}`)):t.defaultRouteKey===a&&i("info",`proxy legacy default route key dropped: ${a}`);for(const[e,r]of Object.entries(t.hostDefaultRoutes??{}))this.runtimeRoutes.setHostDefaultRoute(e,r),i("info",`proxy host default route: ${e} \u2192 ${r}`)}async writeStateFile(t){const e=o(this.proxyDataDir,h),r={...t,pid:process.pid,updated_at:Date.now()};await c(e,`${JSON.stringify(r,null,2)}
7
- `,"utf8")}}export{k as ProxyManager,S as ProxyNoRelayRouteError};
5
+ `;return await f(e,`${r}
6
+ ${s}`,"utf8"),e}loadRoutesFile(){const t=u(this.proxyDataDir,E);if(!x(t))return null;try{return JSON.parse(A(t,"utf8"))}catch(e){const r=e instanceof Error?e.message:String(e);return n("warn",`proxy routes file ignored (${t}): ${r}`),null}}applyRoutesFile(t){if(Array.isArray(t.relayAgents))n("warn","relayAgents in routes.json is an outdated array format (no host info): discarding it. Re-enable Grix relay per agent in the desktop app.");else for(const[e,r]of Object.entries(t.relayAgents??{})){const s=e.trim(),i=R(r);s&&i.length>0&&this.relayAgents.set(s,i)}this.relayAgents.size>0&&n("info",`proxy relay agents restored: ${[...this.relayAgents.keys()].join(", ")}`),t.relayAgents===void 0&&(t.routes??[]).some(e=>e.routeKey.startsWith("grix-gateway"))&&n("warn","found legacy Grix relay config without per-agent list: relay is now per-agent and starts OFF. Re-enable it per agent in the desktop app (Settings \u2192 Grix relay).");for(const e of t.routes??[]){if(e.routeKey===l){n("info",`proxy legacy route dropped: ${l}`);continue}try{this.runtimeRoutes.handleControlLine(JSON.stringify(e.type==="delete_route"?e:{type:"set_route",routeKey:e.routeKey,targetBaseUrl:e.targetBaseUrl,...e.headers?{headers:e.headers}:{},...e.model?{model:e.model}:{},...e.modelMap?{modelMap:e.modelMap}:{},...e.passthrough?{passthrough:!0}:{},...e.relayOnly?{relayOnly:!0}:{},...e.codexResponsesToChat?{codexResponsesToChat:!0}:{}})),n("info",`proxy route loaded: ${e.routeKey} \u2192 ${e.targetBaseUrl}`)}catch(r){const s=r instanceof Error?r.message:String(r);n("warn",`proxy route "${e.routeKey}" skipped: ${s}`)}}t.defaultRouteKey&&t.defaultRouteKey!==l?(this.runtimeRoutes.setDefaultRouteKey(t.defaultRouteKey),n("info",`proxy default route key: ${t.defaultRouteKey}`)):t.defaultRouteKey===l&&n("info",`proxy legacy default route key dropped: ${l}`);for(const[e,r]of Object.entries(t.hostDefaultRoutes??{}))this.runtimeRoutes.setHostDefaultRoute(e,r),n("info",`proxy host default route: ${e} \u2192 ${r}`)}async writeStateFile(t){const e=u(this.proxyDataDir,p),r={...t,pid:process.pid,updated_at:Date.now()};await f(e,`${JSON.stringify(r,null,2)}
7
+ `,"utf8")}}export{m as ProxyManager,$ as ProxyNoRelayRouteError};
@@ -0,0 +1 @@
1
+ import{relayHostsForClientType as h}from"./relay-hosts.js";import{buildGatewayRoute as R}from"./gateway-route.js";import{setOneAgentRelayAndRestart as g}from"./relay-orchestration.js";class e extends Error{code;constructor(i,s){super(i),this.code=s,this.name="RelayCredentialError"}}async function N(o,i,s,I,t){const r=t.localName.trim(),_=t.agentId.trim(),d=t.virtualKey.trim();if(!r)throw new e("agent name is required","INVALID_ARGUMENT");if(!_)throw new e("agent_id is required","MISSING_AGENT_ID");if(!d)throw new e("virtual_key is required","MISSING_VIRTUAL_KEY");const n=s(r);if(!n)throw new e(`Agent "${r}" not found`,"NOT_FOUND");if(n.agentId&&n.agentId!==_)throw new e(`agent_id does not match local agent "${r}"`,"AGENT_ID_MISMATCH");const a=String(n.clientType??"").trim().toLowerCase(),l=h(a);if(l.length===0)throw new e(`client type "${a}" does not support Grix relay`,"UNSUPPORTED_CLIENT_TYPE");const c=a==="claude",u=(c?t.anthropicBaseUrl:t.openaiBaseUrl)?.trim();if(!u)throw new e(`${c?"anthropic_base_url":"openai_base_url"} is required`,"MISSING_TARGET_BASE_URL");try{new URL(u)}catch{throw new e(`${c?"anthropic_base_url":"openai_base_url"} must be a valid URL`,"INVALID_TARGET_BASE_URL")}const w=R(a,u,d);o.setRoute(w);for(const y of l)o.setHostDefaultRoute(y,w.routeKey);return g(o,i,I,{agentName:r,enabled:!0,relayHosts:l})}export{e as RelayCredentialError,N as setAgentRelayCredential};
@@ -1 +1 @@
1
- const e={claude:"api.anthropic.com",codex:"api.openai.com"};function t(o){return e[String(o??"").trim().toLowerCase()]}export{t as relayHostForClientType};
1
+ const c={claude:["api.anthropic.com"],codex:["api.openai.com","chatgpt.com","chat.openai.com"]};function e(t){const o=c[String(t??"").trim().toLowerCase()];return o?[...o]:[]}export{e as relayHostsForClientType};
@@ -1 +1 @@
1
- async function f(a,t){return a.getRuntimeInfo()?a.stopIfUnused(()=>t.getAgentsWithStaleRelayEnv().length>0):!1}async function u(a,t,n){for(const e of a.getRelayAgents())t.markAgentRelayEnvStale(e);const s=await a.disableAllAgentRelay();for(const e of s)t.markAgentRelayEnvStale(e);for(const e of s){if(t.isAgentBusy(e)){n.info(`relay disabled for "${e}" but agent is busy; env stays stale until its next message`);continue}await t.restartAgent(e).catch(r=>{if(r?.code==="NOT_FOUND"){n.info(`relay disabled for "${e}" (agent no longer exists; nothing to restart)`);return}n.warn(`relay disabled for "${e}" but restart failed (env stays stale): ${r}`)})}return await f(a,t),s}async function g(a,t,n,s){const{agentName:e,enabled:r,relayHost:y}=s;r||t.markAgentRelayEnvStale(e),await a.setAgentRelayEnabled(e,r,r?{relayHost:y}:{});let l=!1,o=!1;if(t.isAgentBusy(e))o=!0,t.markAgentRelayEnvStale(e),n.info(`relay toggled for "${e}" but agent is busy; env stays stale until its next message`);else try{await t.restartAgent(e),l=!0}catch(i){if(i?.code==="NOT_FOUND")n.info(`relay toggled for "${e}" (agent no longer exists; nothing to restart)`);else{const d=t.markAgentRelayEnvStale(e);n.warn(`relay toggled for "${e}" but restart failed${d?" (old instance still holds the proxy env; marked stale)":" (no running instance; nothing holds the proxy env)"}: ${i}`)}}return await f(a,t),{restarted:l,busy:o}}export{u as disableAllRelayAndRestart,g as setOneAgentRelayAndRestart,f as stopProxyIfNobodyNeedsIt};
1
+ async function f(a,t){return a.getRuntimeInfo()?a.stopIfUnused(()=>t.getAgentsWithStaleRelayEnv().length>0):!1}async function g(a,t,n){for(const e of a.getRelayAgents())t.markAgentRelayEnvStale(e);const s=await a.disableAllAgentRelay();for(const e of s)t.markAgentRelayEnvStale(e);for(const e of s){if(t.isAgentBusy(e)){n.info(`relay disabled for "${e}" but agent is busy; env stays stale until its next message`);continue}await t.restartAgent(e).catch(r=>{if(r?.code==="NOT_FOUND"){n.info(`relay disabled for "${e}" (agent no longer exists; nothing to restart)`);return}n.warn(`relay disabled for "${e}" but restart failed (env stays stale): ${r}`)})}return await f(a,t),s}async function c(a,t,n,s){const{agentName:e,enabled:r,relayHost:y,relayHosts:d}=s;r||t.markAgentRelayEnvStale(e),await a.setAgentRelayEnabled(e,r,r?{relayHost:y,relayHosts:d}:{});let l=!1,o=!1;if(t.isAgentBusy(e))o=!0,t.markAgentRelayEnvStale(e),n.info(`relay toggled for "${e}" but agent is busy; env stays stale until its next message`);else try{await t.restartAgent(e),l=!0}catch(i){if(i?.code==="NOT_FOUND")n.info(`relay toggled for "${e}" (agent no longer exists; nothing to restart)`);else{const u=t.markAgentRelayEnvStale(e);n.warn(`relay toggled for "${e}" but restart failed${u?" (old instance still holds the proxy env; marked stale)":" (no running instance; nothing holds the proxy env)"}: ${i}`)}}return await f(a,t),{restarted:l,busy:o}}export{g as disableAllRelayAndRestart,c as setOneAgentRelayAndRestart,f as stopProxyIfNobodyNeedsIt};
@@ -1 +1 @@
1
- import{normalizeHost as i}from"./intercept-list.js";const h="x-grix-proxy-route-key";function a(s){return Array.isArray(s)?s[0]?.trim()||null:s?.trim()||null}function d(s){return typeof s!="object"||s===null||Array.isArray(s)?!1:Object.values(s).every(e=>typeof e=="string")}function y(s){if(typeof s!="object"||s===null||Array.isArray(s))return!1;const e=s;return e.type!=="set_route"&&e.type!=="delete_route"&&e.type!=="pin_route"&&e.type!=="unpin_route"||typeof e.routeKey!="string"||e.routeKey.trim().length===0?!1:e.type==="unpin_route"?typeof e.sessionId=="string"&&e.sessionId.trim().length>0:e.type==="delete_route"?!0:typeof e.targetBaseUrl=="string"&&e.targetBaseUrl.trim().length>0&&(e.headers===void 0||d(e.headers))&&(e.model===void 0||typeof e.model=="string")&&(e.modelMap===void 0||d(e.modelMap))&&(e.passthrough===void 0||typeof e.passthrough=="boolean")&&(e.type==="set_route"||typeof e.sessionId=="string"&&e.sessionId.trim().length>0)}function f(s){const e=s.model?.trim();return{routeKey:s.routeKey.trim(),targetBaseUrl:s.targetBaseUrl.trim(),...s.headers?{headers:{...s.headers}}:{},...e?{model:e}:{},...s.modelMap?{modelMap:{...s.modelMap}}:{},...s.passthrough?{passthrough:!0}:{},...s.relayOnly?{relayOnly:!0}:{},...s.codexResponsesToChat?{codexResponsesToChat:!0}:{}}}class R{routes=new Map;pinnedRoutes=new Map;defaultRouteKey=null;hostDefaultRoutes=new Map;setDefaultRouteKey(e){this.defaultRouteKey=e?.trim()||null}getDefaultRouteKey(){return this.defaultRouteKey}setHostDefaultRoute(e,r){const t=i(e);if(!t)return;const o=r?.trim();o?this.hostDefaultRoutes.set(t,o):this.hostDefaultRoutes.delete(t)}getHostDefaultRoutes(){return Object.fromEntries(this.hostDefaultRoutes)}hasUsableHostRoute(e){const r=this.hostDefaultRoutes.get(i(e));return!!r&&this.routes.has(r)}listRoutes(){return[...this.routes.values()].map(e=>({...e}))}setRoute(e){const r=f(e);this.routes.set(r.routeKey,r)}deleteRoute(e){this.routes.delete(e.trim())}pinRoute(e,r){const t=f(e);this.pinnedRoutes.set(this.buildPinnedRouteKey(t.routeKey,r),t)}unpinRoute(e,r){this.pinnedRoutes.delete(this.buildPinnedRouteKey(e,r))}resolveRequestRouteInfo(e,r){const t=a(e.headers[h]);if(!t){if(r){const n=this.hostDefaultRoutes.get(i(r));if(n){const l=this.routes.get(n);if(l)return{route:l,routeKey:n,pinned:!1}}}if(this.defaultRouteKey){const n=this.routes.get(this.defaultRouteKey);if(n)return{route:n,routeKey:this.defaultRouteKey,pinned:!1}}return null}const o=a(e.headers["x-claude-code-session-id"]);if(o){const n=this.pinnedRoutes.get(this.buildPinnedRouteKey(t,o));if(n)return{route:n,routeKey:t,sessionId:o,pinned:!0}}const u=this.routes.get(t);return u?{route:u,routeKey:t,...o?{sessionId:o}:{},pinned:!1}:null}resolveRequestRoute(e){return this.resolveRequestRouteInfo(e)?.route??null}buildPinnedRouteKey(e,r){return`${e.trim()}:${r.trim()}`}handleControlLine(e){const r=e.trim();if(!r)return!1;const t=JSON.parse(r);if(!y(t))return!1;if(t.type==="delete_route")return this.deleteRoute(t.routeKey),!0;if(t.type==="unpin_route")return this.unpinRoute(t.routeKey,t.sessionId??""),!0;const o=t.targetBaseUrl;if(!o)return!1;const u={routeKey:t.routeKey,targetBaseUrl:o,...t.headers?{headers:t.headers}:{},...t.model?{model:t.model}:{},...t.modelMap?{modelMap:t.modelMap}:{},...t.passthrough?{passthrough:!0}:{},...t.relayOnly?{relayOnly:!0}:{},...t.codexResponsesToChat?{codexResponsesToChat:!0}:{}};return t.type==="pin_route"?(this.pinRoute(u,t.sessionId??""),!0):(this.setRoute(u),!0)}}export{h as GRIX_PROXY_ROUTE_KEY_HEADER,R as RuntimeRouteRegistry};
1
+ import{normalizeHost as i}from"./intercept-list.js";const h="x-grix-proxy-route-key";function a(s){return Array.isArray(s)?s[0]?.trim()||null:s?.trim()||null}function d(s){return typeof s!="object"||s===null||Array.isArray(s)?!1:Object.values(s).every(e=>typeof e=="string")}function y(s){if(typeof s!="object"||s===null||Array.isArray(s))return!1;const e=s;return e.type!=="set_route"&&e.type!=="delete_route"&&e.type!=="pin_route"&&e.type!=="unpin_route"||typeof e.routeKey!="string"||e.routeKey.trim().length===0?!1:e.type==="unpin_route"?typeof e.sessionId=="string"&&e.sessionId.trim().length>0:e.type==="delete_route"?!0:typeof e.targetBaseUrl=="string"&&e.targetBaseUrl.trim().length>0&&(e.headers===void 0||d(e.headers))&&(e.model===void 0||typeof e.model=="string")&&(e.modelMap===void 0||d(e.modelMap))&&(e.passthrough===void 0||typeof e.passthrough=="boolean")&&(e.type==="set_route"||typeof e.sessionId=="string"&&e.sessionId.trim().length>0)}function f(s){const e=s.model?.trim();return{routeKey:s.routeKey.trim(),targetBaseUrl:s.targetBaseUrl.trim(),...s.headers?{headers:{...s.headers}}:{},...e?{model:e}:{},...s.modelMap?{modelMap:{...s.modelMap}}:{},...s.passthrough?{passthrough:!0}:{},...s.relayOnly?{relayOnly:!0}:{},...s.codexResponsesToChat?{codexResponsesToChat:!0}:{}}}class R{routes=new Map;pinnedRoutes=new Map;defaultRouteKey=null;hostDefaultRoutes=new Map;setDefaultRouteKey(e){this.defaultRouteKey=e?.trim()||null}getDefaultRouteKey(){return this.defaultRouteKey}setHostDefaultRoute(e,r){const t=i(e);if(!t)return;const o=r?.trim();o?this.hostDefaultRoutes.set(t,o):this.hostDefaultRoutes.delete(t)}getHostDefaultRoutes(){return Object.fromEntries(this.hostDefaultRoutes)}getUsableRouteKeyForHost(e){const r=this.hostDefaultRoutes.get(i(e));return r&&this.routes.has(r)?r:null}hasUsableHostRoute(e){return this.getUsableRouteKeyForHost(e)!==null}listRoutes(){return[...this.routes.values()].map(e=>({...e}))}setRoute(e){const r=f(e);this.routes.set(r.routeKey,r)}deleteRoute(e){this.routes.delete(e.trim())}pinRoute(e,r){const t=f(e);this.pinnedRoutes.set(this.buildPinnedRouteKey(t.routeKey,r),t)}unpinRoute(e,r){this.pinnedRoutes.delete(this.buildPinnedRouteKey(e,r))}resolveRequestRouteInfo(e,r){const t=a(e.headers[h]);if(!t){if(r){const u=this.hostDefaultRoutes.get(i(r));if(u){const l=this.routes.get(u);if(l)return{route:l,routeKey:u,pinned:!1}}}if(this.defaultRouteKey){const u=this.routes.get(this.defaultRouteKey);if(u)return{route:u,routeKey:this.defaultRouteKey,pinned:!1}}return null}const o=a(e.headers["x-claude-code-session-id"]);if(o){const u=this.pinnedRoutes.get(this.buildPinnedRouteKey(t,o));if(u)return{route:u,routeKey:t,sessionId:o,pinned:!0}}const n=this.routes.get(t);return n?{route:n,routeKey:t,...o?{sessionId:o}:{},pinned:!1}:null}resolveRequestRoute(e){return this.resolveRequestRouteInfo(e)?.route??null}buildPinnedRouteKey(e,r){return`${e.trim()}:${r.trim()}`}handleControlLine(e){const r=e.trim();if(!r)return!1;const t=JSON.parse(r);if(!y(t))return!1;if(t.type==="delete_route")return this.deleteRoute(t.routeKey),!0;if(t.type==="unpin_route")return this.unpinRoute(t.routeKey,t.sessionId??""),!0;const o=t.targetBaseUrl;if(!o)return!1;const n={routeKey:t.routeKey,targetBaseUrl:o,...t.headers?{headers:t.headers}:{},...t.model?{model:t.model}:{},...t.modelMap?{modelMap:t.modelMap}:{},...t.passthrough?{passthrough:!0}:{},...t.relayOnly?{relayOnly:!0}:{},...t.codexResponsesToChat?{codexResponsesToChat:!0}:{}};return t.type==="pin_route"?(this.pinRoute(n,t.sessionId??""),!0):(this.setRoute(n),!0)}}export{h as GRIX_PROXY_ROUTE_KEY_HEADER,R as RuntimeRouteRegistry};
@@ -1 +1 @@
1
- const l="x-grix-proxy-target-base-url";function u(t){return Array.isArray(t)?t[0]:t}function d(t,r){const o=r.indexOf("/v1/");if(o>=0){const e=r.slice(o);return t?`${t}${e.slice(3)}`:`${t}${e}`}return r==="/v1"?`${t}/v1`:t&&(r===t||r.startsWith(`${t}/`))?r:`${t}${r}`}function m(t,r){const o=new URL(r),e=o.protocol==="https:"?"https:":"http:",p=e==="https:"?443:80,s=o.pathname.replace(/\/+$/,""),n=t?.startsWith("http://")||t?.startsWith("https://")?`${new URL(t).pathname}${new URL(t).search}`:t?.startsWith("/")?t:`/${t??""}`,a=d(s,n);return{target:{host:o.hostname,port:o.port?Number.parseInt(o.port,10):p,protocol:e},path:a}}function f(t,r){return{...m(t.url,r.targetBaseUrl),...r.headers?{headerOverrides:r.headers}:{},...r.model?{modelOverride:r.model}:{},...r.modelMap?{modelMapOverride:r.modelMap}:{},...r.passthrough?{passthrough:!0}:{}}}function g(t){return t.route?f(t.request,t.route):{target:t.fallbackTarget,path:t.request.url??"/"}}function $(t){const r=u(t.headers[l]);if(r&&t.url&&!t.url.startsWith("http://")&&!t.url.startsWith("https://")){const s=new URL(r),n=s.protocol==="https:"?"https:":"http:",a=n==="https:"?443:80,c=s.pathname.replace(/\/+$/,""),h=t.url.startsWith("/")?t.url:`/${t.url}`;return{target:{host:s.hostname,port:s.port?Number.parseInt(s.port,10):a,protocol:n},path:`${c}${h}`}}const o=new URL(t.url??""),e=o.protocol==="https:"?"https:":"http:",p=e==="https:"?443:80;return{target:{host:o.hostname,port:o.port?Number.parseInt(o.port,10):p,protocol:e},path:`${o.pathname}${o.search}`}}function x(t){const r=t.trim();if(!r)throw new Error("CONNECT authority is empty");if(r.startsWith("[")){const a=r.indexOf("]");if(a===-1)throw new Error(`Invalid CONNECT authority: ${t}`);const c=r.slice(1,a),h=r.slice(a+2),i=Number.parseInt(h,10);return{host:c,port:Number.isFinite(i)?i:443,protocol:"https:"}}const o=r.lastIndexOf(":"),e=o>0&&r.indexOf(":")===o,p=e?r.slice(0,o):r,s=e?r.slice(o+1):"443",n=Number.parseInt(s,10);return{host:p,port:Number.isFinite(n)?n:443,protocol:"https:"}}export{l as GRIX_PROXY_TARGET_BASE_URL_HEADER,x as parseConnectAuthority,$ as resolveAbsoluteRequestTarget,g as resolveMitmRequestTarget,f as resolveRuntimeRoutedRequestTarget};
1
+ const d="x-grix-proxy-target-base-url";function u(t){return Array.isArray(t)?t[0]:t}const l="/backend-api/codex/";function m(t,r){if(r.startsWith(l)){const e=r.slice(l.length);return`${t}/${e}`}const o=r.indexOf("/v1/");if(o>=0){const e=r.slice(o);return t?`${t}${e.slice(3)}`:`${t}${e}`}return r==="/v1"?`${t}/v1`:t&&(r===t||r.startsWith(`${t}/`))?r:`${t}${r}`}function f(t,r){const o=new URL(r),e=o.protocol==="https:"?"https:":"http:",a=e==="https:"?443:80,n=o.pathname.replace(/\/+$/,""),s=t?.startsWith("http://")||t?.startsWith("https://")?`${new URL(t).pathname}${new URL(t).search}`:t?.startsWith("/")?t:`/${t??""}`,p=m(n,s);return{target:{host:o.hostname,port:o.port?Number.parseInt(o.port,10):a,protocol:e},path:p}}function $(t,r){return{...f(t.url,r.targetBaseUrl),...r.headers?{headerOverrides:r.headers}:{},...r.model?{modelOverride:r.model}:{},...r.modelMap?{modelMapOverride:r.modelMap}:{},...r.passthrough?{passthrough:!0}:{}}}function g(t){return t.route?$(t.request,t.route):{target:t.fallbackTarget,path:t.request.url??"/"}}function x(t){const r=u(t.headers[d]);if(r&&t.url&&!t.url.startsWith("http://")&&!t.url.startsWith("https://")){const n=new URL(r),s=n.protocol==="https:"?"https:":"http:",p=s==="https:"?443:80,c=n.pathname.replace(/\/+$/,""),i=t.url.startsWith("/")?t.url:`/${t.url}`;return{target:{host:n.hostname,port:n.port?Number.parseInt(n.port,10):p,protocol:s},path:`${c}${i}`}}const o=new URL(t.url??""),e=o.protocol==="https:"?"https:":"http:",a=e==="https:"?443:80;return{target:{host:o.hostname,port:o.port?Number.parseInt(o.port,10):a,protocol:e},path:`${o.pathname}${o.search}`}}function R(t){const r=t.trim();if(!r)throw new Error("CONNECT authority is empty");if(r.startsWith("[")){const p=r.indexOf("]");if(p===-1)throw new Error(`Invalid CONNECT authority: ${t}`);const c=r.slice(1,p),i=r.slice(p+2),h=Number.parseInt(i,10);return{host:c,port:Number.isFinite(h)?h:443,protocol:"https:"}}const o=r.lastIndexOf(":"),e=o>0&&r.indexOf(":")===o,a=e?r.slice(0,o):r,n=e?r.slice(o+1):"443",s=Number.parseInt(n,10);return{host:a,port:Number.isFinite(s)?s:443,protocol:"https:"}}export{d as GRIX_PROXY_TARGET_BASE_URL_HEADER,R as parseConnectAuthority,x as resolveAbsoluteRequestTarget,g as resolveMitmRequestTarget,$ as resolveRuntimeRoutedRequestTarget};
@@ -1,10 +1,10 @@
1
- import{randomUUID as u}from"node:crypto";import g from"node:http";import H from"node:net";import _ from"node:tls";import{GRIX_PROXY_ROUTE_KEY_HEADER as E,parseConnectAuthority as S,resolveAbsoluteRequestTarget as P,resolveMitmRequestTarget as x,resolveRuntimeRoutedRequestTarget as R}from"../routing/index.js";import{forwardHttpRequest as f}from"../forwarding/index.js";import{CODEX_OPENAI_WIRE_COMPAT_HEADER as w,CODEX_OPENAI_WIRE_COMPAT_MODE as I}from"../compat/index.js";import{handleWebSocketUpgrade as C}from"./websocket-handler.js";import{logProxyRuntimeStderr as m}from"./logger.js";const y=1e4;function M(c){if(!c||!c.startsWith("http://")&&!c.startsWith("https://"))return null;try{return new URL(c).hostname}catch{return null}}function W(c){let t;try{t=new URL(c,"http://grix.internal").pathname}catch{t=c}return t.includes("/messages")||t.includes("/chat/completions")||t.includes("/responses")}function O(c){let t;try{t=new URL(c,"http://grix.internal").pathname}catch{t=c}return t.endsWith("/responses")}function v(c){return c?{routeSource:c.pinned?"pinned":"workspace",routeKey:c.routeKey,...c.sessionId?{routeSessionId:c.sessionId}:{},routeTargetBaseUrl:c.route.targetBaseUrl,...c.route.model?{routeModel:c.route.model}:{}}:{routeSource:"direct"}}class K{captureWriter;certificateAuthority;events;runtimeRoutes;interceptHosts;proxyServer;mitmHttpServer;mitmPlainHttpServer;constructor(t,r,a,n,i=null){this.captureWriter=t,this.certificateAuthority=r,this.events=a,this.runtimeRoutes=n,this.interceptHosts=i,this.proxyServer=g.createServer((o,e)=>{this.handlePlainHttpRequest(o,e)}),this.proxyServer.on("connect",(o,e,s)=>{this.handleConnectRequest(o,e,s)}),this.proxyServer.on("clientError",(o,e)=>{m("warn","proxy client error:",o.message),e.end(`HTTP/1.1 400 Bad Request\r
1
+ import{randomUUID as l}from"node:crypto";import g from"node:http";import H from"node:net";import _ from"node:tls";import{GRIX_PROXY_ROUTE_KEY_HEADER as S,parseConnectAuthority as P,resolveAbsoluteRequestTarget as R,resolveMitmRequestTarget as x,resolveRuntimeRoutedRequestTarget as E}from"../routing/index.js";import{forwardHttpRequest as f}from"../forwarding/index.js";import{CODEX_OPENAI_WIRE_COMPAT_HEADER as w,CODEX_OPENAI_WIRE_COMPAT_MODE as I}from"../compat/index.js";import{handleWebSocketUpgrade as C}from"./websocket-handler.js";import{logProxyRuntimeStderr as m}from"./logger.js";const y=1e4;function W(c){if(!c||!c.startsWith("http://")&&!c.startsWith("https://"))return null;try{return new URL(c).hostname}catch{return null}}function M(c){let t;try{t=new URL(c,"http://grix.internal").pathname}catch{t=c}return t.includes("/messages")||t.includes("/chat/completions")||t.includes("/responses")}function O(c){let t;try{t=new URL(c,"http://grix.internal").pathname}catch{t=c}return t.endsWith("/responses")}function v(c){return c?{routeSource:c.pinned?"pinned":"workspace",routeKey:c.routeKey,...c.sessionId?{routeSessionId:c.sessionId}:{},routeTargetBaseUrl:c.route.targetBaseUrl,...c.route.model?{routeModel:c.route.model}:{}}:{routeSource:"direct"}}class k{captureWriter;certificateAuthority;events;runtimeRoutes;interceptHosts;proxyServer;mitmHttpServer;mitmPlainHttpServer;constructor(t,r,a,n,i=null){this.captureWriter=t,this.certificateAuthority=r,this.events=a,this.runtimeRoutes=n,this.interceptHosts=i,this.proxyServer=g.createServer((o,e)=>{this.handlePlainHttpRequest(o,e)}),this.proxyServer.on("connect",(o,e,s)=>{this.handleConnectRequest(o,e,s)}),this.proxyServer.on("clientError",(o,e)=>{m("warn","proxy client error:",o.message),e.end(`HTTP/1.1 400 Bad Request\r
2
2
  \r
3
3
  `)}),this.mitmHttpServer=g.createServer((o,e)=>{this.handleMitmHttpRequest(o,e)}),this.mitmHttpServer.on("upgrade",(o,e,s)=>{this.handleMitmUpgrade(o,e,s)}),this.mitmHttpServer.on("clientError",(o,e)=>{m("warn","mitm client error:",o.message),e.end(`HTTP/1.1 400 Bad Request\r
4
4
  \r
5
5
  `)}),this.mitmPlainHttpServer=g.createServer((o,e)=>{this.handleMitmHttpRequest(o,e)}),this.mitmPlainHttpServer.on("upgrade",(o,e,s)=>{this.handleMitmUpgrade(o,e,s)}),this.mitmPlainHttpServer.on("clientError",(o,e)=>{m("warn","mitm plain client error:",o.message),e.end(`HTTP/1.1 400 Bad Request\r
6
6
  \r
7
- `)})}async start(t,r="0.0.0.0"){await new Promise((n,i)=>{this.proxyServer.once("error",i),this.proxyServer.listen(t??0,r,()=>{this.proxyServer.off("error",i),n()})});const a=this.proxyServer.address();if(!a||typeof a=="string")throw new Error("Proxy server failed to resolve listen port");return{port:a.port}}async stop(){this.mitmHttpServer.close(),this.mitmPlainHttpServer.close(),await new Promise(t=>{this.proxyServer.close(()=>t())})}async handlePlainHttpRequest(t,r){const a=u();let n=this.runtimeRoutes.resolveRequestRouteInfo(t);if(n&&this.interceptHosts&&!t.headers[E]){const l=M(t.url);l&&!this.interceptHosts.matches(l)&&(n=null)}if(!n&&t.url&&!t.url.startsWith("http://")&&!t.url.startsWith("https://")){r.writeHead(502,{"content-type":"text/plain"}),r.end("Direct HTTP requests without a matched route are not supported on the MITM port. Use the reverse proxy port instead.");return}const{target:i,path:o,headerOverrides:e,modelOverride:s,modelMapOverride:p,passthrough:h}=n?R(t,n.route):{...P(t),headerOverrides:void 0,modelOverride:void 0,modelMapOverride:void 0,passthrough:void 0};this.captureWriter.append({type:"http_request_start",requestId:a,connectionId:a,targetHost:i.host,targetPort:i.port,protocol:i.protocol,method:t.method??"GET",path:o,headers:t.headers}),await f({requestId:a,connectionId:a,target:i,path:o,headerOverrides:e,modelOverride:s,modelMapOverride:p,passthrough:h,request:t,response:r,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:v(n)})}async handleConnectRequest(t,r,a){const n=u();let i=t.url??"",o=443;try{const e=S(t.url??"");if(i=e.host,o=e.port,this.captureWriter.append({type:"connect_start",connectionId:n,targetHost:e.host,targetPort:e.port}),this.interceptHosts&&!this.interceptHosts.matches(e.host)){this.blindTunnel(r,e,a,n);return}r.write(`HTTP/1.1 200 Connection Established\r
7
+ `)})}async start(t,r="0.0.0.0"){await new Promise((n,i)=>{this.proxyServer.once("error",i),this.proxyServer.listen(t??0,r,()=>{this.proxyServer.off("error",i),n()})});const a=this.proxyServer.address();if(!a||typeof a=="string")throw new Error("Proxy server failed to resolve listen port");return{port:a.port}}async stop(){this.mitmHttpServer.close(),this.mitmPlainHttpServer.close(),await new Promise(t=>{this.proxyServer.close(()=>t())})}async handlePlainHttpRequest(t,r){const a=l();let n=this.runtimeRoutes.resolveRequestRouteInfo(t);if(n&&this.interceptHosts&&!t.headers[S]){const u=W(t.url);u&&!this.interceptHosts.matches(u)&&(n=null)}if(!n&&t.url&&!t.url.startsWith("http://")&&!t.url.startsWith("https://")){r.writeHead(502,{"content-type":"text/plain"}),r.end("Direct HTTP requests without a matched route are not supported on the MITM port. Use the reverse proxy port instead.");return}const{target:i,path:o,headerOverrides:e,modelOverride:s,modelMapOverride:p,passthrough:h}=n?E(t,n.route):{...R(t),headerOverrides:void 0,modelOverride:void 0,modelMapOverride:void 0,passthrough:void 0};this.captureWriter.append({type:"http_request_start",requestId:a,connectionId:a,targetHost:i.host,targetPort:i.port,protocol:i.protocol,method:t.method??"GET",path:o,headers:t.headers}),await f({requestId:a,connectionId:a,target:i,path:o,headerOverrides:e,modelOverride:s,modelMapOverride:p,passthrough:h,request:t,response:r,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:v(n)})}async handleConnectRequest(t,r,a){const n=l();let i=t.url??"",o=443;try{const e=P(t.url??"");if(i=e.host,o=e.port,this.captureWriter.append({type:"connect_start",connectionId:n,targetHost:e.host,targetPort:e.port}),this.interceptHosts&&!this.interceptHosts.matches(e.host)){this.blindTunnel(r,e,a,n);return}r.write(`HTTP/1.1 200 Connection Established\r
8
8
  \r
9
9
  `),a.length>0&&r.unshift(a);const s=h=>{this.captureWriter.append({type:"connect_error",connectionId:n,targetHost:e.host,error:h.message}),this.events.emitConnectError({requestId:n,host:e.host,port:e.port,error:h.message})};r.on("error",s);const p=()=>{const h=r.read(1);if(!h||h.length===0){r.once("readable",p);return}if(r.unshift(h),h[0]===22)this.startTlsMitm({socket:r,target:e,connectionId:n,reportConnectError:s});else{const d=r;d.__grixConnectionId=n,d.__grixProxyTarget={...e,protocol:"http:"},d.on("error",s),this.mitmPlainHttpServer.emit("connection",d)}};p()}catch(e){const s=e instanceof Error?e.message:String(e);this.captureWriter.append({type:"connect_error",connectionId:n,targetHost:i,error:s}),this.events.emitConnectError({requestId:n,host:i,port:o,error:s}),r.end(`HTTP/1.1 502 Bad Gateway\r
10
10
  \r
@@ -12,4 +12,6 @@ import{randomUUID as u}from"node:crypto";import g from"node:http";import H from"
12
12
  \r
13
13
  `),i.destroy())};s=setTimeout(()=>{p(new Error(`blind tunnel connect timeout after ${y}ms`))},y),i.once("connect",()=>{e=!0,clearTimeout(s),t.write(`HTTP/1.1 200 Connection Established\r
14
14
  \r
15
- `),a.length>0&&i.write(a),t.pipe(i),i.pipe(t)}),i.on("error",p),t.on("error",p)}async startTlsMitm(t){const{socket:r,target:a,connectionId:n,reportConnectError:i}=t;try{const o=await this.certificateAuthority.getLeafCertificate(a.host),e=new _.TLSSocket(r,{isServer:!0,secureContext:o.secureContext,ALPNProtocols:["http/1.1"]});e.__grixConnectionId=n,e.__grixProxyTarget=a,e.once("secure",()=>{this.mitmHttpServer.emit("connection",e)}),e.on("error",i)}catch(o){i(o instanceof Error?o:new Error(String(o))),r.destroy()}}async handleMitmHttpRequest(t,r){const a=t.socket,n=a.__grixProxyTarget,i=a.__grixConnectionId??u();if(!n){r.writeHead(502),r.end("Missing MITM target");return}const o=u(),e=this.runtimeRoutes.resolveRequestRouteInfo(t,n.host);if(!e){this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:i,targetHost:n.host,targetPort:n.port,protocol:n.protocol,method:t.method??"GET",path:t.url??"",headers:t.headers}),m("error",`intercepted ${n.host} but no relay route is configured; refusing to fall back to the real endpoint (that would silently use the agent's own account). Reconfigure Grix relay for this agent.`),t.resume(),r.writeHead(502,{"content-type":"text/plain"}),r.end("grix relay: intercepted host has no route; refusing silent direct connect");return}const{target:s,path:p,headerOverrides:h,modelOverride:l,modelMapOverride:d,passthrough:T}=x({request:t,fallbackTarget:n,route:e.route});if(e?.route.relayOnly&&!W(p)){this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:i,targetHost:s.host,targetPort:s.port,protocol:s.protocol,method:t.method??"GET",path:p,headers:t.headers}),t.resume(),r.writeHead(404,{"content-type":"text/plain"}),r.end("relay: non-inference endpoint blocked");return}e?.route.codexResponsesToChat&&(t.method??"GET").toUpperCase()==="POST"&&O(p)&&(t.headers[w]=I),this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:i,targetHost:s.host,targetPort:s.port,protocol:s.protocol,method:t.method??"GET",path:p,headers:t.headers}),await f({requestId:o,connectionId:i,target:s,path:p,headerOverrides:h,modelOverride:l,modelMapOverride:d,passthrough:T,request:t,response:r,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:v(e)})}handleMitmUpgrade(t,r,a){C(this.captureWriter,this.events,t,r,a)}}export{K as AcpTrafficProxyServer};
15
+ `),a.length>0&&i.write(a),t.pipe(i),i.pipe(t)}),i.on("error",p),t.on("error",p)}async startTlsMitm(t){const{socket:r,target:a,connectionId:n,reportConnectError:i}=t;try{const o=await this.certificateAuthority.getLeafCertificate(a.host),e=new _.TLSSocket(r,{isServer:!0,secureContext:o.secureContext,ALPNProtocols:["http/1.1"]});e.__grixConnectionId=n,e.__grixProxyTarget=a,e.once("secure",()=>{this.mitmHttpServer.emit("connection",e)}),e.on("error",i)}catch(o){i(o instanceof Error?o:new Error(String(o))),r.destroy()}}async handleMitmHttpRequest(t,r){const a=t.socket,n=a.__grixProxyTarget,i=a.__grixConnectionId??l();if(!n){r.writeHead(502),r.end("Missing MITM target");return}const o=l(),e=this.runtimeRoutes.resolveRequestRouteInfo(t,n.host);if(!e){this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:i,targetHost:n.host,targetPort:n.port,protocol:n.protocol,method:t.method??"GET",path:t.url??"",headers:t.headers}),m("error",`intercepted ${n.host} but no relay route is configured; refusing to fall back to the real endpoint (that would silently use the agent's own account). Reconfigure Grix relay for this agent.`),t.resume(),r.writeHead(502,{"content-type":"text/plain"}),r.end("grix relay: intercepted host has no route; refusing silent direct connect");return}const{target:s,path:p,headerOverrides:h,modelOverride:u,modelMapOverride:d,passthrough:T}=x({request:t,fallbackTarget:n,route:e.route});if(e?.route.relayOnly&&!M(p)){this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:i,targetHost:s.host,targetPort:s.port,protocol:s.protocol,method:t.method??"GET",path:p,headers:t.headers}),t.resume(),r.writeHead(404,{"content-type":"text/plain"}),r.end("relay: non-inference endpoint blocked");return}e?.route.codexResponsesToChat&&(t.method??"GET").toUpperCase()==="POST"&&O(p)&&(t.headers[w]=I),this.captureWriter.append({type:"http_request_start",requestId:o,connectionId:i,targetHost:s.host,targetPort:s.port,protocol:s.protocol,method:t.method??"GET",path:p,headers:t.headers}),await f({requestId:o,connectionId:i,target:s,path:p,headerOverrides:h,modelOverride:u,modelMapOverride:d,passthrough:T,request:t,response:r,captureWriter:this.captureWriter,events:this.events,routeDiagnostics:v(e)})}handleMitmUpgrade(t,r,a){const n=r,i=n.__grixProxyTarget;if((i?this.runtimeRoutes.resolveRequestRouteInfo(t,i.host):null)?.route.relayOnly){this.captureWriter.append({type:"websocket_upgrade_error",requestId:l(),connectionId:n.__grixConnectionId??l(),targetHost:i.host,error:"relay-only route: refusing direct WebSocket upgrade to origin"}),m("warn",`relay-only route active for ${i.host}; refusing WebSocket upgrade so the client falls back to HTTPS`),r.end(`HTTP/1.1 404 Not Found\r
16
+ \r
17
+ `);return}C(this.captureWriter,this.events,t,r,a)}}export{k as AcpTrafficProxyServer};
package/dist/grix.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import x from"node:path";import{writeFileSync as C}from"node:fs";import{Manager as V}from"./manager.js";import{ensureGrixDirs as X,initLogger as J,log as o,installProcessLogRotation as K,setConsoleOutput as q}from"./core/log/index.js";import{HealthServer as Y,bindPortOrFail as U}from"./core/runtime/index.js";import{writePidFile as z,removePidFile as S,readDaemonPid as Q}from"./core/runtime/index.js";import{resolveRuntimePaths as v}from"./core/config/index.js";import{ServiceManager as Z}from"./service/service-manager.js";import{killProcessesByCommandLine as ee,isWindowsElevated as te}from"./service/process-control.js";import{acquireDaemonLock as oe,isLockHolderSameProcess as re,readDaemonLock as ne,releaseDaemonLock as R}from"./runtime/daemon-lock.js";import{writeDaemonStatus as I,removeDaemonStatus as ae}from"./runtime/service-state.js";import{AdminServer as se,generateToken as ie,writeTokenFile as ce}from"./core/admin/index.js";import{initSentry as le,closeSentry as b,reportFatal as T}from"./core/observability/sentry.js";import{initProxyManager as de,getProxyManager as m,relayHostForClientType as pe}from"./core/proxy/index.js";import{stopProxyIfNobodyNeedsIt as me,disableAllRelayAndRestart as ge,setOneAgentRelayAndRestart as ue}from"./core/proxy/relay-orchestration.js";import{resolveClientVersion as fe}from"./core/util/client-version.js";import{parseCliArgs as he}from"./core/util/cli-args.js";const{command:u,flags:g,unknownFlags:F}=he(process.argv.slice(2));if(F.length>0&&(console.error(`Unknown option${F.length>1?"s":""}: ${F.map(t=>`--${t}`).join(", ")}
3
- Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),g.version&&(console.log(fe()),process.exit(0)),g.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
2
+ import E from"node:path";import{writeFileSync as N}from"node:fs";import{Manager as X}from"./manager.js";import{ensureGrixDirs as K,initLogger as J,log as n,installProcessLogRotation as q,setConsoleOutput as Y}from"./core/log/index.js";import{HealthServer as z,bindPortOrFail as H}from"./core/runtime/index.js";import{writePidFile as Q,removePidFile as A,readDaemonPid as Z}from"./core/runtime/index.js";import{resolveRuntimePaths as b}from"./core/config/index.js";import{ServiceManager as ee}from"./service/service-manager.js";import{killProcessesByCommandLine as te,isWindowsElevated as oe}from"./service/process-control.js";import{acquireDaemonLock as re,isLockHolderSameProcess as ne,readDaemonLock as ae,releaseDaemonLock as D}from"./runtime/daemon-lock.js";import{writeDaemonStatus as T,removeDaemonStatus as se}from"./runtime/service-state.js";import{AdminServer as ie,generateToken as ce,writeTokenFile as le}from"./core/admin/index.js";import{initSentry as de,closeSentry as $,reportFatal as F}from"./core/observability/sentry.js";import{initProxyManager as pe,getProxyManager as m,relayHostsForClientType as L}from"./core/proxy/index.js";import{stopProxyIfNobodyNeedsIt as me,disableAllRelayAndRestart as ge,setOneAgentRelayAndRestart as ue}from"./core/proxy/relay-orchestration.js";import{setAgentRelayCredential as fe}from"./core/proxy/relay-credential.js";import{resolveClientVersion as he}from"./core/util/client-version.js";import{parseCliArgs as ye}from"./core/util/cli-args.js";const{command:h,flags:u,unknownFlags:O}=ye(process.argv.slice(2));if(O.length>0&&(console.error(`Unknown option${O.length>1?"s":""}: ${O.map(t=>`--${t}`).join(", ")}
3
+ Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),u.version&&(console.log(he()),process.exit(0)),u.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)),u==="reload"){const t=Q();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 d=ne(v().daemonLockFile);d&&d.pid===t&&!re(d)&&(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(d){console.error(`reload failed: ${d instanceof Error?d.message:d}`),process.exit(1)}}const N=["start","stop","restart","status"];if(u&&N.includes(u)){process.platform==="win32"&&["start","stop","restart"].includes(u)&&!te()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
37
+ `),process.exit(0)),h==="reload"){const t=Z();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=ae(b().daemonLockFile);p&&p.pid===t&&!ne(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 _=["start","stop","restart","status"];if(h&&_.includes(h)){process.platform==="win32"&&["start","stop","restart"].includes(h)&&!oe()&&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=v(),d=g["config-dir"]??(g.profile?x.join(t.configDir,g.profile):void 0),f=x.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),s=new Z({cliPath:f,nodePath:process.execPath});try{let l;switch(u){case"start":(await s.status({rootDir:t.rootDir})).installed?l=await s.start({rootDir:t.rootDir}):l=await s.install({rootDir:t.rootDir,configDir:d});break;case"stop":l=await s.stop({rootDir:t.rootDir});break;case"restart":(await s.status({rootDir:t.rootDir})).installed?l=await s.restart({rootDir:t.rootDir}):l=await s.install({rootDir:t.rootDir,configDir:d});break;case"status":l=await s.status({rootDir:t.rootDir});break}console.log(JSON.stringify(l,null,2)),process.exit(0)}catch(l){console.error(`${u} failed: ${l instanceof Error?l.message:l}`),process.exit(1)}}else u&&(console.error(`Unknown command: ${u}
40
- Valid commands: ${N.join(", ")}`),process.exit(1));const i=v(),ye=g["config-dir"]??(g.profile?`${i.configDir}/${g.profile}`:void 0),n=new V,D=new Y,H=ie(),h=new se(H);let $=!1;async function L(t){process.stderr.write(t.message+`
41
- `),o.error("main",t.message.replace(/\n/g," \u2014 ")),await I(i.daemonStatusFile,{state:"failed",pid:process.pid,updated_at:Date.now(),reason:`port_bind_${t.kind}:${t.label}:${t.port}`}).catch(()=>{}),await b(),R(i.daemonLockFile).catch(()=>{}),S(),process.exit(1)}async function A(t){if($)return;$=!0,o.info("main",`Received ${t}, shutting down...`),D.markShuttingDown();const d=setTimeout(()=>{o.error("main","Shutdown timed out, forcing exit"),R(i.daemonLockFile).catch(()=>{}),S(),process.exit(2)},1e4);try{await n.stop(),await m()?.stop().catch(()=>{}),await h.stop(),await D.stop(),await b(),await R(i.daemonLockFile),await ae(i.daemonStatusFile).catch(()=>{}),clearTimeout(d),S(),o.info("main","Shutdown complete"),process.exit(0)}catch(f){o.error("main",`Shutdown error: ${f}`),R(i.daemonLockFile).catch(()=>{}),S(),process.exit(2)}}async function we(){X(),J(),await le(),K(i.stdoutLogFile,i.stderrLogFile),q(!1),process.platform==="win32"&&await ee("GrixConnectorDaemon",{platform:"win32"});try{await oe(i.daemonLockFile,i.rootDir)}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}z(),o.info("main",`grix-connector starting (PID ${process.pid})`),await I(i.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const t=parseInt(g["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await U({label:"health",port:t,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:c=>D.start(c)});e&&await L(e)}const d=x.join(i.dataDir,"health-port");C(d,String(t),"utf-8"),process.on("SIGINT",()=>A("SIGINT")),process.on("SIGTERM",()=>A("SIGTERM")),process.on("SIGHUP",()=>{$||(o.info("main","Received SIGHUP, reloading config..."),n.reload().then(e=>o.info("main",`reload done: ${JSON.stringify(e)}`)).catch(e=>o.error("main",`reload failed: ${e instanceof Error?e.message:e}`)))});let f="",s=0,l;process.on("uncaughtException",e=>{const c=e instanceof Error?e.stack??e.message:String(e);c===f?(s++,(s<=3||s%100===0)&&o.error("main",`Uncaught exception (x${s}): ${c}`)):(s>3&&o.error("main",`Previous exception repeated ${s} times total`),f=c,s=1,o.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),l||(l=setTimeout(()=>{s>3&&o.error("main",`Previous exception repeated ${s} times total`),f="",s=0,l=void 0},1e4).unref())),!_(e)&&(T(e,"uncaughtException"),A("uncaughtException"))}),process.on("unhandledRejection",e=>{o.error("main",`Unhandled rejection: ${e}`),!_(e)&&(T(e,"unhandledRejection"),A("unhandledRejection"))});const w=de(i.dataDir);await w.clearRuntimeState();const P=w.getRelayAgents();if(P.length>0)try{await w.startIfAnyRelayAgent(),o.info("main",`mitm proxy started for relay agents: ${P.join(", ")} (per-agent; others stay direct)`)}catch(e){o.error("main",`mitm proxy FAILED to start (relay agents: ${P.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 o.info("main",'mitm proxy idle (no agent has Grix relay enabled; enable: PUT /api/proxy/agents/<name>/enabled {"enabled":true})');D.setStatusProvider(()=>n.getAgentsStatus()),await n.start(ye);const O=parseInt(g["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);if(h.setAgentHandler({list:()=>n.getAgentsStatus(),add:e=>n.addAgent(e),remove:e=>n.removeAgent(e),restart:e=>n.restartAgent(e),reload:()=>n.reload()}),h.setUpgradeHandler({check:()=>n.checkUpgrade(),trigger:()=>n.triggerUpgrade()}),h.setProbeHandler({probeAll:e=>n.probeAll(e),probeOne:(e,c)=>n.probeOne(e,c)}),h.setInstallHandler({listInstallable:()=>n.listInstallable(),installAgent:e=>n.installAgent(e),getInstallProgress:e=>n.getInstallProgress(e)}),m()){const e=async()=>{const r=m();r&&await me(r,n)&&o.info("main","no agent uses Grix relay anymore; mitm proxy stopped")};n.setRelayEnvSettledHandler(()=>{e().catch(r=>{o.warn("main",`failed to stop idle mitm proxy: ${r}`)})});const c=()=>{const r=m(),a=r.getRelayAgents(),p=r.getDegradedRelayAgents();return{enabled:a.length>0,relayAgents:a,staleRelayAgents:n.getAgentsWithStaleRelayEnv(),degradedRelayAgents:p,runtime:r.getRuntimeInfo(),config:r.getConfigSnapshot()}};h.setProxyHandler({status:()=>c(),setAgentRelay:async(r,a)=>{const p=m();let y;if(a){const k=n.getAgentsStatus().find(W=>W.name===r);if(!k)throw Object.assign(new Error(`Agent "${r}" not found`),{code:"NOT_FOUND"});if(y=pe(k.clientType),!y)throw Object.assign(new Error(`client type "${k.clientType??""}" does not support Grix relay`),{code:"UNSUPPORTED_CLIENT_TYPE"})}const{restarted:M,busy:B}=await ue(p,n,{info:E=>o.info("main",E),warn:E=>o.warn("main",E)},{agentName:r,enabled:a,relayHost:y});return{...c(),restarted:M,busy:B}},disableAll:async()=>{const r=m();return await ge(r,n,{info:a=>o.info("main",a),warn:a=>o.warn("main",a)}),c()},setRoute:async(r,a)=>{const p=a,y=m();return y.setRoute({routeKey:r,targetBaseUrl:p.targetBaseUrl,...p.headers?{headers:p.headers}:{},...p.model?{model:p.model}:{},...p.modelMap?{modelMap:p.modelMap}:{},...p.passthrough?{passthrough:!0}:{},...p.relayOnly?{relayOnly:!0}:{},...p.codexResponsesToChat?{codexResponsesToChat:!0}:{}}),await y.persistConfig(),c()},deleteRoute:async r=>{const a=m();a.deleteRoute(r),await a.persistConfig()},setDefaultRoute:async r=>{const a=m();return a.setDefaultRouteKey(r),await a.persistConfig(),c()},setInterceptHosts:async r=>{const a=m();return a.setInterceptHosts(r),await a.persistConfig(),c()}})}{const e=await U({label:"admin",port:O,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:c=>h.start(c)});e&&await L(e)}const G=x.join(i.dataDir,"admin-token"),j=x.join(i.dataDir,"admin-port");ce(G,H),C(j,String(O),"utf-8"),await I(i.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),o.info("main","grix-connector ready")}we().catch(async t=>{o.error("main",`Fatal: ${t}`),T(t,"startup"),await b(),R(i.daemonLockFile).catch(()=>{}),S(),process.exit(1)});const xe=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function _(t){return t instanceof Error&&"code"in t?xe.has(t.code):!1}
39
+ right-click the terminal and select "Run as administrator".`);const t=b(),p=u["config-dir"]??(u.profile?E.join(t.configDir,u.profile):void 0),y=E.resolve(process.argv[1]||`${t.rootDir}/dist/grix.js`),c=new ee({cliPath:y,nodePath:process.execPath});try{let d;switch(h){case"start":(await c.status({rootDir:t.rootDir})).installed?d=await c.start({rootDir:t.rootDir}):d=await c.install({rootDir:t.rootDir,configDir:p});break;case"stop":d=await c.stop({rootDir:t.rootDir});break;case"restart":(await c.status({rootDir:t.rootDir})).installed?d=await c.restart({rootDir:t.rootDir}):d=await c.install({rootDir:t.rootDir,configDir:p});break;case"status":d=await c.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: ${_.join(", ")}`),process.exit(1));const l=b(),we=u["config-dir"]??(u.profile?`${l.configDir}/${u.profile}`:void 0),a=new X,P=new z,G=ce(),w=new ie(G);let U=!1;async function j(t){process.stderr.write(t.message+`
41
+ `),n.error("main",t.message.replace(/\n/g," \u2014 ")),await T(l.daemonStatusFile,{state:"failed",pid:process.pid,updated_at:Date.now(),reason:`port_bind_${t.kind}:${t.label}:${t.port}`}).catch(()=>{}),await $(),D(l.daemonLockFile).catch(()=>{}),A(),process.exit(1)}async function k(t){if(U)return;U=!0,n.info("main",`Received ${t}, shutting down...`),P.markShuttingDown();const p=setTimeout(()=>{n.error("main","Shutdown timed out, forcing exit"),D(l.daemonLockFile).catch(()=>{}),A(),process.exit(2)},1e4);try{await a.stop(),await m()?.stop().catch(()=>{}),await w.stop(),await P.stop(),await $(),await D(l.daemonLockFile),await se(l.daemonStatusFile).catch(()=>{}),clearTimeout(p),A(),n.info("main","Shutdown complete"),process.exit(0)}catch(y){n.error("main",`Shutdown error: ${y}`),D(l.daemonLockFile).catch(()=>{}),A(),process.exit(2)}}async function Se(){K(),J(),await de(),q(l.stdoutLogFile,l.stderrLogFile),Y(!1),process.platform==="win32"&&await te("GrixConnectorDaemon",{platform:"win32"});try{await re(l.daemonLockFile,l.rootDir)}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}Q(),n.info("main",`grix-connector starting (PID ${process.pid})`),await T(l.daemonStatusFile,{state:"starting",pid:process.pid,updated_at:Date.now()});const t=parseInt(u["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await H({label:"health",port:t,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:s=>P.start(s)});e&&await j(e)}const p=E.join(l.dataDir,"health-port");N(p,String(t),"utf-8"),process.on("SIGINT",()=>k("SIGINT")),process.on("SIGTERM",()=>k("SIGTERM")),process.on("SIGHUP",()=>{U||(n.info("main","Received SIGHUP, reloading config..."),a.reload().then(e=>n.info("main",`reload done: ${JSON.stringify(e)}`)).catch(e=>n.error("main",`reload failed: ${e instanceof Error?e.message:e}`)))});let y="",c=0,d;process.on("uncaughtException",e=>{const s=e instanceof Error?e.stack??e.message:String(e);s===y?(c++,(c<=3||c%100===0)&&n.error("main",`Uncaught exception (x${c}): ${s}`)):(c>3&&n.error("main",`Previous exception repeated ${c} times total`),y=s,c=1,n.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),d||(d=setTimeout(()=>{c>3&&n.error("main",`Previous exception repeated ${c} times total`),y="",c=0,d=void 0},1e4).unref())),!M(e)&&(F(e,"uncaughtException"),k("uncaughtException"))}),process.on("unhandledRejection",e=>{n.error("main",`Unhandled rejection: ${e}`),!M(e)&&(F(e,"unhandledRejection"),k("unhandledRejection"))});const S=pe(l.dataDir);await S.clearRuntimeState();const x=S.getRelayAgents();if(x.length>0)try{await S.startIfAnyRelayAgent(),n.info("main",`mitm proxy started for relay agents: ${x.join(", ")} (per-agent; others stay direct)`)}catch(e){n.error("main",`mitm proxy FAILED to start (relay agents: ${x.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 n.info("main",'mitm proxy idle (no agent has Grix relay enabled; enable: PUT /api/proxy/agents/<name>/enabled {"enabled":true})');if(P.setStatusProvider(()=>a.getAgentsStatus()),await a.start(we),x.length>0){const e=a.getAgentsStatus();for(const s of x){const o=e.find(i=>i.name===s);if(!o)continue;const r=L(o.clientType);r.length!==0&&await S.setAgentRelayEnabled(s,!0,{relayHosts:r}).catch(i=>{n.warn("main",`failed to reconcile relay hosts for "${s}": ${i}`)})}}const C=parseInt(u["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);if(w.setAgentHandler({list:()=>a.getAgentsStatus(),add:e=>a.addAgent(e),remove:e=>a.removeAgent(e),restart:e=>a.restartAgent(e),reload:()=>a.reload()}),w.setUpgradeHandler({check:()=>a.checkUpgrade(),trigger:()=>a.triggerUpgrade()}),w.setProbeHandler({probeAll:e=>a.probeAll(e),probeOne:(e,s)=>a.probeOne(e,s)}),w.setInstallHandler({listInstallable:()=>a.listInstallable(),installAgent:e=>a.installAgent(e),getInstallProgress:e=>a.getInstallProgress(e)}),m()){const e=async()=>{const o=m();o&&await me(o,a)&&n.info("main","no agent uses Grix relay anymore; mitm proxy stopped")};a.setRelayEnvSettledHandler(()=>{e().catch(o=>{n.warn("main",`failed to stop idle mitm proxy: ${o}`)})});const s=()=>{const o=m(),r=o.getRelayAgents(),i=o.getDegradedRelayAgents();return{enabled:r.length>0,relayAgents:r,staleRelayAgents:a.getAgentsWithStaleRelayEnv(),degradedRelayAgents:i,runtime:o.getRuntimeInfo(),config:o.getConfigSnapshot()}};w.setProxyHandler({status:()=>s(),setAgentRelay:async(o,r)=>{const i=m();let f;if(r){const R=a.getAgentsStatus().find(V=>V.name===o);if(!R)throw Object.assign(new Error(`Agent "${o}" not found`),{code:"NOT_FOUND"});if(f=L(R.clientType),f.length===0)throw Object.assign(new Error(`client type "${R.clientType??""}" does not support Grix relay`),{code:"UNSUPPORTED_CLIENT_TYPE"})}const{restarted:v,busy:I}=await ue(i,a,{info:g=>n.info("main",g),warn:g=>n.warn("main",g)},{agentName:o,enabled:r,relayHosts:f});return{...s(),restarted:v,busy:I}},setAgentRelayCredential:async(o,r)=>{const i=m(),f=a.getAgentsStatus(),{restarted:v,busy:I}=await fe(i,a,g=>f.find(R=>R.name===g),{info:g=>n.info("main",g),warn:g=>n.warn("main",g)},{localName:o,agentId:r.agentId,virtualKey:r.virtualKey,anthropicBaseUrl:r.anthropicBaseUrl,openaiBaseUrl:r.openaiBaseUrl});return{...s(),restarted:v,busy:I}},disableAll:async()=>{const o=m();return await ge(o,a,{info:r=>n.info("main",r),warn:r=>n.warn("main",r)}),s()},setRoute:async(o,r)=>{const i=r,f=m();return f.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 f.persistConfig(),s()},deleteRoute:async o=>{const r=m();r.deleteRoute(o),await r.persistConfig()},setDefaultRoute:async o=>{const r=m();return r.setDefaultRouteKey(o),await r.persistConfig(),s()},setInterceptHosts:async o=>{const r=m();return r.setInterceptHosts(o),await r.persistConfig(),s()}})}{const e=await H({label:"admin",port:C,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:s=>w.start(s)});e&&await j(e)}const B=E.join(l.dataDir,"admin-token"),W=E.join(l.dataDir,"admin-port");le(B,G),N(W,String(C),"utf-8"),await T(l.daemonStatusFile,{state:"running",pid:process.pid,updated_at:Date.now()}),process.send&&process.send("ready"),n.info("main","grix-connector ready")}Se().catch(async t=>{n.error("main",`Fatal: ${t}`),F(t,"startup"),await $(),D(l.daemonLockFile).catch(()=>{}),A(),process.exit(1)});const xe=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function M(t){return t instanceof Error&&"code"in t?xe.has(t.code):!1}
package/dist/log.js CHANGED
@@ -1,3 +1,3 @@
1
- import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as t}from"node:path";import{homedir as m}from"node:os";const i=t(m(),".grix"),s={base:i,config:t(i,"config"),log:t(i,"log"),data:t(i,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=t(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...n){const e=`${c()} [${o}] ${r}${n.length?" "+n.map(String).join(" "):""}`;console.log(e),a?.write(e+`
2
- `)},error(o,r,...n){const e=`${c()} [${o}] ERROR ${r}${n.length?" "+n.map(String).join(" "):""}`;console.error(e),a?.write(e+`
1
+ import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as i}from"node:path";import{homedir as m}from"node:os";const n=i(m(),".grix"),s={base:n,config:i(n,"config"),log:i(n,"log"),data:i(n,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=i(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...t){const e=`${c()} [${o}] ${r}${t.length?" "+t.map(String).join(" "):""}`;console.log(e),a?.write(e+`
2
+ `)},error(o,r,...t){const e=`${c()} [${o}] ERROR ${r}${t.length?" "+t.map(String).join(" "):""}`;console.error(e),a?.write(e+`
3
3
  `)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
@@ -1 +1 @@
1
- import*as i from"node:net";const e={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function n(o){const u={bind:o?.bind??e.bind,port:o?.port??e.port,endpoint:o?.endpoint??e.endpoint,sessionTimeoutMs:o?.sessionTimeoutMs??e.sessionTimeoutMs,invokeTimeoutMs:o?.invokeTimeoutMs??e.invokeTimeoutMs,allowedOrigins:o?.allowedOrigins,allowedHosts:o?.allowedHosts};return s(u.bind),u.port!==0&&t(u.port),r(u.sessionTimeoutMs),u}function s(o){if(!o||!i.isIPv4(o)&&!i.isIPv6(o))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${o}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function t(o){if(!Number.isInteger(o)||o<1||o>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(o){if(!Number.isInteger(o)||o<1e3||o>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${o} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{n as createDefaultGatewayConfig};
1
+ import*as n from"node:net";const i={bind:"127.0.0.1",port:0,endpoint:"/mcp",sessionTimeoutMs:18e5,invokeTimeoutMs:3e4};function s(u){const e={bind:u?.bind??i.bind,port:u?.port??i.port,endpoint:u?.endpoint??i.endpoint,sessionTimeoutMs:u?.sessionTimeoutMs??i.sessionTimeoutMs,invokeTimeoutMs:u?.invokeTimeoutMs??i.invokeTimeoutMs,allowedOrigins:u?.allowedOrigins,allowedHosts:u?.allowedHosts};return t(e.bind),e.port!==0&&o(e.port),r(e.sessionTimeoutMs),e}function t(u){if(!u||!n.isIPv4(u)&&!n.isIPv6(u))throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: bind \u5730\u5740 "${u}" \u4E0D\u662F\u5408\u6CD5\u7684 IPv4 \u6216 IPv6 \u5730\u5740`)}function o(u){if(!Number.isInteger(u)||u<1||u>65535)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: port \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1-65535 \u5185\u6216\u4E0D\u662F\u6574\u6570`)}function r(u){if(!Number.isInteger(u)||u<1e3||u>864e5)throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: session_timeout_ms \u503C ${u} \u4E0D\u5728\u5408\u6CD5\u8303\u56F4 1000-86400000 \u5185`)}export{s as createDefaultGatewayConfig};