eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
1 lines • 4.37 kB
JavaScript
import{createEveSessionStreamRoutePath}from"#protocol/routes.js";import{EVE_STREAM_TAIL_INDEX_HEADER}from"#protocol/message.js";import{isStreamDisconnectError,readNdjsonStream}from"#client/ndjson.js";import{readMessageStreamVersion}from"#client/stream-version.js";import{ClientError}from"#client/client-error.js";import{createClientUrl}from"#client/url.js";const DEFAULT_STREAM_RECONNECT_POLICY={retryableErrorStatuses:new Set([404,409,425,500,502,503,504]),streamIdleReconnectPolicy:{baseDelayMs:250,maxAttempts:5,maxDelayMs:4e3},streamOpenReconnectPolicy:{baseDelayMs:250,maxAttempts:12,maxDelayMs:5e3}},NO_STREAM_RECONNECT_POLICY={...DEFAULT_STREAM_RECONNECT_POLICY,streamIdleReconnectPolicy:{...DEFAULT_STREAM_RECONNECT_POLICY.streamIdleReconnectPolicy,maxAttempts:0},streamOpenReconnectPolicy:{...DEFAULT_STREAM_RECONNECT_POLICY.streamOpenReconnectPolicy,maxAttempts:1}};function resolveRetryPolicy(e,t){return{...t,...e}}function resolveStreamReconnectPolicy(e){if(e&&`reconnect`in e&&e.reconnect===!1)return NO_STREAM_RECONNECT_POLICY;let t=e;return{retryableErrorStatuses:t?.retryableErrorStatuses?new Set(t.retryableErrorStatuses):DEFAULT_STREAM_RECONNECT_POLICY.retryableErrorStatuses,streamIdleReconnectPolicy:resolveRetryPolicy(t?.streamIdleReconnectPolicy,DEFAULT_STREAM_RECONNECT_POLICY.streamIdleReconnectPolicy),streamOpenReconnectPolicy:resolveRetryPolicy(t?.streamOpenReconnectPolicy,DEFAULT_STREAM_RECONNECT_POLICY.streamOpenReconnectPolicy)}}async function*followStreamIterable(e){if(e.follow===!1&&e.startIndex<0)throw Error(`stream({ follow: false }) requires a nonnegative startIndex; a tail-relative cursor cannot be bounded.`);let t=resolveStreamReconnectPolicy(e.streamReconnectPolicy),n=t.streamIdleReconnectPolicy,r=e.startIndex,i=n.baseDelayMs,a=0,o=!0,s;for(;;){let c;try{c=await openStreamBody({...e,retryPolicy:t,startIndex:r,requestTailIndex:e.follow===!1&&s===void 0})}catch(t){if(e.signal?.aborted)return;throw t}if(e.follow===!1&&s===void 0&&(s=c.tailIndex,s===void 0))throw c.close(),Error(`stream({ follow: false }) requires the server to report the ${EVE_STREAM_TAIL_INDEX_HEADER} header. The agent may be running an older eve version.`);if(s!==void 0&&r>s){c.close();return}let l=!1;try{for await(let t of readNdjsonStream(c.body,{idleTimeoutMs:e.streamReadIdleTimeoutMs??15e3,streamVersion:c.streamVersion}))if(r+=1,l=!0,i=n.baseDelayMs,a=0,yield t,s!==void 0&&r>s)return}catch(e){if(!isStreamDisconnectError(e))throw e}finally{c.close()}if(e.signal?.aborted||e.startIndex<0||n.maxAttempts===0||e.keepAlive!==!0&&!l&&!o&&(a+=1)>=n.maxAttempts||(o=!1,await sleep(i,e.signal),e.signal?.aborted))return;i=Math.min(i*2,n.maxDelayMs)}}async function openStreamBody(e){let t=e.retryPolicy??DEFAULT_STREAM_RECONNECT_POLICY,n=t.streamOpenReconnectPolicy,r,i,a,o=n.baseDelayMs,s={};e.startIndex!==0&&(s.startIndex=String(e.startIndex)),e.requestTailIndex===!0&&(s.includeTailIndex=`1`);for(let c=0;c<n.maxAttempts;c+=1){let l=createClientUrl(e.host,createEveSessionStreamRoutePath(e.sessionId),Object.keys(s).length>0?s:void 0),u=await e.resolveHeaders(),d=new AbortController,f=e.signal?AbortSignal.any([e.signal,d.signal]):d.signal,p;try{p=await fetch(l,{cache:`no-store`,headers:u,redirect:e.redirect,signal:f})}catch(t){if(e.signal?.aborted||!isStreamDisconnectError(t)||c===n.maxAttempts-1)throw t;await sleep(o,e.signal),o=Math.min(o*2,n.maxDelayMs);continue}if(p.ok){if(!p.body)throw new ClientError(p.status,`Response body is null.`,p.headers);let e=!1;return{body:p.body,close:()=>{e||(e=!0,p.body?.cancel().catch(()=>{}),d.abort())},streamVersion:readMessageStreamVersion(p.headers),tailIndex:parseTailIndexHeader(p.headers)}}if(r=p.status,i=await p.text(),a=p.headers,!t.retryableErrorStatuses.has(p.status))throw new ClientError(p.status,i,p.headers);c<n.maxAttempts-1&&(await sleep(o,e.signal),o=Math.min(o*2,n.maxDelayMs))}throw new ClientError(r??0,i??`Failed to open message stream.`,a)}function parseTailIndexHeader(e){let t=e.get(EVE_STREAM_TAIL_INDEX_HEADER);if(t===null||!/^-?\d+$/.test(t))return;let n=Number(t);return Number.isSafeInteger(n)?n:void 0}async function sleep(e,t){t?.aborted||await new Promise(n=>{let onAbort=()=>{clearTimeout(r),n()},r=setTimeout(()=>{t?.removeEventListener(`abort`,onAbort),n()},e);t?.addEventListener(`abort`,onAbort,{once:!0})})}export{followStreamIterable,openStreamBody,sleep};