@roll-agent/client-node 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/package.json +2 -2
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 p,parseLatestRuntimeMethodParams as f,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,l.approvalPreview]);function U(e){return"1.6"===e||"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&&"1.6"!==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&&p(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&&"1.6"!==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&&"1.6"!==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?f(e,t):_(r,e,t),o=e===l.turnStart?f(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 p,parseLatestRuntimeMethodParams as f,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,C=100,I=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.modelConfigGet,l.threadList,l.threadSnapshot,l.threadCapabilities,l.operationGet,l.operationResultGet,l.approvalPreview]);function U(e){return"1.6"===e||"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 G(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 J(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function K(e){if(!J(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(!J(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??I,this.maxReadRetries=e.maxReadRetries??1,this.readRetryDelayMs=e.readRetryDelayMs??C,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&&"1.6"!==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&&p(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&&"1.6"!==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&&"1.6"!==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?f(e,t):_(r,e,t),o=e===l.turnStart?f(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(!J(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=K(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=G(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{}}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roll-agent/client-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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.12.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.0.0"
|