@roll-agent/client-node 0.5.2 → 0.6.1
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.
- package/README.md +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/runtime-event-recovery.d.ts +3 -3
- package/dist/runtime-event-recovery.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ try {
|
|
|
74
74
|
## Runtime→Client Request
|
|
75
75
|
|
|
76
76
|
- `"1.4"` / `"1.3"` / `"1.2"` 没有强制 Handler;使用默认帧预算时,即使没有 Handler 也会广告
|
|
77
|
-
`["1.4","1.3","1.2","1.0"]`,并以 revision 1 ACK 空能力集合;只有 `userInput.request` 时
|
|
77
|
+
`["1.5", "1.4","1.3","1.2","1.0"]`,并以 revision 1 ACK 空能力集合;只有 `userInput.request` 时
|
|
78
78
|
仍保持该集合,存在 `approval.request` 时才同时广告要求该 Handler 的 1.1;
|
|
79
79
|
- 显式把 `maxFrameBytes` 设为低于 `17 MiB` 时不会广告 `"1.4"` 与 `"1.3"`,因为该预算无法保证接收
|
|
80
80
|
最大 durable event frame;`"1.2"` / `"1.1"` / `"1.0"` 的回落规则保持不变;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Readable, Writable } from "node:stream";
|
|
2
2
|
import { RuntimeEventRecoveryManager, type RuntimeEventRecoveryManagerOptions } from "./runtime-event-recovery.ts";
|
|
3
|
-
import { RUNTIME_SERVER_REQUEST_METHODS, type InitializeResult, type JsonRpcId, type LatestRuntimeMethod, type RuntimeEventEnvelope, type LatestRuntimeMethodInput, type RuntimeMethodResultForVersion, type RuntimeProtocolErrorDataForVersion, type RuntimeProtocolVersion, type RuntimeServerRequestMethod, type RuntimeServerRequestParamsForSupportedVersions, type RuntimeServerRequestResultForSupportedVersions, type TurnId } from "@roll-agent/protocol";
|
|
3
|
+
import { RUNTIME_SERVER_REQUEST_METHODS, type InitializeResult, type JsonRpcId, type LatestRuntimeMethod, type RuntimeEventEnvelope, type LatestRuntimeMethodInput, type RuntimeMethodInputForVersion, type RuntimeMethodResultForVersion, type RuntimeProtocolErrorDataForVersion, type RuntimeProtocolVersion, type RuntimeServerRequestMethod, type RuntimeServerRequestParamsForSupportedVersions, type RuntimeServerRequestResultForSupportedVersions, type TurnId } from "@roll-agent/protocol";
|
|
4
4
|
export { DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_BYTES, DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_EVENTS, RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS, RuntimeEventRecoveryError, RuntimeEventRecoveryManager, type RuntimeDurableEventEnvelope, type RuntimeEphemeralEventEnvelope, type RuntimeEventRecoveryBridge, type RuntimeEventRecoveryCheckpoint, type RuntimeEventRecoveryErrorContext, type RuntimeEventRecoveryManagerOptions, type RuntimeEventRecoveryMode, type RuntimeEventRecoverySnapshot, type RuntimeEventRecoverySnapshotContext, type RuntimeEventRecoverySnapshotReason, type RuntimeEventRecoveryStartResult, type RuntimeEventRecoveryThreadOptions, } from "./runtime-event-recovery.ts";
|
|
5
5
|
export interface RuntimeClientTransport {
|
|
6
6
|
readonly stdin: Writable;
|
|
@@ -162,6 +162,7 @@ export declare class RollNodeClient {
|
|
|
162
162
|
registerServerRequestHandler<TMethod extends RuntimeServerRequestMethod>(method: TMethod, handler: RuntimeServerRequestHandler<TMethod>): () => void;
|
|
163
163
|
/** Registers the typed Runtime Protocol 1.3/1.2 userInput.request handler. */
|
|
164
164
|
onUserInputRequest(handler: UserInputRequestHandler): () => void;
|
|
165
|
+
getOperationResult(input: RuntimeMethodInputForVersion<"1.5", "operation.result.get">): Promise<RuntimeClientMethodResult<"operation.result.get">>;
|
|
165
166
|
private requestEventRecoverySnapshot;
|
|
166
167
|
private requestRuntimeEventsResume;
|
|
167
168
|
request<TMethod extends LatestRuntimeMethod>(method: TMethod, input: LatestRuntimeMethodInput<TMethod>): Promise<RuntimeClientMethodResult<TMethod>>;
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{spawn as e}from"node:child_process";import{once as t}from"node:events";import{createInterface as r}from"node:readline";import i from"../package.json"with{type:"json"};import{RuntimeEventRecoveryError as o,RuntimeEventRecoveryManager as n}from"./runtime-event-recovery.js";import{RUNTIME_EVENT_NOTIFICATION as s,RUNTIME_METHODS as l,RUNTIME_SERVER_REQUEST_CANCEL_NOTIFICATION as a,RUNTIME_SERVER_REQUEST_METHODS as u,RUNTIME_V13_MIN_CLIENT_FRAME_BYTES as c,SUPPORTED_RUNTIME_PROTOCOL_VERSIONS as d,getRuntimeProtocolCapabilities as h,getRuntimeProtocolRegistry as R,interactionIdSchema as m,isRuntimeServerRequestMethodAvailable as v,isLatestRuntimeServerRequestMethod as w,isRuntimeServerRequestMethodRequired as f,parseLatestRuntimeMethodParams as p,parseRuntimeMethodParamsForVersion as E,parseLatestRuntimeMethodResult as q,parseRuntimeMethodResultForVersion as g,parseRuntimeProtocolErrorDataForVersion as y,parseRuntimeServerRequestCancelParamsForVersion as S,parseRuntimeServerRequestParamsForVersion as x,parseRuntimeServerRequestResultForVersion as P,initializeResultSchema as T,runtimeEventEnvelopeSchema as b}from"@roll-agent/protocol";export{DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_BYTES,DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_EVENTS,RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS,RuntimeEventRecoveryError,RuntimeEventRecoveryManager}from"./runtime-event-recovery.js";const M=c,F=1,I=100,C=3e4,V=3e4,O=1e4,k=5e3;function N(e){if("object"!=typeof e||null===e||!("version"in e)||"string"!=typeof e.version||0===e.version.trim().length)throw new Error("@roll-agent/client-node package metadata is missing a valid version");return e.version}const $=N(i),B={methodNotFound:-32601,invalidParams:-32602,internalError:-32603},z=new Set([l.threadList,l.threadSnapshot,l.threadCapabilities,l.operationGet]);function U(e){return"1.4"===e||"1.3"===e||"1.2"===e}function j(e,t,r){Object.assign(e,{[t]:r})}function H(e){const t={...e.serverRequestHandlers},r=t[u.userInputRequest];if(void 0!==r&&void 0!==e.onUserInputRequest&&r!==e.onUserInputRequest)throw new Error('Provide userInput.request through either "onUserInputRequest" or "serverRequestHandlers", not both');return void 0!==e.onUserInputRequest&&(t[u.userInputRequest]=e.onUserInputRequest),t}function A(e,t){return h(e).requiredServerRequestMethods.every(e=>"function"==typeof t[e])}function L(e,t,r){if(U(e)){if(!v(e,t))throw new Error(`Runtime Protocol ${e} does not support server request ${t}`);return x(e,t,r)}if(!v(e,t))throw new Error(`Runtime Protocol ${e} does not support server request ${t}`);return x(e,t,r)}function _(e,t,r){return E(e,t,r)}function D(e,t,r){return g(e,t,r)}function J(e,t,r){if(U(e)){if(!v(e,t))throw new Error(`Runtime Protocol ${e} does not support server request ${t}`);return P(e,t,r)}if(!v(e,t))throw new Error(`Runtime Protocol ${e} does not support server request ${t}`);return P(e,t,r)}export class RollRpcError extends Error{code;data;constructor(e){super(e.message),this.name="RollRpcError",this.code=e.code,this.data=e.data}}export class RollRuntimeExitedError extends Error{code;signal;constructor(e,t){super(`Roll Runtime exited before the request completed (code=${String(e)}, signal=${String(t)})`),this.name="RollRuntimeExitedError",this.code=e,this.signal=t}}export class RollRequestTimeoutError extends Error{method;timeoutMs;constructor(e,t){super(`Roll Runtime request "${e}" timed out after ${String(t)} ms`),this.name="RollRequestTimeoutError",this.method=e,this.timeoutMs=t}}export class RollRequestFrameTooLargeError extends Error{frameBytes;maxFrameBytes;constructor(e,t){super(`Roll Runtime request frame is ${String(e)} bytes, exceeding the ${String(t)} byte limit`),this.name="RollRequestFrameTooLargeError",this.frameBytes=e,this.maxFrameBytes=t}}export class RollRuntimeClosingError extends Error{constructor(){super("Roll Runtime client is shutting down"),this.name="RollRuntimeClosingError"}}export class RollRuntimeShutdownTimeoutError extends Error{gracefulTimeoutMs;terminateTimeoutMs;forceKillTimeoutMs;constructor(e,t,r){super(`Roll Runtime did not exit within ${String(e+t+r)} ms`),this.name="RollRuntimeShutdownTimeoutError",this.gracefulTimeoutMs=e,this.terminateTimeoutMs=t,this.forceKillTimeoutMs=r}}export class RollProtocolViolationError extends Error{constructor(e,t){super(e,void 0===t?void 0:{cause:t}),this.name="RollProtocolViolationError"}}export class RollUncorrelatedRpcError extends RollRpcError{constructor(e){super(e),this.name="RollUncorrelatedRpcError"}}function K(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function G(e){if(!K(e))return;const t=m.safeParse(e.interactionId);return t.success?t.data:void 0}function Q(e){return"string"==typeof e||"number"==typeof e&&Number.isFinite(e)}function Y(e,t){if(!Number.isInteger(e)||e<=0)throw new Error(`${t} must be a positive integer`);return e}function X(e={}){return{gracefulTimeoutMs:Y(e.gracefulTimeoutMs??V,"gracefulTimeoutMs"),terminateTimeoutMs:Y(e.terminateTimeoutMs??O,"terminateTimeoutMs"),forceKillTimeoutMs:Y(e.forceKillTimeoutMs??k,"forceKillTimeoutMs")}}function W(e,t,r){if(!K(e)||"number"!=typeof e.code||!Number.isInteger(e.code)||"string"!=typeof e.message)throw new RollProtocolViolationError(`Roll Runtime returned an invalid error for ${t}`);let i;if(void 0!==e.data)try{i=y(r??"1.1",e.data)}catch(e){throw new RollProtocolViolationError(`Roll Runtime returned invalid error data for ${t}`,e)}return{code:e.code,message:e.message,...void 0===i?{}:{data:i}}}function Z(e,t){return new Promise((r,i)=>{const o=setTimeout(()=>r(void 0),t);e.then(e=>{clearTimeout(o),r(e)},e=>{clearTimeout(o),i(e instanceof Error?e:new Error(String(e)))})})}function ee(e){return{stdin:e.stdin,stdout:e.stdout,stderr:e.stderr,onExit(t){let r=!1;const i=(e,i)=>{r||(r=!0,t(e,i))};e.once("close",i),e.once("error",()=>i(null,null))},close(){e.stdin.end()},terminate(){null===e.exitCode&&null===e.signalCode&&e.kill("SIGTERM")},forceClose(){null===e.exitCode&&null===e.signalCode&&e.kill("SIGKILL")}}}export class RollNodeClient{transport;maxFrameBytes;outboundMaxFrameBytes;pending=new Map;listeners=new Set;exitListeners=new Set;activeTurns=new Set;unknownTurns=new Set;serverRequestHandlers;serverRequestHandlerCapabilityRevisions=new Map;advertisedProtocolVersions;inFlightServerRequests=new Map;inFlightServerRequestsByInteractionId=new Map;acknowledgedServerRequestMethods=new Set;onTurnOutcomeUnknown;requestTimeoutMs;maxReadRetries;readRetryDelayMs;defaultShutdownOptions;outputReader;stderrReader;requestId=0;capabilityRevision=0;acknowledgedCapabilityRevision=0;writeQueue=Promise.resolve();capabilitySyncTail=Promise.resolve();exitPromise;resolveExit;exited=!1;closing=!1;outputReaderClosed=!1;stderrReaderClosed=!1;initializationResult;connectionFailure;exitResult;shutdownPromise;eventRecoveryManager;constructor(e){if(this.transport=e.transport,this.maxFrameBytes=e.maxFrameBytes??M,this.outboundMaxFrameBytes=this.maxFrameBytes,this.onTurnOutcomeUnknown=e.onTurnOutcomeUnknown,this.requestTimeoutMs=e.requestTimeoutMs??C,this.maxReadRetries=e.maxReadRetries??1,this.readRetryDelayMs=e.readRetryDelayMs??I,this.defaultShutdownOptions=X(e.shutdownOptions),this.serverRequestHandlers=H(e),this.advertisedProtocolVersions=d.filter(e=>A(e,this.serverRequestHandlers)&&("1.3"!==e&&"1.4"!==e||this.maxFrameBytes>=c)),!Number.isInteger(this.maxFrameBytes)||this.maxFrameBytes<=0)throw new Error("maxFrameBytes must be a positive integer");if(!Number.isInteger(this.requestTimeoutMs)||this.requestTimeoutMs<=0)throw new Error("requestTimeoutMs must be a positive integer");if(!Number.isInteger(this.maxReadRetries)||this.maxReadRetries<0)throw new Error("maxReadRetries must be a non-negative integer");if(!Number.isInteger(this.readRetryDelayMs)||this.readRetryDelayMs<0)throw new Error("readRetryDelayMs must be a non-negative integer");this.exitPromise=new Promise(e=>{this.resolveExit=e}),this.outputReader=r({input:e.transport.stdout}),this.outputReader.on("line",e=>this.handleLine(e)),this.stderrReader=void 0===e.transport.stderr?void 0:r({input:e.transport.stderr}),this.stderrReader?.on("line",t=>{try{e.onStderr?.(t)}catch{}}),this.transport.onExit((e,t)=>this.handleExit(e,t))}static async start(t){if(0===t.cwd.trim().length)throw new Error("RollNodeClient requires an explicit non-empty cwd");const r=e(t.command??"roll",[...t.args??["runtime","serve","--stdio"]],{cwd:t.cwd,env:t.env??process.env,shell:!1,stdio:"pipe"});return await RollNodeClient.connect({transport:ee(r),...void 0!==t.clientName?{clientName:t.clientName}:{},...void 0!==t.clientVersion?{clientVersion:t.clientVersion}:{},...void 0!==t.onStderr?{onStderr:t.onStderr}:{},...void 0!==t.onTurnOutcomeUnknown?{onTurnOutcomeUnknown:t.onTurnOutcomeUnknown}:{},...void 0!==t.maxFrameBytes?{maxFrameBytes:t.maxFrameBytes}:{},...void 0!==t.requestTimeoutMs?{requestTimeoutMs:t.requestTimeoutMs}:{},...void 0!==t.maxReadRetries?{maxReadRetries:t.maxReadRetries}:{},...void 0!==t.readRetryDelayMs?{readRetryDelayMs:t.readRetryDelayMs}:{},...void 0!==t.shutdownOptions?{shutdownOptions:t.shutdownOptions}:{},...void 0!==t.serverRequestHandlers?{serverRequestHandlers:t.serverRequestHandlers}:{},...void 0!==t.onUserInputRequest?{onUserInputRequest:t.onUserInputRequest}:{}})}static async connect(e){let t;try{return t=new RollNodeClient(e),t.initializationResult=await t.request(l.initialize,{protocolVersions:[...t.advertisedProtocolVersions],client:{name:e.clientName??"@roll-agent/client-node",version:e.clientVersion??$}}),t.acceptInitializationResult(t.initializationResult),await t.initializeServerRequestCapabilities(),t}catch(r){throw void 0===t?e.transport.close():await t.shutdown().catch(()=>{}),r}}async initializeServerRequestCapabilities(){const e=this.getInitializationResult().protocolVersion;U(e)?await this.queueServerRequestCapabilitySync():this.acknowledgedServerRequestMethods=new Set(R(e).serverRequestMethods.filter(e=>void 0!==this.serverRequestHandlers[e]))}queueServerRequestCapabilitySync(){const e=this.getInitializationResult().protocolVersion;if(!U(e))return Promise.resolve();if(this.capabilityRevision>=Number.MAX_SAFE_INTEGER)return Promise.reject(new RollProtocolViolationError("Runtime Client capability revision was exhausted"));const t=this.capabilityRevision+1;this.capabilityRevision=t;const r=R(e).serverRequestMethods.filter(e=>void 0!==this.serverRequestHandlers[e]);for(const e of r)this.serverRequestHandlerCapabilityRevisions.has(e)||this.serverRequestHandlerCapabilityRevisions.set(e,t);const i=this.capabilitySyncTail.then(async()=>{await this.requestClientCapabilitiesSet(e,t,r)});return this.capabilitySyncTail=i,i}requestClientCapabilitiesSet(e,t,r){if(void 0!==this.connectionFailure)return Promise.reject(this.connectionFailure);if(this.exited)return Promise.reject(this.exitResult?.error??new RollRuntimeExitedError(null,null));if(this.closing)return Promise.reject(new RollRuntimeClosingError);const i=l.clientCapabilitiesSet,o=E(e,i,{revision:t,serverRequestMethods:[...r]});return this.requestOnce(i,o,t=>g(e,i,t),i=>{this.acceptClientCapabilitiesSetResult(e,t,r,i)})}acceptClientCapabilitiesSetResult(e,t,r,i){const o=g(e,l.clientCapabilitiesSet,i);if(o.revision!==t)throw new RollProtocolViolationError(`Roll Runtime acknowledged Client capability revision ${String(o.revision)}; expected ${String(t)}`);const n=new Set(r),s=new Set(o.acceptedServerRequestMethods);for(const e of s)if(!n.has(e))throw new RollProtocolViolationError(`Roll Runtime acknowledged Client capability method "${e}" that was not requested`);this.acknowledgedCapabilityRevision=t,this.acknowledgedServerRequestMethods=s}observeServerRequestCapabilitySync(e){e.catch(e=>{this.failConnection(e instanceof Error?e:new Error(String(e)))})}onEvent(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}createEventRecovery(e={}){if(void 0!==this.connectionFailure)throw this.connectionFailure;if(this.exited)throw this.exitResult?.error??new RollRuntimeExitedError(null,null);if(this.closing)throw new RollRuntimeClosingError;return void 0===this.eventRecoveryManager||this.eventRecoveryManager.isClosed()?(this.eventRecoveryManager=new n({getInitializationResult:()=>this.getInitializationResult(),requestSnapshot:e=>this.requestEventRecoverySnapshot(e),requestResume:(e,t)=>this.requestRuntimeEventsResume(e,t)},e),this.eventRecoveryManager):this.eventRecoveryManager}onExit(e){const t=this.exitResult;return void 0!==t?(queueMicrotask(()=>{try{e(t)}catch{}}),()=>{}):(this.exitListeners.add(e),()=>{this.exitListeners.delete(e)})}getInitializationResult(){if(void 0===this.initializationResult)throw new Error("Roll Runtime client has not completed initialization");return this.initializationResult}registerServerRequestHandler(e,t){if(void 0!==this.connectionFailure)throw this.connectionFailure;if(this.exited)throw this.exitResult?.error??new RollRuntimeExitedError(null,null);if(this.closing)throw new RollRuntimeClosingError;if(!w(e))throw new Error(`Unknown Runtime server request method: ${String(e)}`);const r=this.initializationResult?.protocolVersion;if(void 0!==r&&!h(r).serverRequests)throw new Error(`Cannot register a Runtime server request handler after Protocol ${r} was negotiated; pass serverRequestHandlers to RollNodeClient.connect() or RollNodeClient.start()`);const i=this.serverRequestHandlers[e];return j(this.serverRequestHandlers,e,t),void 0!==r&&U(r)&&void 0===i&&this.observeServerRequestCapabilitySync(this.queueServerRequestCapabilitySync()),()=>{if(this.serverRequestHandlers[e]!==t)return;const r=this.initializationResult?.protocolVersion;void 0!==r&&f(r,e)?this.failProtocol(new RollProtocolViolationError(`Cannot unregister required Runtime server request handler "${e}" while Protocol ${r} is active`)):(delete this.serverRequestHandlers[e],this.serverRequestHandlerCapabilityRevisions.delete(e),void 0!==r&&U(r)&&(this.retireServerRequestsForMethod(e,new Error(`Runtime Client capability "${e}" was withdrawn`)),this.observeServerRequestCapabilitySync(this.queueServerRequestCapabilitySync())))}}onUserInputRequest(e){return this.registerServerRequestHandler(u.userInputRequest,e)}requestEventRecoverySnapshot(e){if(void 0!==this.connectionFailure)return Promise.reject(this.connectionFailure);if(this.exited)return Promise.reject(this.exitResult?.error??new RollRuntimeExitedError(null,null));if(this.closing)return Promise.reject(new RollRuntimeClosingError);const t=this.getInitializationResult().protocolVersion;if("1.3"!==t&&"1.4"!==t)return this.request(l.threadSnapshot,{threadId:e,limit:1});const r=l.threadSnapshot,i=E(t,r,{threadId:e,limit:1,recovery:!0});return this.requestOnce(r,i,e=>g(t,r,e))}requestRuntimeEventsResume(e,t){if(void 0!==this.connectionFailure)return Promise.reject(this.connectionFailure);if(this.exited)return Promise.reject(this.exitResult?.error??new RollRuntimeExitedError(null,null));if(this.closing)return Promise.reject(new RollRuntimeClosingError);const r=this.getInitializationResult().protocolVersion;if("1.3"!==r&&"1.4"!==r)return Promise.reject(new o("runtime.events.resume requires negotiated Runtime Protocol 1.3 or newer"));const i=l.runtimeEventsResume,n=E("1.3",i,e);return this.requestOnce(i,n,e=>g("1.3",i,e),e=>{t(g("1.3",i,e))})}async request(e,t){if(void 0!==this.connectionFailure)throw this.connectionFailure;if(this.exited)throw this.exitResult?.error??new RollRuntimeExitedError(null,null);if(this.closing)throw new RollRuntimeClosingError;if(e===l.initialize&&void 0!==this.initializationResult)throw new Error("Roll Runtime client has already completed initialization");const r=this.initializationResult?.protocolVersion,i=void 0===r?p(e,t):_(r,e,t),o=e===l.turnStart?p(l.turnStart,i).turnId:void 0;void 0!==o&&this.activeTurns.add(o);let n=0;for(;;)try{return await this.requestOnce(e,i,t=>void 0===r?e===l.initialize?T.parse(t):q(e,t):D(r,e,t))}catch(t){if(!z.has(e)||!(t instanceof RollRpcError)||!0!==t.data?.retryable||n>=this.maxReadRetries)throw void 0!==o&&(t instanceof RollRpcError&&!(t instanceof RollUncorrelatedRpcError)||t instanceof RollRequestFrameTooLargeError?this.activeTurns.delete(o):this.markTurnOutcomeUnknown(o)),t;n+=1,await new Promise(e=>{setTimeout(e,this.readRetryDelayMs)})}}async requestOnce(e,t,r,i){const o=this.requestId;this.requestId+=1;const n=new Promise((t,r)=>{const n=setTimeout(()=>{this.pending.delete(o)&&r(new RollRequestTimeoutError(e,this.requestTimeoutMs))},this.requestTimeoutMs);this.pending.set(o,{method:e,resolve:t,reject:r,timer:n,...void 0===i?{}:{acceptResult:i}})});try{await this.write({jsonrpc:"2.0",id:o,method:e,params:t});const i=await n;try{return r(i)}catch(t){const r=new RollProtocolViolationError(`Roll Runtime returned an invalid result for "${e}"`,t);throw this.failProtocol(r),r}}catch(e){const t=this.pending.get(o);throw void 0!==t&&(clearTimeout(t.timer),this.pending.delete(o)),e}}getOutcomeUnknownTurnIds(){return[...this.unknownTurns]}close(){this.shutdown().catch(()=>{})}shutdown(e){const t=this.exitResult;return void 0!==t?Promise.resolve(t):(this.shutdownPromise??=this.performShutdown(void 0===e?this.defaultShutdownOptions:X(e)),this.shutdownPromise)}async performShutdown(e){const{gracefulTimeoutMs:t,terminateTimeoutMs:r,forceKillTimeoutMs:i}=e;this.closing=!0,this.abortServerRequests(new RollRuntimeClosingError),this.transport.close();const o=await Z(this.exitPromise,t);if(void 0!==o)return o;if(void 0!==this.transport.terminate){this.transport.terminate();const e=await Z(this.exitPromise,r);if(void 0!==e)return e}if(void 0===this.transport.forceClose){const e=new RollRuntimeShutdownTimeoutError(t,r,i);throw this.finishExit(e,null,null),this.closeReaders(),e}this.transport.forceClose();const n=await Z(this.exitPromise,i);if(void 0!==n)return n;const s=new RollRuntimeShutdownTimeoutError(t,r,i);throw this.finishExit(s,null,null),this.closeReaders(),s}async write(e,r){const i=JSON.stringify(e),o=Buffer.byteLength(i,"utf8");if(o>this.outboundMaxFrameBytes)throw new RollRequestFrameTooLargeError(o,this.outboundMaxFrameBytes);const n=`${i}\n`;this.writeQueue=this.writeQueue.then(async()=>{this.closing||void 0!==this.connectionFailure||this.exited||!1===r?.()||this.transport.stdin.write(n)||await t(this.transport.stdin,"drain")});try{await this.writeQueue}catch(e){const t=new RollProtocolViolationError("Failed to write to the Roll Runtime transport",e);throw this.failProtocol(t),t}}handleLine(e){if(Buffer.byteLength(e,"utf8")>this.maxFrameBytes)return void this.failProtocol(new RollProtocolViolationError(`Roll Runtime frame exceeds ${String(this.maxFrameBytes)} bytes`));let t;try{t=JSON.parse(e)}catch(e){return void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted invalid JSON",e))}if(!K(t)||"2.0"!==t.jsonrpc)return void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted an invalid JSON-RPC frame"));const r=Object.hasOwn(t,"method"),i=Object.hasOwn(t,"id"),o=Object.hasOwn(t,"result"),n=Object.hasOwn(t,"error");if(r)return"string"!=typeof t.method||o||n?void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted an invalid JSON-RPC request or notification")):i?Q(t.id)?void this.handleServerRequest(t.id,t.method,t.params):void this.failProtocol(new RollProtocolViolationError("Roll Runtime request has an invalid id")):void this.handleNotification(t.method,t.params);if(!i)return void this.failProtocol(new RollProtocolViolationError("Roll Runtime response is missing an id"));if(null===t.id){if(!n||o)return void this.failProtocol(new RollProtocolViolationError("Roll Runtime response with a null id must contain exactly one error"));try{this.failConnection(new RollUncorrelatedRpcError(W(t.error,"an uncorrelated request",this.initializationResult?.protocolVersion)))}catch(e){this.failProtocol(e instanceof RollProtocolViolationError?e:new RollProtocolViolationError("Roll Runtime returned an invalid uncorrelated error",e))}return}if(!Q(t.id))return void this.failProtocol(new RollProtocolViolationError("Roll Runtime response has an invalid id"));const s=this.pending.get(t.id);if(void 0!==s)if(n!==o){if(n){let e;try{e=new RollRpcError(W(t.error,`"${s.method}"`,this.initializationResult?.protocolVersion))}catch(e){return void this.failProtocol(e instanceof RollProtocolViolationError?e:new RollProtocolViolationError(`Roll Runtime returned an invalid error for "${s.method}"`,e))}return this.pending.delete(t.id),clearTimeout(s.timer),void s.reject(e)}if(s.method===l.initialize)try{this.acceptInitializationResult(T.parse(t.result))}catch(e){return void this.failProtocol(e instanceof RollProtocolViolationError?e:new RollProtocolViolationError('Roll Runtime returned an invalid result for "initialize"',e))}if(void 0!==s.acceptResult)try{s.acceptResult(t.result)}catch(e){return void this.failProtocol(e instanceof RollProtocolViolationError?e:new RollProtocolViolationError(`Roll Runtime returned an invalid result for "${s.method}"`,e))}this.pending.delete(t.id),clearTimeout(s.timer),s.resolve(t.result)}else this.failProtocol(new RollProtocolViolationError(`Roll Runtime response for "${s.method}" must contain exactly one result or error`))}handleNotification(e,t){if(e===s){const e=b.safeParse(t);return e.success?void 0===this.initializationResult||e.data.protocolVersion!==this.initializationResult.protocolVersion?void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted runtime.event outside the negotiated Protocol")):void this.handleEvent(e.data):void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted an invalid runtime.event",e.error))}if(e===a){const e=this.initializationResult?.protocolVersion;if(void 0===e||!h(e).serverRequests)return void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted runtime.serverRequest.cancel outside a negotiated server-request capability"));try{if(U(e)){const r=S(e,t),i=this.inFlightServerRequestsByInteractionId.get(r.interactionId);return void(void 0!==i&&this.retireServerRequest(i,new Error(r.reason)))}if("1.1"===e){const r=S(e,t),i=this.inFlightServerRequests.get(r.serverRequestId);return void(void 0!==i&&this.retireServerRequest(i,new Error(r.reason)))}}catch(e){return void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted an invalid runtime.serverRequest.cancel",e))}return void this.failProtocol(new RollProtocolViolationError(`Roll Runtime emitted runtime.serverRequest.cancel for unsupported Protocol ${e}`))}this.failProtocol(new RollProtocolViolationError(`Roll Runtime emitted an unknown notification: ${e}`))}handleServerRequest(e,t,r){if(this.closing||void 0!==this.connectionFailure||this.exited)return;if(this.inFlightServerRequests.has(e))return void this.failProtocol(new RollProtocolViolationError(`Roll Runtime reused in-flight server request id ${JSON.stringify(e)}`));const i=this.initializationResult?.protocolVersion;if(void 0===i||"1.0"===i||!h(i).serverRequests||!w(t)||!v(i,t))return void this.observeServerRequestResponse(this.writeServerRequestError(e,B.methodNotFound,"Method not found"));const o=this.serverRequestHandlers[t],n=this.serverRequestHandlerCapabilityRevisions.get(t);U(i)&&(!this.acknowledgedServerRequestMethods.has(t)||void 0===n||this.acknowledgedCapabilityRevision<n)||void 0===o?this.observeServerRequestResponse(this.writeServerRequestError(e,B.methodNotFound,"Method not found")):this.observeServerRequestResponse(this.dispatchServerRequest(i,e,t,r,o))}observeServerRequestResponse(e){e.catch(e=>{void 0!==this.connectionFailure||this.exited||this.failProtocol(new RollProtocolViolationError("Failed to respond to Runtime server request",e))})}async dispatchServerRequest(e,t,r,i,o){const n={controller:new AbortController,method:r,requestId:t};this.inFlightServerRequests.set(t,n);try{let s;try{s=L(e,r,i)}catch{return void await this.writeServerRequestError(t,B.invalidParams,"Invalid params",()=>this.isServerRequestActive(t,n))}const l=G(s);if(void 0!==l&&this.inFlightServerRequestsByInteractionId.has(l))return void this.failProtocol(new RollProtocolViolationError(`Roll Runtime reused in-flight interaction id ${JSON.stringify(l)}`));let a,u;void 0!==l&&(n.interactionId=l,this.inFlightServerRequestsByInteractionId.set(l,n),n.controller.signal.addEventListener("abort",()=>{this.inFlightServerRequestsByInteractionId.get(l)===n&&this.inFlightServerRequestsByInteractionId.delete(l)},{once:!0}));try{a=await o(s,{requestId:t,signal:n.controller.signal})}catch{return void(this.isServerRequestActive(t,n)&&await this.writeServerRequestError(t,B.internalError,"Internal error",()=>this.isServerRequestActive(t,n)))}if(!this.isServerRequestActive(t,n))return;try{u=J(e,r,a)}catch{return void await this.writeServerRequestError(t,B.internalError,"Internal error",()=>this.isServerRequestActive(t,n))}this.isServerRequestActive(t,n)&&await this.write({jsonrpc:"2.0",id:t,result:u},()=>this.isServerRequestActive(t,n))}finally{this.retireServerRequest(n)}}retireServerRequest(e,t){this.inFlightServerRequests.get(e.requestId)===e&&this.inFlightServerRequests.delete(e.requestId),void 0!==e.interactionId&&this.inFlightServerRequestsByInteractionId.get(e.interactionId)===e&&this.inFlightServerRequestsByInteractionId.delete(e.interactionId),void 0===t||e.controller.signal.aborted||e.controller.abort(t)}retireServerRequestsForMethod(e,t){for(const r of[...this.inFlightServerRequests.values()])r.method===e&&this.retireServerRequest(r,t)}isServerRequestActive(e,t){return!t.controller.signal.aborted&&this.inFlightServerRequests.get(e)===t}async writeServerRequestError(e,t,r,i){await this.write({jsonrpc:"2.0",id:e,error:{code:t,message:r}},i)}acceptInitializationResult(e){if(!this.advertisedProtocolVersions.includes(e.protocolVersion))throw new RollProtocolViolationError(`Roll Runtime selected unadvertised Protocol ${e.protocolVersion}`);this.initializationResult=e,this.outboundMaxFrameBytes=Math.min(this.maxFrameBytes,e.limits.maxFrameBytes)}handleEvent(e){if(void 0===e.turnId||"turn.completed"!==e.event.type&&"turn.cancelled"!==e.event.type&&"turn.failed"!==e.event.type||this.activeTurns.delete(e.turnId),!0!==this.eventRecoveryManager?.acceptEvent(e))for(const t of this.listeners)try{t(e)}catch{}}handleExit(e,t){const r=this.connectionFailure??new RollRuntimeExitedError(e,t);this.abortServerRequests(r),this.finishExit(r,e,t),this.closeReaders()}abortServerRequests(e){const t=[...this.inFlightServerRequests.values()];this.inFlightServerRequests.clear();for(const r of t)r.controller.abort(e)}failProtocol(e){this.failConnection(e)}failConnection(e){void 0!==this.connectionFailure||this.exited||(this.connectionFailure=e,this.closing=!0,this.rejectPending(e),this.markActiveTurnsOutcomeUnknown(),this.closeOutputReader(),this.shutdown().catch(()=>{}))}closeOutputReader(){this.outputReaderClosed||(this.outputReaderClosed=!0,this.outputReader.close())}closeReaders(){this.closeOutputReader(),void 0===this.stderrReader||this.stderrReaderClosed||(this.stderrReaderClosed=!0,this.stderrReader.close())}finishExit(e,t,r){if(!this.exited){this.exited=!0,this.closing=!0,this.rejectPending(e),this.markActiveTurnsOutcomeUnknown(),this.exitResult={code:t,signal:r,error:e},this.eventRecoveryManager?.acceptClientExit(e),this.resolveExit(this.exitResult);for(const e of this.exitListeners)try{e(this.exitResult)}catch{}this.exitListeners.clear()}}rejectPending(e){for(const t of this.pending.values())clearTimeout(t.timer),t.reject(e);this.pending.clear()}markActiveTurnsOutcomeUnknown(){for(const e of this.activeTurns)this.markTurnOutcomeUnknown(e);this.activeTurns.clear()}markTurnOutcomeUnknown(e){if(this.activeTurns.delete(e),!this.unknownTurns.has(e)){this.unknownTurns.add(e);try{this.onTurnOutcomeUnknown?.(e)}catch{}}}}
|
|
1
|
+
import{spawn as e}from"node:child_process";import{once as t}from"node:events";import{createInterface as r}from"node:readline";import i from"../package.json"with{type:"json"};import{RuntimeEventRecoveryError as o,RuntimeEventRecoveryManager as n}from"./runtime-event-recovery.js";import{RUNTIME_EVENT_NOTIFICATION as s,RUNTIME_METHODS as l,RUNTIME_SERVER_REQUEST_CANCEL_NOTIFICATION as a,RUNTIME_SERVER_REQUEST_METHODS as u,RUNTIME_V13_MIN_CLIENT_FRAME_BYTES as c,SUPPORTED_RUNTIME_PROTOCOL_VERSIONS as d,getRuntimeProtocolCapabilities as h,getRuntimeProtocolRegistry as R,interactionIdSchema as m,isRuntimeServerRequestMethodAvailable as v,isLatestRuntimeServerRequestMethod as w,isRuntimeServerRequestMethodRequired as f,parseLatestRuntimeMethodParams as p,parseRuntimeMethodParamsForVersion as E,parseLatestRuntimeMethodResult as q,parseRuntimeMethodResultForVersion as g,parseRuntimeProtocolErrorDataForVersion as y,parseRuntimeServerRequestCancelParamsForVersion as S,parseRuntimeServerRequestParamsForVersion as x,parseRuntimeServerRequestResultForVersion as P,initializeResultSchema as T,runtimeEventEnvelopeSchema as b}from"@roll-agent/protocol";export{DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_BYTES,DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_EVENTS,RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS,RuntimeEventRecoveryError,RuntimeEventRecoveryManager}from"./runtime-event-recovery.js";const M=c,F=1,I=100,C=3e4,V=3e4,O=1e4,k=5e3;function N(e){if("object"!=typeof e||null===e||!("version"in e)||"string"!=typeof e.version||0===e.version.trim().length)throw new Error("@roll-agent/client-node package metadata is missing a valid version");return e.version}const $=N(i),B={methodNotFound:-32601,invalidParams:-32602,internalError:-32603},z=new Set([l.threadList,l.threadSnapshot,l.threadCapabilities,l.operationGet,l.operationResultGet]);function U(e){return"1.5"===e||"1.4"===e||"1.3"===e||"1.2"===e}function j(e,t,r){Object.assign(e,{[t]:r})}function H(e){const t={...e.serverRequestHandlers},r=t[u.userInputRequest];if(void 0!==r&&void 0!==e.onUserInputRequest&&r!==e.onUserInputRequest)throw new Error('Provide userInput.request through either "onUserInputRequest" or "serverRequestHandlers", not both');return void 0!==e.onUserInputRequest&&(t[u.userInputRequest]=e.onUserInputRequest),t}function A(e,t){return h(e).requiredServerRequestMethods.every(e=>"function"==typeof t[e])}function L(e,t,r){if(U(e)){if(!v(e,t))throw new Error(`Runtime Protocol ${e} does not support server request ${t}`);return x(e,t,r)}if(!v(e,t))throw new Error(`Runtime Protocol ${e} does not support server request ${t}`);return x(e,t,r)}function _(e,t,r){return E(e,t,r)}function D(e,t,r){return g(e,t,r)}function J(e,t,r){if(U(e)){if(!v(e,t))throw new Error(`Runtime Protocol ${e} does not support server request ${t}`);return P(e,t,r)}if(!v(e,t))throw new Error(`Runtime Protocol ${e} does not support server request ${t}`);return P(e,t,r)}export class RollRpcError extends Error{code;data;constructor(e){super(e.message),this.name="RollRpcError",this.code=e.code,this.data=e.data}}export class RollRuntimeExitedError extends Error{code;signal;constructor(e,t){super(`Roll Runtime exited before the request completed (code=${String(e)}, signal=${String(t)})`),this.name="RollRuntimeExitedError",this.code=e,this.signal=t}}export class RollRequestTimeoutError extends Error{method;timeoutMs;constructor(e,t){super(`Roll Runtime request "${e}" timed out after ${String(t)} ms`),this.name="RollRequestTimeoutError",this.method=e,this.timeoutMs=t}}export class RollRequestFrameTooLargeError extends Error{frameBytes;maxFrameBytes;constructor(e,t){super(`Roll Runtime request frame is ${String(e)} bytes, exceeding the ${String(t)} byte limit`),this.name="RollRequestFrameTooLargeError",this.frameBytes=e,this.maxFrameBytes=t}}export class RollRuntimeClosingError extends Error{constructor(){super("Roll Runtime client is shutting down"),this.name="RollRuntimeClosingError"}}export class RollRuntimeShutdownTimeoutError extends Error{gracefulTimeoutMs;terminateTimeoutMs;forceKillTimeoutMs;constructor(e,t,r){super(`Roll Runtime did not exit within ${String(e+t+r)} ms`),this.name="RollRuntimeShutdownTimeoutError",this.gracefulTimeoutMs=e,this.terminateTimeoutMs=t,this.forceKillTimeoutMs=r}}export class RollProtocolViolationError extends Error{constructor(e,t){super(e,void 0===t?void 0:{cause:t}),this.name="RollProtocolViolationError"}}export class RollUncorrelatedRpcError extends RollRpcError{constructor(e){super(e),this.name="RollUncorrelatedRpcError"}}function K(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function G(e){if(!K(e))return;const t=m.safeParse(e.interactionId);return t.success?t.data:void 0}function Q(e){return"string"==typeof e||"number"==typeof e&&Number.isFinite(e)}function Y(e,t){if(!Number.isInteger(e)||e<=0)throw new Error(`${t} must be a positive integer`);return e}function X(e={}){return{gracefulTimeoutMs:Y(e.gracefulTimeoutMs??V,"gracefulTimeoutMs"),terminateTimeoutMs:Y(e.terminateTimeoutMs??O,"terminateTimeoutMs"),forceKillTimeoutMs:Y(e.forceKillTimeoutMs??k,"forceKillTimeoutMs")}}function W(e,t,r){if(!K(e)||"number"!=typeof e.code||!Number.isInteger(e.code)||"string"!=typeof e.message)throw new RollProtocolViolationError(`Roll Runtime returned an invalid error for ${t}`);let i;if(void 0!==e.data)try{i=y(r??"1.1",e.data)}catch(e){throw new RollProtocolViolationError(`Roll Runtime returned invalid error data for ${t}`,e)}return{code:e.code,message:e.message,...void 0===i?{}:{data:i}}}function Z(e,t){return new Promise((r,i)=>{const o=setTimeout(()=>r(void 0),t);e.then(e=>{clearTimeout(o),r(e)},e=>{clearTimeout(o),i(e instanceof Error?e:new Error(String(e)))})})}function ee(e){return{stdin:e.stdin,stdout:e.stdout,stderr:e.stderr,onExit(t){let r=!1;const i=(e,i)=>{r||(r=!0,t(e,i))};e.once("close",i),e.once("error",()=>i(null,null))},close(){e.stdin.end()},terminate(){null===e.exitCode&&null===e.signalCode&&e.kill("SIGTERM")},forceClose(){null===e.exitCode&&null===e.signalCode&&e.kill("SIGKILL")}}}export class RollNodeClient{transport;maxFrameBytes;outboundMaxFrameBytes;pending=new Map;listeners=new Set;exitListeners=new Set;activeTurns=new Set;unknownTurns=new Set;serverRequestHandlers;serverRequestHandlerCapabilityRevisions=new Map;advertisedProtocolVersions;inFlightServerRequests=new Map;inFlightServerRequestsByInteractionId=new Map;acknowledgedServerRequestMethods=new Set;onTurnOutcomeUnknown;requestTimeoutMs;maxReadRetries;readRetryDelayMs;defaultShutdownOptions;outputReader;stderrReader;requestId=0;capabilityRevision=0;acknowledgedCapabilityRevision=0;writeQueue=Promise.resolve();capabilitySyncTail=Promise.resolve();exitPromise;resolveExit;exited=!1;closing=!1;outputReaderClosed=!1;stderrReaderClosed=!1;initializationResult;connectionFailure;exitResult;shutdownPromise;eventRecoveryManager;constructor(e){if(this.transport=e.transport,this.maxFrameBytes=e.maxFrameBytes??M,this.outboundMaxFrameBytes=this.maxFrameBytes,this.onTurnOutcomeUnknown=e.onTurnOutcomeUnknown,this.requestTimeoutMs=e.requestTimeoutMs??C,this.maxReadRetries=e.maxReadRetries??1,this.readRetryDelayMs=e.readRetryDelayMs??I,this.defaultShutdownOptions=X(e.shutdownOptions),this.serverRequestHandlers=H(e),this.advertisedProtocolVersions=d.filter(e=>A(e,this.serverRequestHandlers)&&("1.3"!==e&&"1.4"!==e&&"1.5"!==e||this.maxFrameBytes>=c)),!Number.isInteger(this.maxFrameBytes)||this.maxFrameBytes<=0)throw new Error("maxFrameBytes must be a positive integer");if(!Number.isInteger(this.requestTimeoutMs)||this.requestTimeoutMs<=0)throw new Error("requestTimeoutMs must be a positive integer");if(!Number.isInteger(this.maxReadRetries)||this.maxReadRetries<0)throw new Error("maxReadRetries must be a non-negative integer");if(!Number.isInteger(this.readRetryDelayMs)||this.readRetryDelayMs<0)throw new Error("readRetryDelayMs must be a non-negative integer");this.exitPromise=new Promise(e=>{this.resolveExit=e}),this.outputReader=r({input:e.transport.stdout}),this.outputReader.on("line",e=>this.handleLine(e)),this.stderrReader=void 0===e.transport.stderr?void 0:r({input:e.transport.stderr}),this.stderrReader?.on("line",t=>{try{e.onStderr?.(t)}catch{}}),this.transport.onExit((e,t)=>this.handleExit(e,t))}static async start(t){if(0===t.cwd.trim().length)throw new Error("RollNodeClient requires an explicit non-empty cwd");const r=e(t.command??"roll",[...t.args??["runtime","serve","--stdio"]],{cwd:t.cwd,env:t.env??process.env,shell:!1,stdio:"pipe"});await new Promise((e,t)=>{const i=e=>t(e);r.once("error",i),r.once("spawn",()=>{r.off("error",i),e()})});return await RollNodeClient.connect({transport:ee(r),...void 0!==t.clientName?{clientName:t.clientName}:{},...void 0!==t.clientVersion?{clientVersion:t.clientVersion}:{},...void 0!==t.onStderr?{onStderr:t.onStderr}:{},...void 0!==t.onTurnOutcomeUnknown?{onTurnOutcomeUnknown:t.onTurnOutcomeUnknown}:{},...void 0!==t.maxFrameBytes?{maxFrameBytes:t.maxFrameBytes}:{},...void 0!==t.requestTimeoutMs?{requestTimeoutMs:t.requestTimeoutMs}:{},...void 0!==t.maxReadRetries?{maxReadRetries:t.maxReadRetries}:{},...void 0!==t.readRetryDelayMs?{readRetryDelayMs:t.readRetryDelayMs}:{},...void 0!==t.shutdownOptions?{shutdownOptions:t.shutdownOptions}:{},...void 0!==t.serverRequestHandlers?{serverRequestHandlers:t.serverRequestHandlers}:{},...void 0!==t.onUserInputRequest?{onUserInputRequest:t.onUserInputRequest}:{}})}static async connect(e){let t;try{return t=new RollNodeClient(e),t.initializationResult=await t.request(l.initialize,{protocolVersions:[...t.advertisedProtocolVersions],client:{name:e.clientName??"@roll-agent/client-node",version:e.clientVersion??$}}),t.acceptInitializationResult(t.initializationResult),await t.initializeServerRequestCapabilities(),t}catch(r){throw void 0===t?e.transport.close():await t.shutdown().catch(()=>{}),r}}async initializeServerRequestCapabilities(){const e=this.getInitializationResult().protocolVersion;U(e)?await this.queueServerRequestCapabilitySync():this.acknowledgedServerRequestMethods=new Set(R(e).serverRequestMethods.filter(e=>void 0!==this.serverRequestHandlers[e]))}queueServerRequestCapabilitySync(){const e=this.getInitializationResult().protocolVersion;if(!U(e))return Promise.resolve();if(this.capabilityRevision>=Number.MAX_SAFE_INTEGER)return Promise.reject(new RollProtocolViolationError("Runtime Client capability revision was exhausted"));const t=this.capabilityRevision+1;this.capabilityRevision=t;const r=R(e).serverRequestMethods.filter(e=>void 0!==this.serverRequestHandlers[e]);for(const e of r)this.serverRequestHandlerCapabilityRevisions.has(e)||this.serverRequestHandlerCapabilityRevisions.set(e,t);const i=this.capabilitySyncTail.then(async()=>{await this.requestClientCapabilitiesSet(e,t,r)});return this.capabilitySyncTail=i,i}requestClientCapabilitiesSet(e,t,r){if(void 0!==this.connectionFailure)return Promise.reject(this.connectionFailure);if(this.exited)return Promise.reject(this.exitResult?.error??new RollRuntimeExitedError(null,null));if(this.closing)return Promise.reject(new RollRuntimeClosingError);const i=l.clientCapabilitiesSet,o=E(e,i,{revision:t,serverRequestMethods:[...r]});return this.requestOnce(i,o,t=>g(e,i,t),i=>{this.acceptClientCapabilitiesSetResult(e,t,r,i)})}acceptClientCapabilitiesSetResult(e,t,r,i){const o=g(e,l.clientCapabilitiesSet,i);if(o.revision!==t)throw new RollProtocolViolationError(`Roll Runtime acknowledged Client capability revision ${String(o.revision)}; expected ${String(t)}`);const n=new Set(r),s=new Set(o.acceptedServerRequestMethods);for(const e of s)if(!n.has(e))throw new RollProtocolViolationError(`Roll Runtime acknowledged Client capability method "${e}" that was not requested`);this.acknowledgedCapabilityRevision=t,this.acknowledgedServerRequestMethods=s}observeServerRequestCapabilitySync(e){e.catch(e=>{this.failConnection(e instanceof Error?e:new Error(String(e)))})}onEvent(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}createEventRecovery(e={}){if(void 0!==this.connectionFailure)throw this.connectionFailure;if(this.exited)throw this.exitResult?.error??new RollRuntimeExitedError(null,null);if(this.closing)throw new RollRuntimeClosingError;return void 0===this.eventRecoveryManager||this.eventRecoveryManager.isClosed()?(this.eventRecoveryManager=new n({getInitializationResult:()=>this.getInitializationResult(),requestSnapshot:e=>this.requestEventRecoverySnapshot(e),requestResume:(e,t)=>this.requestRuntimeEventsResume(e,t)},e),this.eventRecoveryManager):this.eventRecoveryManager}onExit(e){const t=this.exitResult;return void 0!==t?(queueMicrotask(()=>{try{e(t)}catch{}}),()=>{}):(this.exitListeners.add(e),()=>{this.exitListeners.delete(e)})}getInitializationResult(){if(void 0===this.initializationResult)throw new Error("Roll Runtime client has not completed initialization");return this.initializationResult}registerServerRequestHandler(e,t){if(void 0!==this.connectionFailure)throw this.connectionFailure;if(this.exited)throw this.exitResult?.error??new RollRuntimeExitedError(null,null);if(this.closing)throw new RollRuntimeClosingError;if(!w(e))throw new Error(`Unknown Runtime server request method: ${String(e)}`);const r=this.initializationResult?.protocolVersion;if(void 0!==r&&!h(r).serverRequests)throw new Error(`Cannot register a Runtime server request handler after Protocol ${r} was negotiated; pass serverRequestHandlers to RollNodeClient.connect() or RollNodeClient.start()`);const i=this.serverRequestHandlers[e];return j(this.serverRequestHandlers,e,t),void 0!==r&&U(r)&&void 0===i&&this.observeServerRequestCapabilitySync(this.queueServerRequestCapabilitySync()),()=>{if(this.serverRequestHandlers[e]!==t)return;const r=this.initializationResult?.protocolVersion;void 0!==r&&f(r,e)?this.failProtocol(new RollProtocolViolationError(`Cannot unregister required Runtime server request handler "${e}" while Protocol ${r} is active`)):(delete this.serverRequestHandlers[e],this.serverRequestHandlerCapabilityRevisions.delete(e),void 0!==r&&U(r)&&(this.retireServerRequestsForMethod(e,new Error(`Runtime Client capability "${e}" was withdrawn`)),this.observeServerRequestCapabilitySync(this.queueServerRequestCapabilitySync())))}}onUserInputRequest(e){return this.registerServerRequestHandler(u.userInputRequest,e)}getOperationResult(e){return this.request(l.operationResultGet,e)}requestEventRecoverySnapshot(e){if(void 0!==this.connectionFailure)return Promise.reject(this.connectionFailure);if(this.exited)return Promise.reject(this.exitResult?.error??new RollRuntimeExitedError(null,null));if(this.closing)return Promise.reject(new RollRuntimeClosingError);const t=this.getInitializationResult().protocolVersion;if("1.3"!==t&&"1.4"!==t&&"1.5"!==t)return this.request(l.threadSnapshot,{threadId:e,limit:1});const r=l.threadSnapshot,i=E(t,r,{threadId:e,limit:1,recovery:!0});return this.requestOnce(r,i,e=>g(t,r,e))}requestRuntimeEventsResume(e,t){if(void 0!==this.connectionFailure)return Promise.reject(this.connectionFailure);if(this.exited)return Promise.reject(this.exitResult?.error??new RollRuntimeExitedError(null,null));if(this.closing)return Promise.reject(new RollRuntimeClosingError);const r=this.getInitializationResult().protocolVersion;if("1.3"!==r&&"1.4"!==r&&"1.5"!==r)return Promise.reject(new o("runtime.events.resume requires negotiated Runtime Protocol 1.3 or newer"));const i=l.runtimeEventsResume,n=E("1.3",i,e);return this.requestOnce(i,n,e=>g("1.3",i,e),e=>{t(g("1.3",i,e))})}async request(e,t){if(void 0!==this.connectionFailure)throw this.connectionFailure;if(this.exited)throw this.exitResult?.error??new RollRuntimeExitedError(null,null);if(this.closing)throw new RollRuntimeClosingError;if(e===l.initialize&&void 0!==this.initializationResult)throw new Error("Roll Runtime client has already completed initialization");const r=this.initializationResult?.protocolVersion,i=void 0===r?p(e,t):_(r,e,t),o=e===l.turnStart?p(l.turnStart,i).turnId:void 0;void 0!==o&&this.activeTurns.add(o);let n=0;for(;;)try{return await this.requestOnce(e,i,t=>void 0===r?e===l.initialize?T.parse(t):q(e,t):D(r,e,t))}catch(t){if(!z.has(e)||!(t instanceof RollRpcError)||!0!==t.data?.retryable||n>=this.maxReadRetries)throw void 0!==o&&(t instanceof RollRpcError&&!(t instanceof RollUncorrelatedRpcError)||t instanceof RollRequestFrameTooLargeError?this.activeTurns.delete(o):this.markTurnOutcomeUnknown(o)),t;n+=1,await new Promise(e=>{setTimeout(e,this.readRetryDelayMs)})}}async requestOnce(e,t,r,i){const o=this.requestId;this.requestId+=1;const n=new Promise((t,r)=>{const n=setTimeout(()=>{this.pending.delete(o)&&r(new RollRequestTimeoutError(e,this.requestTimeoutMs))},this.requestTimeoutMs);this.pending.set(o,{method:e,resolve:t,reject:r,timer:n,...void 0===i?{}:{acceptResult:i}})});try{await this.write({jsonrpc:"2.0",id:o,method:e,params:t});const i=await n;try{return r(i)}catch(t){const r=new RollProtocolViolationError(`Roll Runtime returned an invalid result for "${e}"`,t);throw this.failProtocol(r),r}}catch(e){const t=this.pending.get(o);throw void 0!==t&&(clearTimeout(t.timer),this.pending.delete(o)),e}}getOutcomeUnknownTurnIds(){return[...this.unknownTurns]}close(){this.shutdown().catch(()=>{})}shutdown(e){const t=this.exitResult;return void 0!==t?Promise.resolve(t):(this.shutdownPromise??=this.performShutdown(void 0===e?this.defaultShutdownOptions:X(e)),this.shutdownPromise)}async performShutdown(e){const{gracefulTimeoutMs:t,terminateTimeoutMs:r,forceKillTimeoutMs:i}=e;this.closing=!0,this.abortServerRequests(new RollRuntimeClosingError),this.transport.close();const o=await Z(this.exitPromise,t);if(void 0!==o)return o;if(void 0!==this.transport.terminate){this.transport.terminate();const e=await Z(this.exitPromise,r);if(void 0!==e)return e}if(void 0===this.transport.forceClose){const e=new RollRuntimeShutdownTimeoutError(t,r,i);throw this.finishExit(e,null,null),this.closeReaders(),e}this.transport.forceClose();const n=await Z(this.exitPromise,i);if(void 0!==n)return n;const s=new RollRuntimeShutdownTimeoutError(t,r,i);throw this.finishExit(s,null,null),this.closeReaders(),s}async write(e,r){const i=JSON.stringify(e),o=Buffer.byteLength(i,"utf8");if(o>this.outboundMaxFrameBytes)throw new RollRequestFrameTooLargeError(o,this.outboundMaxFrameBytes);const n=`${i}\n`;this.writeQueue=this.writeQueue.then(async()=>{this.closing||void 0!==this.connectionFailure||this.exited||!1===r?.()||this.transport.stdin.write(n)||await t(this.transport.stdin,"drain")});try{await this.writeQueue}catch(e){const t=new RollProtocolViolationError("Failed to write to the Roll Runtime transport",e);throw this.failProtocol(t),t}}handleLine(e){if(Buffer.byteLength(e,"utf8")>this.maxFrameBytes)return void this.failProtocol(new RollProtocolViolationError(`Roll Runtime frame exceeds ${String(this.maxFrameBytes)} bytes`));let t;try{t=JSON.parse(e)}catch(e){return void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted invalid JSON",e))}if(!K(t)||"2.0"!==t.jsonrpc)return void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted an invalid JSON-RPC frame"));const r=Object.hasOwn(t,"method"),i=Object.hasOwn(t,"id"),o=Object.hasOwn(t,"result"),n=Object.hasOwn(t,"error");if(r)return"string"!=typeof t.method||o||n?void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted an invalid JSON-RPC request or notification")):i?Q(t.id)?void this.handleServerRequest(t.id,t.method,t.params):void this.failProtocol(new RollProtocolViolationError("Roll Runtime request has an invalid id")):void this.handleNotification(t.method,t.params);if(!i)return void this.failProtocol(new RollProtocolViolationError("Roll Runtime response is missing an id"));if(null===t.id){if(!n||o)return void this.failProtocol(new RollProtocolViolationError("Roll Runtime response with a null id must contain exactly one error"));try{this.failConnection(new RollUncorrelatedRpcError(W(t.error,"an uncorrelated request",this.initializationResult?.protocolVersion)))}catch(e){this.failProtocol(e instanceof RollProtocolViolationError?e:new RollProtocolViolationError("Roll Runtime returned an invalid uncorrelated error",e))}return}if(!Q(t.id))return void this.failProtocol(new RollProtocolViolationError("Roll Runtime response has an invalid id"));const s=this.pending.get(t.id);if(void 0!==s)if(n!==o){if(n){let e;try{e=new RollRpcError(W(t.error,`"${s.method}"`,this.initializationResult?.protocolVersion))}catch(e){return void this.failProtocol(e instanceof RollProtocolViolationError?e:new RollProtocolViolationError(`Roll Runtime returned an invalid error for "${s.method}"`,e))}return this.pending.delete(t.id),clearTimeout(s.timer),void s.reject(e)}if(s.method===l.initialize)try{this.acceptInitializationResult(T.parse(t.result))}catch(e){return void this.failProtocol(e instanceof RollProtocolViolationError?e:new RollProtocolViolationError('Roll Runtime returned an invalid result for "initialize"',e))}if(void 0!==s.acceptResult)try{s.acceptResult(t.result)}catch(e){return void this.failProtocol(e instanceof RollProtocolViolationError?e:new RollProtocolViolationError(`Roll Runtime returned an invalid result for "${s.method}"`,e))}this.pending.delete(t.id),clearTimeout(s.timer),s.resolve(t.result)}else this.failProtocol(new RollProtocolViolationError(`Roll Runtime response for "${s.method}" must contain exactly one result or error`))}handleNotification(e,t){if(e===s){const e=b.safeParse(t);return e.success?void 0===this.initializationResult||e.data.protocolVersion!==this.initializationResult.protocolVersion?void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted runtime.event outside the negotiated Protocol")):void this.handleEvent(e.data):void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted an invalid runtime.event",e.error))}if(e===a){const e=this.initializationResult?.protocolVersion;if(void 0===e||!h(e).serverRequests)return void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted runtime.serverRequest.cancel outside a negotiated server-request capability"));try{if(U(e)){const r=S(e,t),i=this.inFlightServerRequestsByInteractionId.get(r.interactionId);return void(void 0!==i&&this.retireServerRequest(i,new Error(r.reason)))}if("1.1"===e){const r=S(e,t),i=this.inFlightServerRequests.get(r.serverRequestId);return void(void 0!==i&&this.retireServerRequest(i,new Error(r.reason)))}}catch(e){return void this.failProtocol(new RollProtocolViolationError("Roll Runtime emitted an invalid runtime.serverRequest.cancel",e))}return void this.failProtocol(new RollProtocolViolationError(`Roll Runtime emitted runtime.serverRequest.cancel for unsupported Protocol ${e}`))}this.failProtocol(new RollProtocolViolationError(`Roll Runtime emitted an unknown notification: ${e}`))}handleServerRequest(e,t,r){if(this.closing||void 0!==this.connectionFailure||this.exited)return;if(this.inFlightServerRequests.has(e))return void this.failProtocol(new RollProtocolViolationError(`Roll Runtime reused in-flight server request id ${JSON.stringify(e)}`));const i=this.initializationResult?.protocolVersion;if(void 0===i||"1.0"===i||!h(i).serverRequests||!w(t)||!v(i,t))return void this.observeServerRequestResponse(this.writeServerRequestError(e,B.methodNotFound,"Method not found"));const o=this.serverRequestHandlers[t],n=this.serverRequestHandlerCapabilityRevisions.get(t);U(i)&&(!this.acknowledgedServerRequestMethods.has(t)||void 0===n||this.acknowledgedCapabilityRevision<n)||void 0===o?this.observeServerRequestResponse(this.writeServerRequestError(e,B.methodNotFound,"Method not found")):this.observeServerRequestResponse(this.dispatchServerRequest(i,e,t,r,o))}observeServerRequestResponse(e){e.catch(e=>{void 0!==this.connectionFailure||this.exited||this.failProtocol(new RollProtocolViolationError("Failed to respond to Runtime server request",e))})}async dispatchServerRequest(e,t,r,i,o){const n={controller:new AbortController,method:r,requestId:t};this.inFlightServerRequests.set(t,n);try{let s;try{s=L(e,r,i)}catch{return void await this.writeServerRequestError(t,B.invalidParams,"Invalid params",()=>this.isServerRequestActive(t,n))}const l=G(s);if(void 0!==l&&this.inFlightServerRequestsByInteractionId.has(l))return void this.failProtocol(new RollProtocolViolationError(`Roll Runtime reused in-flight interaction id ${JSON.stringify(l)}`));let a,u;void 0!==l&&(n.interactionId=l,this.inFlightServerRequestsByInteractionId.set(l,n),n.controller.signal.addEventListener("abort",()=>{this.inFlightServerRequestsByInteractionId.get(l)===n&&this.inFlightServerRequestsByInteractionId.delete(l)},{once:!0}));try{a=await o(s,{requestId:t,signal:n.controller.signal})}catch{return void(this.isServerRequestActive(t,n)&&await this.writeServerRequestError(t,B.internalError,"Internal error",()=>this.isServerRequestActive(t,n)))}if(!this.isServerRequestActive(t,n))return;try{u=J(e,r,a)}catch{return void await this.writeServerRequestError(t,B.internalError,"Internal error",()=>this.isServerRequestActive(t,n))}this.isServerRequestActive(t,n)&&await this.write({jsonrpc:"2.0",id:t,result:u},()=>this.isServerRequestActive(t,n))}finally{this.retireServerRequest(n)}}retireServerRequest(e,t){this.inFlightServerRequests.get(e.requestId)===e&&this.inFlightServerRequests.delete(e.requestId),void 0!==e.interactionId&&this.inFlightServerRequestsByInteractionId.get(e.interactionId)===e&&this.inFlightServerRequestsByInteractionId.delete(e.interactionId),void 0===t||e.controller.signal.aborted||e.controller.abort(t)}retireServerRequestsForMethod(e,t){for(const r of[...this.inFlightServerRequests.values()])r.method===e&&this.retireServerRequest(r,t)}isServerRequestActive(e,t){return!t.controller.signal.aborted&&this.inFlightServerRequests.get(e)===t}async writeServerRequestError(e,t,r,i){await this.write({jsonrpc:"2.0",id:e,error:{code:t,message:r}},i)}acceptInitializationResult(e){if(!this.advertisedProtocolVersions.includes(e.protocolVersion))throw new RollProtocolViolationError(`Roll Runtime selected unadvertised Protocol ${e.protocolVersion}`);this.initializationResult=e,this.outboundMaxFrameBytes=Math.min(this.maxFrameBytes,e.limits.maxFrameBytes)}handleEvent(e){if(void 0===e.turnId||"turn.completed"!==e.event.type&&"turn.cancelled"!==e.event.type&&"turn.failed"!==e.event.type||this.activeTurns.delete(e.turnId),!0!==this.eventRecoveryManager?.acceptEvent(e))for(const t of this.listeners)try{t(e)}catch{}}handleExit(e,t){const r=this.connectionFailure??new RollRuntimeExitedError(e,t);this.abortServerRequests(r),this.finishExit(r,e,t),this.closeReaders()}abortServerRequests(e){const t=[...this.inFlightServerRequests.values()];this.inFlightServerRequests.clear();for(const r of t)r.controller.abort(e)}failProtocol(e){this.failConnection(e)}failConnection(e){void 0!==this.connectionFailure||this.exited||(this.connectionFailure=e,this.closing=!0,this.rejectPending(e),this.markActiveTurnsOutcomeUnknown(),this.closeOutputReader(),this.shutdown().catch(()=>{}))}closeOutputReader(){this.outputReaderClosed||(this.outputReaderClosed=!0,this.outputReader.close())}closeReaders(){this.closeOutputReader(),void 0===this.stderrReader||this.stderrReaderClosed||(this.stderrReaderClosed=!0,this.stderrReader.close())}finishExit(e,t,r){if(!this.exited){this.exited=!0,this.closing=!0,this.rejectPending(e),this.markActiveTurnsOutcomeUnknown(),this.exitResult={code:t,signal:r,error:e},this.eventRecoveryManager?.acceptClientExit(e),this.resolveExit(this.exitResult);for(const e of this.exitListeners)try{e(this.exitResult)}catch{}this.exitListeners.clear()}}rejectPending(e){for(const t of this.pending.values())clearTimeout(t.timer),t.reject(e);this.pending.clear()}markActiveTurnsOutcomeUnknown(){for(const e of this.activeTurns)this.markTurnOutcomeUnknown(e);this.activeTurns.clear()}markTurnOutcomeUnknown(e){if(this.activeTurns.delete(e),!this.unknownTurns.has(e)){this.unknownTurns.add(e);try{this.onTurnOutcomeUnknown?.(e)}catch{}}}}
|
|
@@ -27,11 +27,11 @@ type RuntimeEventRecoveryPhase = (typeof RECOVERY_PHASES)[keyof typeof RECOVERY_
|
|
|
27
27
|
export type RuntimeEventRecoveryMode = (typeof RECOVERY_MODES)[keyof typeof RECOVERY_MODES];
|
|
28
28
|
export type RuntimeEventRecoverySnapshotReason = (typeof RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS)[keyof typeof RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS];
|
|
29
29
|
export type RuntimeDurableEventEnvelope = Extract<RuntimeEventEnvelope, {
|
|
30
|
-
readonly protocolVersion: "1.3" | "1.4";
|
|
30
|
+
readonly protocolVersion: "1.3" | "1.4" | "1.5";
|
|
31
31
|
readonly durability: "durable";
|
|
32
32
|
}>;
|
|
33
33
|
export type RuntimeEphemeralEventEnvelope = Extract<RuntimeEventEnvelope, {
|
|
34
|
-
readonly protocolVersion: "1.3" | "1.4";
|
|
34
|
+
readonly protocolVersion: "1.3" | "1.4" | "1.5";
|
|
35
35
|
readonly durability: "ephemeral";
|
|
36
36
|
}>;
|
|
37
37
|
export type RuntimeEventRecoverySnapshot = RuntimeMethodResultForVersion<RuntimeProtocolVersion, typeof RUNTIME_METHODS.threadSnapshot>;
|
|
@@ -73,7 +73,7 @@ export type RuntimeEventRecoveryStartResult = {
|
|
|
73
73
|
readonly protocolVersion: Exclude<RuntimeProtocolVersion, DurableRecoveryProtocolVersion>;
|
|
74
74
|
readonly checkpoint: null;
|
|
75
75
|
};
|
|
76
|
-
export type DurableRecoveryProtocolVersion = Extract<RuntimeProtocolVersion, "1.3" | "1.4">;
|
|
76
|
+
export type DurableRecoveryProtocolVersion = Extract<RuntimeProtocolVersion, "1.3" | "1.4" | "1.5">;
|
|
77
77
|
/**
|
|
78
78
|
* Internal transport bridge supplied by RollNodeClient.
|
|
79
79
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash as e}from"node:crypto";import{RUNTIME_ERROR_CODES_V13 as t,RUNTIME_METHODS as r,RUNTIME_V13_DEFAULT_REPLAY_BUFFER_BYTES as n,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as i,compareRuntimeEventCursors as o,runtimeEventCursorDistance as s}from"@roll-agent/protocol";export const DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_EVENTS=i;export const DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_BYTES=n;const a={buffering:"buffering",draining:"draining",live:"live",failed:"failed",stopped:"stopped"},u={resumed:"resumed",snapshotResumed:"snapshot-resumed",snapshotOnly:"snapshot-only"};export const RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS={initial:"initial",runtimeRestarted:"runtime-restarted",cursorExpired:"cursor-expired",cursorGap:"cursor-gap",streamGap:"stream-gap",bufferOverflow:"buffer-overflow",cursorConflict:"cursor-conflict",protocolUnsupported:"protocol-unsupported"};function c(e){return"1.3"===e||"1.4"===e}export class RuntimeEventRecoveryError extends Error{constructor(e,t){super(e,t),this.name="RuntimeEventRecoveryError"}}class RuntimeEventStreamIntegrityError extends RuntimeEventRecoveryError{reason;constructor(e,t,r){super(t,r),this.name="RuntimeEventStreamIntegrityError",this.reason=e}}function E(e,t,r){const n=e??t;if(!Number.isSafeInteger(n)||n<=0)throw new Error(`${r} must be a positive safe integer`);return n}function d(e){return h(e).fingerprint}function h(t){const r=JSON.stringify({...t,sequence:void 0});return{fingerprint:e("sha256").update(r).digest("hex"),bytes:Buffer.byteLength(r,"utf8")}}function f(e){return e instanceof Error?e:new Error(String(e))}function v(e){if("object"==typeof e&&null!==e&&"data"in e&&"object"==typeof e.data&&null!==e.data&&"rollCode"in e.data&&"string"==typeof e.data.rollCode)return e.data.rollCode}function l(e){const r=v(e);return r===t.eventCursorExpired?RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorExpired:r===t.eventCursorGap?RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorGap:void 0}function p(e){if(!("eventCursor"in e))throw new RuntimeEventRecoveryError("Runtime Protocol 1.3 snapshot omitted eventCursor");return e.eventCursor}function R(e){if(!("recoveryProjection"in e)||!0!==e.recoveryProjection)throw new RuntimeEventRecoveryError("Runtime Protocol 1.3 recovery Snapshot omitted the bounded recovery projection marker")}function m(e,t){try{return o(e,t)}catch(e){throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event cursors are not comparable",{cause:e})}}function y(e,t){try{return s(e,t)}catch(e){throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event cursor distance is not comparable",{cause:e})}}function I(e){return("1.3"===e.protocolVersion||"1.4"===e.protocolVersion)&&"durable"===e.durability}function S(e){return("1.3"===e.protocolVersion||"1.4"===e.protocolVersion)&&"ephemeral"===e.durability}export class RuntimeEventRecoveryManager{bridge;maxBufferedEvents;maxBufferedBytes;states=new Map;checkpoints=new Map;closed=!1;constructor(e,t={}){this.bridge=e,this.maxBufferedEvents=E(t.maxBufferedEvents,DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_EVENTS,"maxBufferedEvents"),this.maxBufferedBytes=E(t.maxBufferedBytes,DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_BYTES,"maxBufferedBytes")}isClosed(){return this.closed}getCheckpoint(e){return this.checkpoints.get(e)}async resumeThread(e){if(this.closed)throw new RuntimeEventRecoveryError("Runtime Event recovery manager is closed");if(this.states.has(e.threadId))throw new RuntimeEventRecoveryError(`Runtime Event recovery already owns Thread ${JSON.stringify(e.threadId)}`);if(void 0!==e.checkpoint&&e.checkpoint.threadId!==e.threadId)throw new RuntimeEventRecoveryError("Runtime Event checkpoint belongs to another Thread");const t=this.bridge.getInitializationResult();if(!c(t.protocolVersion)){const r=await this.bridge.requestSnapshot(e.threadId);return await e.applySnapshot(r,{reason:RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.protocolUnsupported,protocolVersion:t.protocolVersion,recoveryProjection:"recoveryProjection"in r&&!0===r.recoveryProjection}),{mode:u.snapshotOnly,protocolVersion:t.protocolVersion,checkpoint:null}}const r={threadId:e.threadId,runtimeInstanceId:t.runtimeInstanceId,cursor:e.checkpoint?.runtimeInstanceId===t.runtimeInstanceId?e.checkpoint.cursor:null},n={options:e,runtimeInstanceId:t.runtimeInstanceId,buffer:[],bufferedByEventId:new Map,bufferedByCursor:new Map,seenByEventId:new Map,seenByCursor:new Map,phase:a.buffering,checkpoint:r,bufferBytes:0,integrityError:void 0,liveDrain:void 0};this.states.set(e.threadId,n),this.checkpoints.set(e.threadId,r);try{let r;if(void 0===e.checkpoint)await this.snapshotAndResume(n,RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.initial),r=u.snapshotResumed;else if(e.checkpoint.runtimeInstanceId!==t.runtimeInstanceId)await this.snapshotAndResume(n,RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.runtimeRestarted),r=u.snapshotResumed;else try{await this.resumeFrom(n,e.checkpoint.cursor),r=u.resumed}catch(e){const t=l(e)??(e instanceof RuntimeEventStreamIntegrityError?e.reason:void 0);if(void 0===t)throw e;await this.snapshotAndResume(n,t),r=u.snapshotResumed}return this.assertActive(n),n.phase=a.live,this.scheduleLiveDrain(n),{mode:r,protocolVersion:t.protocolVersion,checkpoint:n.checkpoint}}catch(t){throw this.failState(n,f(t)),this.states.delete(e.threadId),t}}stopThread(e){const t=this.states.get(e);return void 0!==t&&(t.phase=a.stopped,this.states.delete(e),this.clearBuffer(t),!0)}close(){if(!this.closed){this.closed=!0;for(const e of this.states.values())e.phase=a.stopped,this.clearBuffer(e);this.states.clear()}}acceptEvent(e){const t=this.states.get(e.threadId);if(void 0===t)return!1;if(t.phase===a.stopped||t.phase===a.failed)return!0;if(e.runtimeInstanceId!==t.runtimeInstanceId)return this.markIntegrityFailure(t,new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event belongs to another Runtime instance")),!0;if(S(e)){if(t.phase===a.live)try{t.options.onEphemeralEvent?.(e)}catch{}return!0}return I(e)?(this.bufferDurableEvent(t,e),t.phase===a.live&&this.scheduleLiveDrain(t),!0):(this.markIntegrityFailure(t,new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Claimed Thread received an event outside Runtime Protocol 1.3")),!0)}acceptClientExit(e){for(const t of this.states.values())this.failState(t,e)}async snapshotAndResume(e,t){let r=t;for(let t=0;t<2;t+=1){this.assertActive(e),e.phase=a.buffering;const n=await this.bridge.requestSnapshot(e.options.threadId);this.assertActive(e),R(n),await e.options.applySnapshot(n,{reason:r,protocolVersion:this.bridge.getInitializationResult().protocolVersion,recoveryProjection:!0}),this.assertActive(e);const i=p(n);this.clearBuffer(e),e.seenByEventId.clear(),e.seenByCursor.clear(),e.checkpoint={threadId:e.options.threadId,runtimeInstanceId:e.runtimeInstanceId,cursor:i},this.checkpoints.set(e.options.threadId,e.checkpoint);try{return void await this.resumeFrom(e,i)}catch(e){const n=l(e)??(e instanceof RuntimeEventStreamIntegrityError?e.reason:void 0);if(void 0===n||1===t)throw e;r=n}}throw new RuntimeEventRecoveryError("Runtime Event recovery could not converge on a Snapshot")}async resumeFrom(e,t){let r;this.assertActive(e),e.phase=a.buffering,e.integrityError=void 0;const n=await this.bridge.requestResume({threadId:e.options.threadId,afterCursor:t},e=>{r=e});this.assertActive(e);const i=r??n;e.phase=a.draining,this.throwIntegrityError(e);const o=this.takeBuffer(e),s=this.validateBarrier(o,t,i);for(await this.deliverBatch(e,s,t);e.buffer.length>0;){this.throwIntegrityError(e);const t=this.prepareBatch(e,this.takeBuffer(e));await this.deliverBatch(e,t,e.checkpoint.cursor)}this.throwIntegrityError(e)}validateBarrier(e,t,r){const n=this.prepareBatch(void 0,e).filter(e=>null===t||m(e.cursor,t)>0),i=null===r.throughCursor?[]:n.filter(e=>m(e.cursor,r.throughCursor)<=0);if(i.length!==r.replayedCount)throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.streamGap,`Runtime Event replay announced ${String(r.replayedCount)} events but delivered ${String(i.length)}`);if(0===r.replayedCount){if(0!==m(t,r.throughCursor))throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.streamGap,"Runtime Event replay returned an invalid empty barrier")}else{const e=i.at(-1);if(void 0===e||null===r.throughCursor||0!==m(e.cursor,r.throughCursor))throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.streamGap,"Runtime Event replay did not reach its throughCursor barrier");if(y(t,r.throughCursor)!==BigInt(r.replayedCount))throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.streamGap,"Runtime Event replay barrier contains a cursor gap")}return n}prepareBatch(e,t){return t.sort((e,t)=>m(e.cursor,t.cursor)),void 0===e?t:t.filter(t=>{const r=e.seenByEventId.get(t.eventId);if(void 0!==r){if(r.cursor!==t.cursor||r.fingerprint!==d(t))throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event reused an eventId with conflicting content");return!1}const n=e.seenByCursor.get(t.cursor);if(void 0!==n&&n!==t.eventId)throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event cursor was reused by another eventId");return!0})}async deliverBatch(e,t,r){let n=r;for(const r of t){this.assertActive(e);const t=y(n,r.cursor);if(t<=0n)continue;if(1n!==t)throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.streamGap,"Runtime Event stream contains a cursor gap");const i={threadId:e.options.threadId,runtimeInstanceId:e.runtimeInstanceId,cursor:r.cursor};await e.options.onDurableEvent(r,i),this.assertActive(e),e.checkpoint=i,this.checkpoints.set(e.options.threadId,i),this.rememberDelivered(e,r),n=r.cursor}}bufferDurableEvent(e,t){const{fingerprint:r,bytes:n}=h(t),i=e.seenByEventId.get(t.eventId);if(void 0!==i)return void(i.cursor===t.cursor&&i.fingerprint===r||this.markIntegrityFailure(e,new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event reused a delivered eventId with conflicting content")));const o=e.bufferedByEventId.get(t.eventId);if(void 0!==o)return void(o.cursor===t.cursor&&o.fingerprint===r||this.markIntegrityFailure(e,new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event reused a buffered eventId with conflicting content")));const s=e.bufferedByCursor.get(t.cursor)??e.seenByCursor.get(t.cursor);if(void 0===s||s===t.eventId){if(e.buffer.length+1>this.maxBufferedEvents||e.bufferBytes+n>this.maxBufferedBytes)return this.clearBuffer(e),void this.markIntegrityFailure(e,new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.bufferOverflow,"Runtime Event recovery buffer exceeded its bounded capacity"));e.buffer.push(t),e.bufferBytes+=n,e.bufferedByEventId.set(t.eventId,{cursor:t.cursor,fingerprint:r}),e.bufferedByCursor.set(t.cursor,t.eventId)}else this.markIntegrityFailure(e,new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event cursor was reused by another eventId"))}takeBuffer(e){const t=e.buffer.splice(0);return e.bufferBytes=0,e.bufferedByEventId.clear(),e.bufferedByCursor.clear(),t}clearBuffer(e){e.buffer.splice(0),e.bufferBytes=0,e.bufferedByEventId.clear(),e.bufferedByCursor.clear(),e.integrityError=void 0}rememberDelivered(e,t){for(e.seenByEventId.set(t.eventId,{cursor:t.cursor,fingerprint:d(t)}),e.seenByCursor.set(t.cursor,t.eventId);e.seenByEventId.size>this.maxBufferedEvents;){const t=e.seenByEventId.keys().next().value;if(void 0===t)break;const r=e.seenByEventId.get(t);e.seenByEventId.delete(t),void 0!==r&&e.seenByCursor.delete(r.cursor)}}markIntegrityFailure(e,t){e.integrityError??=t,e.phase===a.live&&this.scheduleLiveDrain(e)}throwIntegrityError(e){if(void 0!==e.integrityError)throw e.integrityError}scheduleLiveDrain(e){void 0!==e.liveDrain||e.phase!==a.live||0===e.buffer.length&&void 0===e.integrityError||(e.liveDrain=this.drainLive(e).finally(()=>{e.liveDrain=void 0,e.phase===a.live&&this.scheduleLiveDrain(e)}))}async drainLive(e){try{for(this.throwIntegrityError(e);e.buffer.length>0;){const t=this.prepareBatch(e,this.takeBuffer(e));await this.deliverBatch(e,t,e.checkpoint.cursor),this.throwIntegrityError(e)}}catch(t){if(t instanceof RuntimeEventStreamIntegrityError)try{return await this.snapshotAndResume(e,t.reason),this.assertActive(e),void(e.phase=a.live)}catch(t){return void this.failState(e,f(t))}this.failState(e,f(t))}}assertActive(e){if(this.closed||this.states.get(e.options.threadId)!==e||e.phase===a.stopped||e.phase===a.failed)throw new RuntimeEventRecoveryError("Runtime Event recovery was stopped")}failState(e,t){if(e.phase!==a.failed&&e.phase!==a.stopped){e.phase=a.failed,this.clearBuffer(e);try{e.options.onError?.(t,{threadId:e.options.threadId,phase:a.failed})}catch{}}}}
|
|
1
|
+
import{createHash as e}from"node:crypto";import{RUNTIME_ERROR_CODES_V13 as t,RUNTIME_METHODS as r,RUNTIME_V13_DEFAULT_REPLAY_BUFFER_BYTES as n,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as i,compareRuntimeEventCursors as o,runtimeEventCursorDistance as s}from"@roll-agent/protocol";export const DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_EVENTS=i;export const DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_BYTES=n;const a={buffering:"buffering",draining:"draining",live:"live",failed:"failed",stopped:"stopped"},u={resumed:"resumed",snapshotResumed:"snapshot-resumed",snapshotOnly:"snapshot-only"};export const RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS={initial:"initial",runtimeRestarted:"runtime-restarted",cursorExpired:"cursor-expired",cursorGap:"cursor-gap",streamGap:"stream-gap",bufferOverflow:"buffer-overflow",cursorConflict:"cursor-conflict",protocolUnsupported:"protocol-unsupported"};function c(e){return"1.3"===e||"1.4"===e||"1.5"===e}export class RuntimeEventRecoveryError extends Error{constructor(e,t){super(e,t),this.name="RuntimeEventRecoveryError"}}class RuntimeEventStreamIntegrityError extends RuntimeEventRecoveryError{reason;constructor(e,t,r){super(t,r),this.name="RuntimeEventStreamIntegrityError",this.reason=e}}function E(e,t,r){const n=e??t;if(!Number.isSafeInteger(n)||n<=0)throw new Error(`${r} must be a positive safe integer`);return n}function d(e){return h(e).fingerprint}function h(t){const r=JSON.stringify({...t,sequence:void 0});return{fingerprint:e("sha256").update(r).digest("hex"),bytes:Buffer.byteLength(r,"utf8")}}function f(e){return e instanceof Error?e:new Error(String(e))}function v(e){if("object"==typeof e&&null!==e&&"data"in e&&"object"==typeof e.data&&null!==e.data&&"rollCode"in e.data&&"string"==typeof e.data.rollCode)return e.data.rollCode}function l(e){const r=v(e);return r===t.eventCursorExpired?RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorExpired:r===t.eventCursorGap?RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorGap:void 0}function p(e){if(!("eventCursor"in e))throw new RuntimeEventRecoveryError("Runtime Protocol 1.3 snapshot omitted eventCursor");return e.eventCursor}function R(e){if(!("recoveryProjection"in e)||!0!==e.recoveryProjection)throw new RuntimeEventRecoveryError("Runtime Protocol 1.3 recovery Snapshot omitted the bounded recovery projection marker")}function m(e,t){try{return o(e,t)}catch(e){throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event cursors are not comparable",{cause:e})}}function y(e,t){try{return s(e,t)}catch(e){throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event cursor distance is not comparable",{cause:e})}}function I(e){return("1.3"===e.protocolVersion||"1.4"===e.protocolVersion||"1.5"===e.protocolVersion)&&"durable"===e.durability}function S(e){return("1.3"===e.protocolVersion||"1.4"===e.protocolVersion||"1.5"===e.protocolVersion)&&"ephemeral"===e.durability}export class RuntimeEventRecoveryManager{bridge;maxBufferedEvents;maxBufferedBytes;states=new Map;checkpoints=new Map;closed=!1;constructor(e,t={}){this.bridge=e,this.maxBufferedEvents=E(t.maxBufferedEvents,DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_EVENTS,"maxBufferedEvents"),this.maxBufferedBytes=E(t.maxBufferedBytes,DEFAULT_RUNTIME_EVENT_RECOVERY_MAX_BUFFERED_BYTES,"maxBufferedBytes")}isClosed(){return this.closed}getCheckpoint(e){return this.checkpoints.get(e)}async resumeThread(e){if(this.closed)throw new RuntimeEventRecoveryError("Runtime Event recovery manager is closed");if(this.states.has(e.threadId))throw new RuntimeEventRecoveryError(`Runtime Event recovery already owns Thread ${JSON.stringify(e.threadId)}`);if(void 0!==e.checkpoint&&e.checkpoint.threadId!==e.threadId)throw new RuntimeEventRecoveryError("Runtime Event checkpoint belongs to another Thread");const t=this.bridge.getInitializationResult();if(!c(t.protocolVersion)){const r=await this.bridge.requestSnapshot(e.threadId);return await e.applySnapshot(r,{reason:RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.protocolUnsupported,protocolVersion:t.protocolVersion,recoveryProjection:"recoveryProjection"in r&&!0===r.recoveryProjection}),{mode:u.snapshotOnly,protocolVersion:t.protocolVersion,checkpoint:null}}const r={threadId:e.threadId,runtimeInstanceId:t.runtimeInstanceId,cursor:e.checkpoint?.runtimeInstanceId===t.runtimeInstanceId?e.checkpoint.cursor:null},n={options:e,runtimeInstanceId:t.runtimeInstanceId,buffer:[],bufferedByEventId:new Map,bufferedByCursor:new Map,seenByEventId:new Map,seenByCursor:new Map,phase:a.buffering,checkpoint:r,bufferBytes:0,integrityError:void 0,liveDrain:void 0};this.states.set(e.threadId,n),this.checkpoints.set(e.threadId,r);try{let r;if(void 0===e.checkpoint)await this.snapshotAndResume(n,RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.initial),r=u.snapshotResumed;else if(e.checkpoint.runtimeInstanceId!==t.runtimeInstanceId)await this.snapshotAndResume(n,RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.runtimeRestarted),r=u.snapshotResumed;else try{await this.resumeFrom(n,e.checkpoint.cursor),r=u.resumed}catch(e){const t=l(e)??(e instanceof RuntimeEventStreamIntegrityError?e.reason:void 0);if(void 0===t)throw e;await this.snapshotAndResume(n,t),r=u.snapshotResumed}return this.assertActive(n),n.phase=a.live,this.scheduleLiveDrain(n),{mode:r,protocolVersion:t.protocolVersion,checkpoint:n.checkpoint}}catch(t){throw this.failState(n,f(t)),this.states.delete(e.threadId),t}}stopThread(e){const t=this.states.get(e);return void 0!==t&&(t.phase=a.stopped,this.states.delete(e),this.clearBuffer(t),!0)}close(){if(!this.closed){this.closed=!0;for(const e of this.states.values())e.phase=a.stopped,this.clearBuffer(e);this.states.clear()}}acceptEvent(e){const t=this.states.get(e.threadId);if(void 0===t)return!1;if(t.phase===a.stopped||t.phase===a.failed)return!0;if(e.runtimeInstanceId!==t.runtimeInstanceId)return this.markIntegrityFailure(t,new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event belongs to another Runtime instance")),!0;if(S(e)){if(t.phase===a.live)try{t.options.onEphemeralEvent?.(e)}catch{}return!0}return I(e)?(this.bufferDurableEvent(t,e),t.phase===a.live&&this.scheduleLiveDrain(t),!0):(this.markIntegrityFailure(t,new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Claimed Thread received an event outside Runtime Protocol 1.3")),!0)}acceptClientExit(e){for(const t of this.states.values())this.failState(t,e)}async snapshotAndResume(e,t){let r=t;for(let t=0;t<2;t+=1){this.assertActive(e),e.phase=a.buffering;const n=await this.bridge.requestSnapshot(e.options.threadId);this.assertActive(e),R(n),await e.options.applySnapshot(n,{reason:r,protocolVersion:this.bridge.getInitializationResult().protocolVersion,recoveryProjection:!0}),this.assertActive(e);const i=p(n);this.clearBuffer(e),e.seenByEventId.clear(),e.seenByCursor.clear(),e.checkpoint={threadId:e.options.threadId,runtimeInstanceId:e.runtimeInstanceId,cursor:i},this.checkpoints.set(e.options.threadId,e.checkpoint);try{return void await this.resumeFrom(e,i)}catch(e){const n=l(e)??(e instanceof RuntimeEventStreamIntegrityError?e.reason:void 0);if(void 0===n||1===t)throw e;r=n}}throw new RuntimeEventRecoveryError("Runtime Event recovery could not converge on a Snapshot")}async resumeFrom(e,t){let r;this.assertActive(e),e.phase=a.buffering,e.integrityError=void 0;const n=await this.bridge.requestResume({threadId:e.options.threadId,afterCursor:t},e=>{r=e});this.assertActive(e);const i=r??n;e.phase=a.draining,this.throwIntegrityError(e);const o=this.takeBuffer(e),s=this.validateBarrier(o,t,i);for(await this.deliverBatch(e,s,t);e.buffer.length>0;){this.throwIntegrityError(e);const t=this.prepareBatch(e,this.takeBuffer(e));await this.deliverBatch(e,t,e.checkpoint.cursor)}this.throwIntegrityError(e)}validateBarrier(e,t,r){const n=this.prepareBatch(void 0,e).filter(e=>null===t||m(e.cursor,t)>0),i=null===r.throughCursor?[]:n.filter(e=>m(e.cursor,r.throughCursor)<=0);if(i.length!==r.replayedCount)throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.streamGap,`Runtime Event replay announced ${String(r.replayedCount)} events but delivered ${String(i.length)}`);if(0===r.replayedCount){if(0!==m(t,r.throughCursor))throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.streamGap,"Runtime Event replay returned an invalid empty barrier")}else{const e=i.at(-1);if(void 0===e||null===r.throughCursor||0!==m(e.cursor,r.throughCursor))throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.streamGap,"Runtime Event replay did not reach its throughCursor barrier");if(y(t,r.throughCursor)!==BigInt(r.replayedCount))throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.streamGap,"Runtime Event replay barrier contains a cursor gap")}return n}prepareBatch(e,t){return t.sort((e,t)=>m(e.cursor,t.cursor)),void 0===e?t:t.filter(t=>{const r=e.seenByEventId.get(t.eventId);if(void 0!==r){if(r.cursor!==t.cursor||r.fingerprint!==d(t))throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event reused an eventId with conflicting content");return!1}const n=e.seenByCursor.get(t.cursor);if(void 0!==n&&n!==t.eventId)throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event cursor was reused by another eventId");return!0})}async deliverBatch(e,t,r){let n=r;for(const r of t){this.assertActive(e);const t=y(n,r.cursor);if(t<=0n)continue;if(1n!==t)throw new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.streamGap,"Runtime Event stream contains a cursor gap");const i={threadId:e.options.threadId,runtimeInstanceId:e.runtimeInstanceId,cursor:r.cursor};await e.options.onDurableEvent(r,i),this.assertActive(e),e.checkpoint=i,this.checkpoints.set(e.options.threadId,i),this.rememberDelivered(e,r),n=r.cursor}}bufferDurableEvent(e,t){const{fingerprint:r,bytes:n}=h(t),i=e.seenByEventId.get(t.eventId);if(void 0!==i)return void(i.cursor===t.cursor&&i.fingerprint===r||this.markIntegrityFailure(e,new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event reused a delivered eventId with conflicting content")));const o=e.bufferedByEventId.get(t.eventId);if(void 0!==o)return void(o.cursor===t.cursor&&o.fingerprint===r||this.markIntegrityFailure(e,new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event reused a buffered eventId with conflicting content")));const s=e.bufferedByCursor.get(t.cursor)??e.seenByCursor.get(t.cursor);if(void 0===s||s===t.eventId){if(e.buffer.length+1>this.maxBufferedEvents||e.bufferBytes+n>this.maxBufferedBytes)return this.clearBuffer(e),void this.markIntegrityFailure(e,new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.bufferOverflow,"Runtime Event recovery buffer exceeded its bounded capacity"));e.buffer.push(t),e.bufferBytes+=n,e.bufferedByEventId.set(t.eventId,{cursor:t.cursor,fingerprint:r}),e.bufferedByCursor.set(t.cursor,t.eventId)}else this.markIntegrityFailure(e,new RuntimeEventStreamIntegrityError(RUNTIME_EVENT_RECOVERY_SNAPSHOT_REASONS.cursorConflict,"Runtime Event cursor was reused by another eventId"))}takeBuffer(e){const t=e.buffer.splice(0);return e.bufferBytes=0,e.bufferedByEventId.clear(),e.bufferedByCursor.clear(),t}clearBuffer(e){e.buffer.splice(0),e.bufferBytes=0,e.bufferedByEventId.clear(),e.bufferedByCursor.clear(),e.integrityError=void 0}rememberDelivered(e,t){for(e.seenByEventId.set(t.eventId,{cursor:t.cursor,fingerprint:d(t)}),e.seenByCursor.set(t.cursor,t.eventId);e.seenByEventId.size>this.maxBufferedEvents;){const t=e.seenByEventId.keys().next().value;if(void 0===t)break;const r=e.seenByEventId.get(t);e.seenByEventId.delete(t),void 0!==r&&e.seenByCursor.delete(r.cursor)}}markIntegrityFailure(e,t){e.integrityError??=t,e.phase===a.live&&this.scheduleLiveDrain(e)}throwIntegrityError(e){if(void 0!==e.integrityError)throw e.integrityError}scheduleLiveDrain(e){void 0!==e.liveDrain||e.phase!==a.live||0===e.buffer.length&&void 0===e.integrityError||(e.liveDrain=this.drainLive(e).finally(()=>{e.liveDrain=void 0,e.phase===a.live&&this.scheduleLiveDrain(e)}))}async drainLive(e){try{for(this.throwIntegrityError(e);e.buffer.length>0;){const t=this.prepareBatch(e,this.takeBuffer(e));await this.deliverBatch(e,t,e.checkpoint.cursor),this.throwIntegrityError(e)}}catch(t){if(t instanceof RuntimeEventStreamIntegrityError)try{return await this.snapshotAndResume(e,t.reason),this.assertActive(e),void(e.phase=a.live)}catch(t){return void this.failState(e,f(t))}this.failState(e,f(t))}}assertActive(e){if(this.closed||this.states.get(e.options.threadId)!==e||e.phase===a.stopped||e.phase===a.failed)throw new RuntimeEventRecoveryError("Runtime Event recovery was stopped")}failState(e,t){if(e.phase!==a.failed&&e.phase!==a.stopped){e.phase=a.failed,this.clearBuffer(e);try{e.options.onError?.(t,{threadId:e.options.threadId,phase:a.failed})}catch{}}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roll-agent/client-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"node": ">=22.6.0"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@roll-agent/protocol": "0.
|
|
29
|
+
"@roll-agent/protocol": "0.8.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.0.0"
|