@messenger-agent/claude-agent 0.30.0-alpha.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{createRequire as f}from"node:module";import u from"ws";import{appConfig as p}from"./config.js";import{ChatBodySchema as y}from"./schemas.js";import{cancelClaudeConversation as C,ChatRequestError as m,handleClaudeChatStream as b}from"./routes/chat.js";import{logger as l}from"@messenger-agent/shared/logger";import{reconnectDelayWithJitter as g}from"@messenger-agent/shared/reconnect-backoff";import{sendHttpTunnelResponse as w}from"@messenger-agent/shared/tunnel-http";import{CODING_AGENT_TUNNEL_DUPLICATE_CLOSE_CODE as T,CODING_AGENT_TUNNEL_PROTOCOL_VERSION as v}from"@messenger-agent/shared/tunnel-protocol";const _=f(import.meta.url)("../package.json").version;function k(r){return new Promise(e=>setTimeout(e,r))}function I(r){try{const e=JSON.parse(r.toString());return!e||typeof e!="object"||!("type"in e)?void 0:e}catch{return}}function O(r){const e=new Headers;for(const[t,c]of Object.entries(r??{}))e.set(t,c);return e}function q(r){if(r.method==="POST")try{const e=new URL(r.path,"http://coding-agent-tunnel.local").pathname,t=/^\/v1\/chat\/([^/]+)\/cancel$/.exec(e);return t?decodeURIComponent(t[1]):void 0}catch{return}}class E{config;stopped=!1;socket;running=new Map;constructor(e){this.config=e}async start(){if(!this.config.enabled)return;if(!this.config.serverUrl||!this.config.tunnelId||!this.config.token)throw new Error("Tunnel config requires server_url, tunnel_id, and token when enabled");let e=this.config.reconnectInitialMs;for(;!this.stopped;){try{await this.connectOnce(),e=this.config.reconnectInitialMs}catch(t){if(this.stopped)break;l.warn("[TunnelClient] websocket tunnel connection failed:",t)}this.stopped||(await k(g(e)),e=Math.min(e*2,this.config.reconnectMaxMs))}}stop(){this.stopped=!0;for(const e of this.running.values())e.abortController.abort();this.running.clear(),this.socket?.close()}connectOnce(){return new Promise((e,t)=>{const c={Authorization:`Bearer ${this.config.token}`},o=new u(this.config.serverUrl,{headers:c});this.socket=o;let n,s=!1;const d=()=>{n&&clearInterval(n),this.socket===o&&(this.socket=void 0);for(const a of this.running.values())a.abortController.abort(new Error("Tunnel connection closed"));this.running.clear()};o.on("open",()=>{this.send({type:"hello",protocolVersion:v,tunnelId:this.config.tunnelId,agentType:"claude",clientVersion:_}),n=setInterval(()=>{this.send({type:"heartbeat"})},this.config.heartbeatIntervalMs),l.info(`[TunnelClient] connected to ${this.config.serverUrl} as ${this.config.tunnelId}`)}),o.on("message",a=>{const i=I(a);if(!i){l.warn("[TunnelClient] received invalid websocket message");return}if(i.type!=="heartbeat"){if(i.type==="cancel"){this.running.get(i.id)?.abortController.abort();return}i.type==="request"&&this.handleRequest(i).catch(h=>{l.error(`[TunnelClient] request ${i.id} failed:`,h)})}}),o.once("error",a=>{s||(s=!0,d(),t(a))}),o.once("close",(a,i)=>{d(),a===T&&(this.stopped=!0,l.warn(`[TunnelClient] duplicate websocket tunnel connection rejected; reconnect disabled: ${i.toString()}`)),s||(s=!0,e())})})}send(e){const t=this.socket;!t||t.readyState!==u.OPEN||t.send(JSON.stringify(e))}async handleRequest(e){const t=q(e);if(t){const n=C(t),s=Response.json(n?{success:!0}:{error:{type:"not_found_error",code:"conversation_not_found",message:`Conversation not found or already completed: ${t}`,param:"conversation_id"}},{status:n?200:404});await w(e.id,s,d=>this.send(d));return}if(e.method!=="POST"||e.path!=="/v1/chat/stream"){this.send({type:"response.error",id:e.id,code:"unsupported_request",errorText:`Unsupported tunnel request: ${e.method} ${e.path}`});return}const c=y.safeParse(e.body);if(!c.success){this.send({type:"response.error",id:e.id,code:"invalid_request",errorText:`Invalid chat request body: ${c.error.message}`});return}const o=new AbortController;this.running.set(e.id,{abortController:o}),this.send({type:"response.start",id:e.id,mode:"sse"});try{await b({body:c.data,headers:O(e.headers),signal:o.signal},{writeData:async n=>this.send({type:"response.chunk",id:e.id,data:n}),writeDone:async()=>this.send({type:"response.end",id:e.id})})}catch(n){const s=n instanceof Error?n.message:"Tunnel request failed",d=n instanceof m?`http_${n.status}`:"request_failed";this.send({type:"response.error",id:e.id,code:d,errorText:s})}finally{this.running.delete(e.id)}}}function L(r=p.tunnel){if(!r.enabled)return;const e=new E(r);return e.start().catch(t=>{l.error("[TunnelClient] stopped unexpectedly:",t)}),e}export{E as ClaudeTunnelClient,L as startClaudeTunnelClient};
1
+ import{createRequire as p}from"node:module";import u from"ws";import{appConfig as y}from"./config.js";import{ChatBodySchema as C}from"./schemas.js";import{cancelClaudeConversation as g,ChatRequestError as m,handleClaudeChatStream as b}from"./routes/chat.js";import{logger as l}from"@messenger-agent/shared/logger";import{reconnectDelayWithJitter as w}from"@messenger-agent/shared/reconnect-backoff";import{sendHttpTunnelResponse as T}from"@messenger-agent/shared/tunnel-http";import{CODING_AGENT_TUNNEL_DUPLICATE_CLOSE_CODE as h,CODING_AGENT_TUNNEL_PROTOCOL_VERSION as v}from"@messenger-agent/shared/tunnel-protocol";const k=p(import.meta.url)("../package.json").version;function _(r){return new Promise(e=>setTimeout(e,r))}function I(r){try{const e=JSON.parse(r.toString());return!e||typeof e!="object"||!("type"in e)?void 0:e}catch{return}}function O(r){const e=new Headers;for(const[t,a]of Object.entries(r??{}))e.set(t,a);return e}function q(r){if(r.method==="POST")try{const e=new URL(r.path,"http://coding-agent-tunnel.local").pathname,t=/^\/v1\/chat\/([^/]+)\/cancel$/.exec(e);return t?decodeURIComponent(t[1]):void 0}catch{return}}class E{config;stopped=!1;socket;running=new Map;constructor(e){this.config=e}async start(){if(!this.config.enabled)return;if(!this.config.serverUrl||!this.config.tunnelId||!this.config.token)throw new Error("Tunnel config requires server_url, tunnel_id, and token when enabled");let e=this.config.reconnectInitialMs;for(;!this.stopped;){try{e=await this.connectOnce()==="duplicate"?this.config.reconnectMaxMs:this.config.reconnectInitialMs}catch(t){if(this.stopped)break;l.warn("[TunnelClient] websocket tunnel connection failed:",t)}this.stopped||(await _(w(e)),e=Math.min(e*2,this.config.reconnectMaxMs))}}stop(){this.stopped=!0;for(const e of this.running.values())e.abortController.abort();this.running.clear(),this.socket?.close()}connectOnce(){return new Promise((e,t)=>{const a={Authorization:`Bearer ${this.config.token}`},o=new u(this.config.serverUrl,{headers:a});this.socket=o;let n,s=!1;const d=()=>{n&&clearInterval(n),this.socket===o&&(this.socket=void 0);for(const c of this.running.values())c.abortController.abort(new Error("Tunnel connection closed"));this.running.clear()};o.on("open",()=>{this.send({type:"hello",protocolVersion:v,tunnelId:this.config.tunnelId,agentType:"claude",clientVersion:k}),n=setInterval(()=>{this.send({type:"heartbeat"})},this.config.heartbeatIntervalMs),l.info(`[TunnelClient] connected to ${this.config.serverUrl} as ${this.config.tunnelId}`)}),o.on("message",c=>{const i=I(c);if(!i){l.warn("[TunnelClient] received invalid websocket message");return}if(i.type!=="heartbeat"){if(i.type==="cancel"){this.running.get(i.id)?.abortController.abort();return}i.type==="request"&&this.handleRequest(i).catch(f=>{l.error(`[TunnelClient] request ${i.id} failed:`,f)})}}),o.once("error",c=>{s||(s=!0,d(),t(c))}),o.once("close",(c,i)=>{d(),c===h&&l.warn(`[TunnelClient] duplicate websocket tunnel connection rejected; retrying with backoff: ${i.toString()}`),s||(s=!0,e(c===h?"duplicate":"closed"))})})}send(e){const t=this.socket;!t||t.readyState!==u.OPEN||t.send(JSON.stringify(e))}async handleRequest(e){const t=q(e);if(t){const n=g(t),s=Response.json(n?{success:!0}:{error:{type:"not_found_error",code:"conversation_not_found",message:`Conversation not found or already completed: ${t}`,param:"conversation_id"}},{status:n?200:404});await T(e.id,s,d=>this.send(d));return}if(e.method!=="POST"||e.path!=="/v1/chat/stream"){this.send({type:"response.error",id:e.id,code:"unsupported_request",errorText:`Unsupported tunnel request: ${e.method} ${e.path}`});return}const a=C.safeParse(e.body);if(!a.success){this.send({type:"response.error",id:e.id,code:"invalid_request",errorText:`Invalid chat request body: ${a.error.message}`});return}const o=new AbortController;this.running.set(e.id,{abortController:o}),this.send({type:"response.start",id:e.id,mode:"sse"});try{await b({body:a.data,headers:O(e.headers),signal:o.signal},{writeData:async n=>this.send({type:"response.chunk",id:e.id,data:n}),writeDone:async()=>this.send({type:"response.end",id:e.id})})}catch(n){const s=n instanceof Error?n.message:"Tunnel request failed",d=n instanceof m?`http_${n.status}`:"request_failed";this.send({type:"response.error",id:e.id,code:d,errorText:s})}finally{this.running.delete(e.id)}}}function L(r=y.tunnel){if(!r.enabled)return;const e=new E(r);return e.start().catch(t=>{l.error("[TunnelClient] stopped unexpectedly:",t)}),e}export{E as ClaudeTunnelClient,L as startClaudeTunnelClient};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@messenger-agent/claude-agent",
3
- "version": "0.30.0-alpha.0",
3
+ "version": "0.31.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -19,7 +19,7 @@
19
19
  "ws": "^8.21.0",
20
20
  "yaml": "^2.9.0",
21
21
  "zod": "^4.4.3",
22
- "@messenger-agent/shared": "0.30.0-alpha.0"
22
+ "@messenger-agent/shared": "0.31.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/ws": "^8.18.1"