eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
1 lines • 8.1 kB
JavaScript
import{mt as e}from"./dist-DjECvUyK.js";import{t}from"./debug-log-CVPstO1D.js";import{E as n,I as r,J as i,T as a,U as o,a as s,at as c,d as l,g as u,nt as d,r as f,u as p,v as m,w as h}from"./frames-D_Q-7A1N.js";import{n as g,t as _}from"./wrapper-CKui_eTd.js";var v=c();g();var y=class extends Error{constructor(e,t){super(e,{cause:t?.cause}),this.name=`WsTransportError`}};function b(e){for(let[t,n]of Object.entries(e))if(t.toLowerCase()===`authorization`)return n;return null}async function x(e){let t=(async function*(){yield e})();for await(let e of f(t))return e;throw Error(`ws-transport: received an empty/unframed message`)}function S(e){if(e.byteLength===0)return`(no message)`;let t=new TextDecoder().decode(e);try{let e=JSON.parse(t);return typeof e.message==`string`?e.message:t}catch{return t}}function C(e){return e instanceof Error?e.message:String(e)}var w=class{wsUrl;getHeaders;connection=null;connecting=null;reconnectAttempts=0;reconnectTimer=null;openCount=0;closed=!1;lastDrainReason=null;needsFreshToken=!1;lastAuthorization=null;constructor(e,t){this.wsUrl=e,this.getHeaders=t}async request(e){if(this.closed)throw new y(`workflow-server events WS channel for ${this.wsUrl} is closed`);let t=await this.ensureConnected(),n=t.nextReqId++,r=e(n),i=u(),a;try{return await new Promise((e,o)=>{t.pending.set(n,{resolve:e,reject:o}),a=setTimeout(()=>{t.pending.delete(n)&&(o(new y(`workflow-server events WS request ${n} to ${this.wsUrl} timed out after ${i}ms with no reply`)),this.failConnection(t,`workflow-server events WS connection to ${this.wsUrl} abandoned after request ${n} timed out`))},i),a.unref?.(),t.ws.send(r,e=>{e&&t.pending.delete(n)&&o(new y(`workflow-server events WS send failed: ${C(e)}`,{cause:e}))})})}finally{a!==void 0&&clearTimeout(a)}}open(){this.closed||(this.openCount++,this.connection===null&&this.connecting===null&&this.ensureConnected().catch(()=>{this.close(`connect failed`)}))}release(e){this.openCount!==0&&(this.openCount--,this.openCount===0&&this.close(e))}close(e){if(this.closed)return;this.closed=!0,this.openCount=0,this.reconnectTimer!==null&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),T.transports.get(this.wsUrl)===this&&T.transports.delete(this.wsUrl);let t=this.connection;this.connection=null,t?.ws.close(1e3,e)}ensureConnected(){let e=this.connection;return e&&e.ws.readyState===_.default.OPEN?Promise.resolve(e):(this.connecting??=this.connectWithSpan().finally(()=>{this.connecting=null}),this.connecting)}async resolveUpgradeHeaders(){let e=this.needsFreshToken,t=await this.getHeaders({forceRefresh:e}),n=b(t);if(e&&n===this.lastAuthorization)throw new y(`workflow-server events WS connection to ${this.wsUrl} drained for auth expiry, but re-resolving the bearer produced the same token. Not reconnecting with a token the server has already rejected; the next event write will retry.`);this.lastAuthorization=n,this.needsFreshToken=!1;let r=new Headers;return await i(r),r.forEach((e,n)=>{t[n]=e}),t}consumeDrainReason(){let e=this.lastDrainReason;this.lastDrainReason=null,e===`auth_expiry`&&(this.needsFreshToken=!0)}connectWithSpan(){return h({method:`GET`,url:this.wsUrl,spanName:`workflow.events.ws.connect`,attributes:{...r(`ws`),...n(`websocket`),...o(this.reconnectAttempts)}},async e=>{try{return await this.connect()}catch(t){throw e?.setAttributes({...a(`TRANSPORT`)}),t instanceof Error&&e?.recordException?.(t),t}})}connect(){return new Promise((e,t)=>{(async()=>{let n;try{let e=await this.resolveUpgradeHeaders(),t=new _.default(this.wsUrl,{headers:e});t.binaryType=`nodebuffer`,n={ws:t,nextReqId:1,pending:new Map}}catch(e){console.error(`world-vercel: ws events transport could not open a connection to ${this.wsUrl}: ${C(e)}`),t(e instanceof y?e:new y(`workflow-server events WS connection to ${this.wsUrl} could not be opened: ${C(e)}`,{cause:e}));return}let{ws:r}=n,i=!1;r.on(`open`,()=>{if(i=!0,this.closed){r.close(1e3,`released while connecting`),t(new y(`workflow-server events WS connection to ${this.wsUrl} was released while connecting`));return}this.connection=n,this.reconnectAttempts=0,e(n)}),r.on(`message`,e=>{this.handleMessage(n,new Uint8Array(e))}),r.on(`error`,e=>{if(console.error(`world-vercel: ws events transport socket error (${this.wsUrl}): ${C(e)}`),!i){t(e);return}r.close()}),r.on(`close`,e=>{this.connection===n&&(this.connection=null),this.consumeDrainReason(),i||t(new y(`workflow-server events WS connection to ${this.wsUrl} closed before opening (code ${e})`)),this.failAllPending(n,new y(`workflow-server events WS connection closed (code ${e})`)),(i||this.reconnectAttempts>0)&&this.scheduleReconnect(e)})})()})}scheduleReconnect(e){if(this.closed||this.connection!==null||this.reconnectTimer!==null)return;if(this.reconnectAttempts>=5){console.error(`world-vercel: ws events transport giving up eager reconnect to ${this.wsUrl} after 5 attempts (last close code ${e}); the next event write will retry.`);return}let t=Math.min(100*2**this.reconnectAttempts,5e3);this.reconnectAttempts++;let n=setTimeout(()=>{this.reconnectTimer=null,this.ensureConnected().catch(()=>{})},t);n.unref?.(),this.reconnectTimer=n}async handleMessage(e,n){let r;try{r=await x(n)}catch(t){let r=`could not decode a ${n.byteLength}-byte reply frame from ${this.wsUrl}: ${C(t)}`;console.error(`world-vercel: ws events transport ${r}`),this.failConnection(e,`workflow-server events WS transport ${r}`);return}if(r.meta.type===`drain`){let e=r.meta.reason===`auth_expiry`?`auth_expiry`:`max_duration`;this.lastDrainReason=e,t(`world-vercel: ws events transport received a drain notice (reason: ${e}, graceMs: ${r.meta.graceMs??`unspecified`}); connection will close soon.`);return}let i=r.meta.reqId;if(i===-1){let t=`received a malformed-frame error from ${this.wsUrl} (reserved reqId -1, status ${String(r.meta.status??`unknown`)}): ${S(r.body)}. The server could not parse a frame this client sent, so the originating write cannot be identified`;console.error(`world-vercel: ws events transport ${t}.`),this.failConnection(e,`workflow-server events WS transport ${t}`);return}if(typeof i!=`number`){let t=`received a reply frame with no numeric reqId from ${this.wsUrl} (type: ${String(r.meta.type??`absent`)})`;console.error(`world-vercel: ws events transport ${t}.`),this.failConnection(e,`workflow-server events WS transport ${t}`);return}let a=e.pending.get(i);if(!a){console.error(`world-vercel: ws events transport received a reply for unknown reqId ${i} from ${this.wsUrl} (already settled, or the request was failed by a send error or its deadline); dropping it.`);return}e.pending.delete(i),a.resolve({meta:r.meta,body:r.body})}failAllPending(e,t){for(let n of e.pending.values())n.reject(t);e.pending.clear()}failConnection(e,t){this.failAllPending(e,new y(t)),this.connection===e&&(this.connection=null),e.ws.close()}};const T=e(`@workflow/world-vercel//wsEventsTransports@${d}`,1,()=>({transports:new Map,loggedWsProxyFallback:!1,loggedWsInUse:!1}));function E(e,t){let n=T.transports.get(e);return n||(n=new w(e,t),T.transports.set(e,n)),n}function D(e,t){let n=new URL(e);return n.protocol=n.protocol===`https:`?`wss:`:`ws:`,n.pathname=`${n.pathname.replace(/\/$/,``)}/websockets/v1/runs/${encodeURIComponent(t)}`,n.toString()}function O(e,n){if(!s())return;let r=A(e,n);if(!r)return;T.loggedWsInUse||(T.loggedWsInUse=!0,t(`world-vercel: using ws events transport (${r}).`));let i=E(r,async({forceRefresh:e})=>{e&&await k();let{headers:t}=await p(n);return m(t)});i.open();let a=!1;return()=>{a||(a=!0,i.release(`invocation complete`))}}async function k(){try{await(0,v.getVercelOidcToken)({expirationBufferMs:864e5})}catch{}}function A(e,n){let{baseUrl:r,usingProxy:i}=l(n);return i?(T.loggedWsProxyFallback||(T.loggedWsProxyFallback=!0,t(`world-vercel: ws events transport unavailable for a World with projectConfig (api-workflow proxy, resolved baseUrl: ${r}) — falling back to HTTP.`)),null):D(r,e)}function j(e,t){let n=A(e,t);if(!n)return null;let r=T.transports.get(n);return r?{transport:r,wsUrl:n}:null}export{O as openWsChannel,j as resolveWsTransport};