@roll-agent/runtime 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{randomUUID as e}from"node:crypto";import{RUNTIME_ERROR_CODES as t,RUNTIME_EVENT_NOTIFICATION as i,RUNTIME_METHODS as s,RUNTIME_PROTOCOL_VERSION as r,RUNTIME_SERVER_REQUEST_METHODS as n,RUNTIME_V13_DEFAULT_REPLAY_BUFFER_BYTES as a,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as o,RUNTIME_V13_RECOVERY_SNAPSHOT_METADATA_MAX_CHARS as c,clientCapabilitiesSetParamsSchema as h,getRuntimeProtocolCapabilities as l,getRuntimeProtocolRegistry as p,interactionIdSchema as d,isRuntimeMethodAvailable as u,normalizeUserInputResult as v,parseRuntimeMethodParamsForVersion as m,projectClientCapabilitiesSetResult as b,projectRuntimeEventEnvelopeForVersion as R,projectThreadSnapshotForVersion as f,projectOperationViewForVersion as y,runtimeMethodSchemas as I,runtimeDurableEventV16Schema as g,turnIdSchema as q}from"@roll-agent/protocol";import{RuntimeService as w,RuntimeServiceError as S}from"../service/runtime-service.js";import{RuntimeClientRequestCancelledError as V,RuntimeClientRequestCoordinator as C,RuntimeClientRequestExpiredError as P,createRuntimeClientResponderId as A,getRuntimeClientRequestCoordinatorInternal as T}from"./runtime-client-request-coordinator.js";const U="客户端未完成审批请求,Runtime 已终止当前 Turn",E="客户端未完成用户输入请求",x=new WeakSet,z=o,G=a;function k(e){return`user-input:${e}`}function B(e){switch(e.status){case"resolved":return"approve"===e.decision?"审批已批准":"审批已拒绝";case"cancelled":return e.reason;case"expired":return e.reason??"审批已到期"}}export class RuntimeProtocolAdapter{service;connection;clientRequests;responderId;unsubscribe;unsubscribeFatalError;unsubscribeUserInput;settlementTasks=new Set;detachResponder;initialized=!1;closing=!1;transportFailed=!1;controlsService=!1;protocolVersion;unsubscribeReasoningText;capabilityRevision;capabilityMethods;capabilityResult;replayGates=new Map;eventSequence=0;closePromise;constructor(e,i,s){if(x.has(e))throw new S(t.capabilityUnavailable,"当前 RuntimeService 已绑定 Runtime Protocol Adapter");x.add(e),this.controlsService=!0,this.service=e,this.connection=i,this.clientRequests=s,this.responderId=A();try{this.unsubscribe=e.onEvent(e=>this.handleServiceEvent(e))}catch(e){throw this.releaseServiceControl(),e}try{this.unsubscribeUserInput=e.onUserInputInteraction(e=>this.handleUserInputInteraction(e))}catch(e){throw this.unsubscribe(),this.releaseServiceControl(),e}try{this.unsubscribeFatalError=e.onFatalError(()=>this.closeBrokenTransport())}catch(e){throw this.unsubscribeUserInput(),this.unsubscribe(),this.releaseServiceControl(),e}}dispatchEventResume(e){if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if("1.3"!==this.protocolVersion&&"1.4"!==this.protocolVersion&&"1.5"!==this.protocolVersion&&"1.6"!==this.protocolVersion)throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持方法:${e.method}`);const i=I[s.runtimeEventsResume].params.parse(e.params);if(this.replayGates.has(i.threadId))throw new S(t.threadBusy,`Thread "${i.threadId}" 已有进行中的 Runtime Event replay`,{retryable:!0});const r={threadId:i.threadId,buffered:[],bufferedBytes:0};this.replayGates.set(i.threadId,r);try{const e=this.service.resumeEvents(i);for(const t of e.events){const e={protocolVersion:"1.6",runtimeInstanceId:this.service.runtimeInstanceId,sequence:this.eventSequence,timestamp:t.timestamp,threadId:i.threadId,...void 0===t.turnId?{}:{turnId:q.parse(t.turnId)},durability:"durable",eventId:t.eventId,cursor:t.cursor,event:g.parse(t.event)};if(!this.sendEvent(e))throw new Error("Runtime Event replay transport write failed")}return{result:{throughCursor:e.throughCursor,replayedCount:e.replayedCount},afterResponse:e=>{e?this.flushReplayGate(r):this.releaseReplayGate(r)}}}catch(e){throw this.releaseReplayGate(r),e}}handles(e){return u(r,e)}handleResponse(e){if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequests||void 0===this.detachResponder)return!1;return T(this.clientRequests).handleResponseForAttachment(this.detachResponder,e)??this.clientRequests.handleResponse(this.responderId,e)}async dispatch(e){return e.method===s.runtimeEventsResume?this.dispatchEventResume(e):e.method===s.clientCapabilitiesSet?this.dispatchClientCapabilitiesSet(e):{result:await this.dispatchValue(e)}}dispatchClientCapabilitiesSet(e){if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequestCapabilityNegotiation)throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持 capability negotiation:${e.method}`);return this.setClientCapabilities(h.parse(e.params))}async dispatchValue(e){if(!this.handles(e.method))throw new S(t.capabilityUnavailable,`未知 Runtime Protocol 方法:${e.method}`);if(e.method===s.initialize){if(this.initialized)throw new S(t.capabilityUnavailable,"当前连接已完成 initialize,不能重新协商 Runtime Protocol");const i=I[s.initialize].params.parse(e.params),r=this.service.initialize(i);if(this.protocolVersion=r.protocolVersion,l(r.protocolVersion).serverRequests){const e=l(r.protocolVersion).serverRequestCapabilityNegotiation;if(this.detachResponder=this.clientRequests.attachResponder({id:this.responderId,scopeId:r.runtimeInstanceId,send:e=>this.connection.send(e),close:()=>this.connection.close()},{acceptedServerRequestMethods:e?[]:p(r.protocolVersion).serverRequestMethods,capabilitiesAcknowledged:!e}),e){if(!(T(this.clientRequests).beginCapabilityNegotiationForAttachment(this.detachResponder)??this.clientRequests.beginResponderCapabilityNegotiation(this.responderId)))throw new S(t.capabilityUnavailable,"Runtime 客户端 responder 无法进入 capability negotiation")}}return this.initialized=!0,r}if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if(void 0===this.protocolVersion||!u(this.protocolVersion,e.method))throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持方法:${e.method}`);if(e.method===s.approvalRespond&&void 0!==this.protocolVersion&&!l(this.protocolVersion).clientApprovalResponses)throw new S(t.capabilityUnavailable,"当前协商协议的审批只能通过 approval.request 响应完成");switch(e.method){case s.reasoningTextSubscribe:return I[s.reasoningTextSubscribe].params.parse(e.params),this.unsubscribeReasoningText??=this.service.subscribeReasoningText(e=>this.handleServiceEvent(e)),{subscribed:void 0!==this.unsubscribeReasoningText};case s.threadList:return this.service.listThreads(I[s.threadList].params.parse(e.params));case s.threadCreate:return this.service.createThread(I[s.threadCreate].params.parse(e.params));case s.threadOpen:return this.projectThreadSnapshot(await this.service.openThread(I[s.threadOpen].params.parse(e.params)));case s.threadSnapshot:{const t=m(this.protocolVersion,s.threadSnapshot,e.params),i=("1.3"===this.protocolVersion||"1.4"===this.protocolVersion||"1.5"===this.protocolVersion||"1.6"===this.protocolVersion)&&"recovery"in t&&!0===t.recovery;return this.projectThreadSnapshot(this.service.snapshotThread({threadId:t.threadId,...void 0===t.messageBeforeSequence?{}:{messageBeforeSequence:t.messageBeforeSequence},...void 0===t.operationBeforeSequence?{}:{operationBeforeSequence:t.operationBeforeSequence},limit:t.limit}),i)}case s.threadRename:return this.service.renameThread(I[s.threadRename].params.parse(e.params));case s.threadDelete:return this.service.deleteThread(I[s.threadDelete].params.parse(e.params));case s.threadDetach:return this.service.detachThread(I[s.threadDetach].params.parse(e.params));case s.threadCapabilities:return this.service.threadCapabilities(I[s.threadCapabilities].params.parse(e.params));case s.turnStart:return this.service.startTurn(m(this.protocolVersion,s.turnStart,e.params));case s.turnCancel:return this.service.cancelTurn(I[s.turnCancel].params.parse(e.params));case s.approvalRespond:return this.service.respondApproval(m(this.protocolVersion,s.approvalRespond,e.params));case s.approvalPreview:return this.service.getApprovalPreview(I[s.approvalPreview].params.parse(e.params));case s.operationGet:{const t=this.service.getOperation(I[s.operationGet].params.parse(e.params));return{operation:null===t.operation?null:y(this.protocolVersion,t.operation)}}case s.operationResultGet:return this.service.getOperationResult(I[s.operationResultGet].params.parse(e.params));case s.attachmentStage:return this.service.stageAttachment(I[s.attachmentStage].params.parse(e.params));case s.attachmentChunk:return this.service.appendAttachmentChunk(I[s.attachmentChunk].params.parse(e.params));case s.attachmentCommit:return this.service.commitAttachment(I[s.attachmentCommit].params.parse(e.params));case s.attachmentRelease:return this.service.releaseAttachment(I[s.attachmentRelease].params.parse(e.params));case s.runtimeEventsResume:throw new Error("runtime.events.resume requires the response-barrier dispatch path");case s.clientCapabilitiesSet:throw new Error("client.capabilities.set requires the response-barrier dispatch path")}}async close(){this.closePromise??=this.performClose(),await this.closePromise}disposeConstructionFailure(){this.closing=!0,this.unsubscribeReasoningText?.(),this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput(),this.releaseServiceControl()}releaseServiceControl(){this.controlsService&&(x.delete(this.service),this.controlsService=!1)}async performClose(){for(this.closing=!0,this.replayGates.clear(),this.service.setUserInputAvailable(!1),this.detachResponder?.(),this.detachResponder=void 0;this.settlementTasks.size>0;)await Promise.allSettled([...this.settlementTasks]);this.unsubscribeReasoningText?.(),this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput()}handleServiceEvent(e){if(!this.initialized||this.closing||void 0===this.protocolVersion)return;const t=this.replayGates.get(e.threadId);void 0===t?this.deliverLiveEvent(e):this.bufferReplayLiveEvent(t,e)||this.closeBrokenTransport()}deliverLiveEvent(e){if(!this.initialized||this.closing||void 0===this.protocolVersion)return!1;const t=l(this.protocolVersion);return t.serverRequests&&"approval.resolved"===e.event.type&&this.clientRequests.cancel(e.event.approvalId,B(e.event.resolution)),t.serverRequests&&"approval.required"===e.event.type&&this.requestApproval(e.threadId,e.event.approval),this.sendEvent(e)}sendEvent(e){if(void 0===this.protocolVersion)return!1;if(!l(this.protocolVersion).approvalResolvedEvents&&"approval.resolved"===e.event.type)return!0;const t={...e,sequence:Math.max(this.eventSequence,e.sequence)};this.eventSequence=t.sequence+1;const s=R(this.protocolVersion,t);try{return this.connection.send({jsonrpc:"2.0",method:i,params:s}),!0}catch{return this.closeBrokenTransport(),!1}}bufferReplayLiveEvent(e,t){const i=Buffer.byteLength(JSON.stringify(t),"utf8");return e.buffered.length>=z||e.bufferedBytes+i>G?(this.releaseReplayGate(e),!1):(e.buffered.push(t),e.bufferedBytes+=i,!0)}flushReplayGate(e){if(this.replayGates.get(e.threadId)!==e)return;let t=0;for(;t<e.buffered.length;){const i=e.buffered[t];if(t+=1,void 0!==i&&!this.deliverLiveEvent(i))return void this.releaseReplayGate(e)}this.releaseReplayGate(e)}releaseReplayGate(e){this.replayGates.get(e.threadId)===e&&this.replayGates.delete(e.threadId),e.buffered.length=0,e.bufferedBytes=0}closeBrokenTransport(){if(!this.closing){this.transportFailed=!0,this.closing=!0,this.replayGates.clear(),this.service.setUserInputAvailable(!1),this.detachResponder?.(),this.detachResponder=void 0,this.unsubscribeReasoningText?.(),this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput();try{this.connection.close()}catch{}}}projectThreadSnapshot(e,i=!1){const s=this.protocolVersion;if(void 0===s)throw new S(t.initializeRequired,"投影 Thread Snapshot 前必须先完成 initialize");let r=[];if(!i&&l(s).serverRequestCapabilityNegotiation){if(void 0===this.detachResponder)throw new S(t.capabilityUnavailable,"Runtime Protocol 1.2 responder 已失效");const i=T(this.clientRequests).getPendingInteractionProjectionsForAttachment(this.detachResponder,e.thread.id)??this.clientRequests.getPendingInteractionProjections(this.responderId,e.thread.id);if(void 0===i)throw new S(t.capabilityUnavailable,"Runtime Protocol 1.2 responder attachment 已失效");r=i}if(i){const t=e=>e?.slice(0,c),i=t(e.thread.title),r=t(e.thread.model);return f(s,{...e,thread:{...e.thread,...void 0===i?{}:{title:i},...void 0===r?{}:{model:r}},messages:{items:[],nextBeforeSequence:null},operations:{items:[],nextBeforeSequence:null},pendingApprovals:[],pendingInteractions:[],recoveryProjection:!0})}return f(s,{...e,pendingInteractions:r})}handleUserInputInteraction(e){"settled"!==e.type?!this.closing&&this.initialized&&void 0!==this.protocolVersion&&l(this.protocolVersion).serverRequestCapabilityNegotiation?this.requestUserInput(e):this.service.cancelPendingUserInput(e.requestId,E):this.clientRequests.cancel(k(e.requestId),e.reason)}requestUserInput(t){const i=this.protocolVersion;if(void 0===i||!l(i).serverRequestCapabilityNegotiation)return void this.service.cancelPendingUserInput(t.requestId,E);const s={interactionId:d.parse(e()),threadId:t.threadId,turnId:t.turnId,expiresAt:t.expiresAt,sensitivity:"normal",...t.form};let r;try{r=this.clientRequests.request(n.userInputRequest,s,{key:k(t.requestId),scopeId:this.service.runtimeInstanceId,eligibleResponderId:this.responderId,protocolVersion:i,expiresAt:t.expiresAt})}catch{return void this.service.cancelPendingUserInput(t.requestId,E)}const a=r.result.then(e=>{try{const i=v(s,e);this.service.resolvePendingUserInput(t.requestId,i)}catch{this.service.cancelPendingUserInput(t.requestId,"客户端返回的用户输入不符合原始表单约束")}},e=>{const i=e instanceof P?"用户输入请求已超时":e instanceof V?e.reason:E;this.service.cancelPendingUserInput(t.requestId,i)});this.trackSettlement(a)}requestApproval(i,s){const r={threadId:i,turnId:s.turnId,approvalId:s.id};let a;try{const t=this.protocolVersion;if(void 0===t||"1.0"===t)throw new V("当前协议不支持 Server Request");const o=this.service.getPendingApprovalExpiresAt(r),c=l(t).serverRequestCapabilityNegotiation,h=c?(()=>{if(void 0===o)throw new V("Runtime Protocol 1.2 审批请求缺少绝对 expiresAt deadline");return o})():void 0,p=c?{interactionId:d.parse(e()),threadId:i,turnId:s.turnId,expiresAt:h,sensitivity:"normal",approval:s}:{threadId:i,approval:s};a=this.clientRequests.request(n.approvalRequest,p,{key:s.id,scopeId:this.service.runtimeInstanceId,eligibleResponderId:this.responderId,approvalId:s.id,protocolVersion:t,...void 0===h?{}:{expiresAt:h}})}catch{const e=Promise.resolve().then(()=>this.service.failPendingApprovalInteraction(r,U)).then(()=>{});return void this.trackSettlement(e)}const o=a.result.then(async e=>{const i="approve"===e.decision?{decision:"approve"}:{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}};try{this.service.resolvePendingApproval(r,i)}catch(e){e instanceof S&&e.rollCode===t.approvalNotFound||await this.service.failPendingApprovalInteraction(r,U)}},async e=>{if(this.closing&&!this.transportFailed)return;if(e instanceof P)return void await this.service.failPendingApprovalInteraction(r,"审批请求已到期",{status:"expired",reason:"审批请求已到期"});const t=e instanceof V?e.reason:U;await this.service.failPendingApprovalInteraction(r,t)});this.trackSettlement(o)}setClientCapabilities(e){if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequestCapabilityNegotiation||void 0===this.detachResponder)throw new S(t.capabilityUnavailable,"client.capabilities.set 仅可用于支持 capability negotiation 的 Runtime Protocol");const i=[...e.serverRequestMethods].sort();if(void 0!==this.capabilityRevision){const s=e.revision===this.capabilityRevision,r=i.length===this.capabilityMethods?.length&&i.every((e,t)=>e===this.capabilityMethods?.[t]);if(s&&r&&void 0!==this.capabilityResult)return{result:this.capabilityResult};if(e.revision<=this.capabilityRevision)throw new S(t.capabilityRevisionConflict,`Capability revision ${String(e.revision)} 与当前 revision ${String(this.capabilityRevision)} 冲突`)}const s=b(e),r=`Runtime 客户端已在 capability revision ${String(e.revision)} 撤销处理能力`,a=T(this.clientRequests),o=a.setServerRequestMethodsForAttachment(this.detachResponder,s.acceptedServerRequestMethods,r)??a.setServerRequestMethodsForResponder(this.responderId,s.acceptedServerRequestMethods,r);if(!1===o)throw new S(t.capabilityUnavailable,"Runtime 客户端 responder 已失效");return this.service.setUserInputAvailable(s.acceptedServerRequestMethods.includes(n.userInputRequest)),this.capabilityRevision=e.revision,this.capabilityMethods=i,this.capabilityResult=s,{result:s,afterResponse:e=>{e&&o()}}}trackSettlement(e){this.settlementTasks.add(e),e.finally(()=>this.settlementTasks.delete(e)).catch(()=>{})}}
1
+ import{randomUUID as e}from"node:crypto";import{RUNTIME_ERROR_CODES as t,RUNTIME_EVENT_NOTIFICATION as i,RUNTIME_METHODS as s,RUNTIME_PROTOCOL_VERSION as r,RUNTIME_SERVER_REQUEST_METHODS as a,RUNTIME_V13_DEFAULT_REPLAY_BUFFER_BYTES as n,RUNTIME_V13_MAX_DURABLE_EVENT_RECORDS as o,RUNTIME_V13_RECOVERY_SNAPSHOT_METADATA_MAX_CHARS as c,clientCapabilitiesSetParamsSchema as h,getRuntimeProtocolCapabilities as l,getRuntimeProtocolRegistry as p,interactionIdSchema as d,isRuntimeMethodAvailable as u,normalizeUserInputResult as v,parseRuntimeMethodParamsForVersion as m,projectClientCapabilitiesSetResult as b,projectRuntimeEventEnvelopeForVersion as R,projectThreadSnapshotForVersion as f,projectOperationViewForVersion as y,runtimeMethodSchemas as I,runtimeDurableEventV16Schema as g,turnIdSchema as q}from"@roll-agent/protocol";import{RuntimeService as w,RuntimeServiceError as S}from"../service/runtime-service.js";import{RuntimeClientRequestCancelledError as C,RuntimeClientRequestCoordinator as V,RuntimeClientRequestExpiredError as P,createRuntimeClientResponderId as A,getRuntimeClientRequestCoordinatorInternal as T}from"./runtime-client-request-coordinator.js";const U="客户端未完成审批请求,Runtime 已终止当前 Turn",E="客户端未完成用户输入请求",x=new WeakSet,G=o,z=n;function k(e){return`user-input:${e}`}function B(e){switch(e.status){case"resolved":return"approve"===e.decision?"审批已批准":"审批已拒绝";case"cancelled":return e.reason;case"expired":return e.reason??"审批已到期"}}export class RuntimeProtocolAdapter{service;connection;clientRequests;responderId;unsubscribe;unsubscribeFatalError;unsubscribeUserInput;settlementTasks=new Set;detachResponder;initialized=!1;closing=!1;transportFailed=!1;controlsService=!1;protocolVersion;unsubscribeReasoningText;capabilityRevision;capabilityMethods;capabilityResult;replayGates=new Map;eventSequence=0;closePromise;constructor(e,i,s){if(x.has(e))throw new S(t.capabilityUnavailable,"当前 RuntimeService 已绑定 Runtime Protocol Adapter");x.add(e),this.controlsService=!0,this.service=e,this.connection=i,this.clientRequests=s,this.responderId=A();try{this.unsubscribe=e.onEvent(e=>this.handleServiceEvent(e))}catch(e){throw this.releaseServiceControl(),e}try{this.unsubscribeUserInput=e.onUserInputInteraction(e=>this.handleUserInputInteraction(e))}catch(e){throw this.unsubscribe(),this.releaseServiceControl(),e}try{this.unsubscribeFatalError=e.onFatalError(()=>this.closeBrokenTransport())}catch(e){throw this.unsubscribeUserInput(),this.unsubscribe(),this.releaseServiceControl(),e}}dispatchEventResume(e){if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if("1.3"!==this.protocolVersion&&"1.4"!==this.protocolVersion&&"1.5"!==this.protocolVersion&&"1.6"!==this.protocolVersion)throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持方法:${e.method}`);const i=I[s.runtimeEventsResume].params.parse(e.params);if(this.replayGates.has(i.threadId))throw new S(t.threadBusy,`Thread "${i.threadId}" 已有进行中的 Runtime Event replay`,{retryable:!0});const r={threadId:i.threadId,buffered:[],bufferedBytes:0};this.replayGates.set(i.threadId,r);try{const e=this.service.resumeEvents(i);for(const t of e.events){const e={protocolVersion:"1.6",runtimeInstanceId:this.service.runtimeInstanceId,sequence:this.eventSequence,timestamp:t.timestamp,threadId:i.threadId,...void 0===t.turnId?{}:{turnId:q.parse(t.turnId)},durability:"durable",eventId:t.eventId,cursor:t.cursor,event:g.parse(t.event)};if(!this.sendEvent(e))throw new Error("Runtime Event replay transport write failed")}return{result:{throughCursor:e.throughCursor,replayedCount:e.replayedCount},afterResponse:e=>{e?this.flushReplayGate(r):this.releaseReplayGate(r)}}}catch(e){throw this.releaseReplayGate(r),e}}handles(e){return u(r,e)}handleResponse(e){if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequests||void 0===this.detachResponder)return!1;return T(this.clientRequests).handleResponseForAttachment(this.detachResponder,e)??this.clientRequests.handleResponse(this.responderId,e)}async dispatch(e){return e.method===s.runtimeEventsResume?this.dispatchEventResume(e):e.method===s.clientCapabilitiesSet?this.dispatchClientCapabilitiesSet(e):{result:await this.dispatchValue(e)}}dispatchClientCapabilitiesSet(e){if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequestCapabilityNegotiation)throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持 capability negotiation:${e.method}`);return this.setClientCapabilities(h.parse(e.params))}async dispatchValue(e){if(!this.handles(e.method))throw new S(t.capabilityUnavailable,`未知 Runtime Protocol 方法:${e.method}`);if(e.method===s.initialize){if(this.initialized)throw new S(t.capabilityUnavailable,"当前连接已完成 initialize,不能重新协商 Runtime Protocol");const i=I[s.initialize].params.parse(e.params),r=this.service.initialize(i);if(this.protocolVersion=r.protocolVersion,l(r.protocolVersion).serverRequests){const e=l(r.protocolVersion).serverRequestCapabilityNegotiation;if(this.detachResponder=this.clientRequests.attachResponder({id:this.responderId,scopeId:r.runtimeInstanceId,send:e=>this.connection.send(e),close:()=>this.connection.close()},{acceptedServerRequestMethods:e?[]:p(r.protocolVersion).serverRequestMethods,capabilitiesAcknowledged:!e}),e){if(!(T(this.clientRequests).beginCapabilityNegotiationForAttachment(this.detachResponder)??this.clientRequests.beginResponderCapabilityNegotiation(this.responderId)))throw new S(t.capabilityUnavailable,"Runtime 客户端 responder 无法进入 capability negotiation")}}return this.initialized=!0,r}if(!this.initialized)throw new S(t.initializeRequired,"调用 Runtime Protocol 方法前必须先完成 initialize");if(void 0===this.protocolVersion||!u(this.protocolVersion,e.method))throw new S(t.capabilityUnavailable,`Runtime Protocol ${String(this.protocolVersion)} 不支持方法:${e.method}`);if(e.method===s.approvalRespond&&void 0!==this.protocolVersion&&!l(this.protocolVersion).clientApprovalResponses)throw new S(t.capabilityUnavailable,"当前协商协议的审批只能通过 approval.request 响应完成");switch(e.method){case s.modelConfigGet:return I[s.modelConfigGet].params.parse(e.params),this.service.getModelConfig();case s.modelConfigReload:return this.service.reloadModelConfig(I[s.modelConfigReload].params.parse(e.params));case s.reasoningTextSubscribe:return I[s.reasoningTextSubscribe].params.parse(e.params),this.unsubscribeReasoningText??=this.service.subscribeReasoningText(e=>this.handleServiceEvent(e)),{subscribed:void 0!==this.unsubscribeReasoningText};case s.threadList:return this.service.listThreads(I[s.threadList].params.parse(e.params));case s.threadCreate:return this.service.createThread(I[s.threadCreate].params.parse(e.params));case s.threadOpen:return this.projectThreadSnapshot(await this.service.openThread(I[s.threadOpen].params.parse(e.params)));case s.threadSnapshot:{const t=m(this.protocolVersion,s.threadSnapshot,e.params),i=("1.3"===this.protocolVersion||"1.4"===this.protocolVersion||"1.5"===this.protocolVersion||"1.6"===this.protocolVersion)&&"recovery"in t&&!0===t.recovery;return this.projectThreadSnapshot(this.service.snapshotThread({threadId:t.threadId,...void 0===t.messageBeforeSequence?{}:{messageBeforeSequence:t.messageBeforeSequence},...void 0===t.operationBeforeSequence?{}:{operationBeforeSequence:t.operationBeforeSequence},limit:t.limit}),i)}case s.threadRename:return this.service.renameThread(I[s.threadRename].params.parse(e.params));case s.threadDelete:return this.service.deleteThread(I[s.threadDelete].params.parse(e.params));case s.threadDetach:return this.service.detachThread(I[s.threadDetach].params.parse(e.params));case s.threadCapabilities:return this.service.threadCapabilities(I[s.threadCapabilities].params.parse(e.params));case s.turnStart:return this.service.startTurn(m(this.protocolVersion,s.turnStart,e.params));case s.turnCancel:return this.service.cancelTurn(I[s.turnCancel].params.parse(e.params));case s.approvalRespond:return this.service.respondApproval(m(this.protocolVersion,s.approvalRespond,e.params));case s.approvalPreview:return this.service.getApprovalPreview(I[s.approvalPreview].params.parse(e.params));case s.operationGet:{const t=this.service.getOperation(I[s.operationGet].params.parse(e.params));return{operation:null===t.operation?null:y(this.protocolVersion,t.operation)}}case s.operationResultGet:return this.service.getOperationResult(I[s.operationResultGet].params.parse(e.params));case s.attachmentStage:return this.service.stageAttachment(I[s.attachmentStage].params.parse(e.params));case s.attachmentChunk:return this.service.appendAttachmentChunk(I[s.attachmentChunk].params.parse(e.params));case s.attachmentCommit:return this.service.commitAttachment(I[s.attachmentCommit].params.parse(e.params));case s.attachmentRelease:return this.service.releaseAttachment(I[s.attachmentRelease].params.parse(e.params));case s.runtimeEventsResume:throw new Error("runtime.events.resume requires the response-barrier dispatch path");case s.clientCapabilitiesSet:throw new Error("client.capabilities.set requires the response-barrier dispatch path")}}async close(){this.closePromise??=this.performClose(),await this.closePromise}disposeConstructionFailure(){this.closing=!0,this.unsubscribeReasoningText?.(),this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput(),this.releaseServiceControl()}releaseServiceControl(){this.controlsService&&(x.delete(this.service),this.controlsService=!1)}async performClose(){for(this.closing=!0,this.replayGates.clear(),this.service.setUserInputAvailable(!1),this.detachResponder?.(),this.detachResponder=void 0;this.settlementTasks.size>0;)await Promise.allSettled([...this.settlementTasks]);this.unsubscribeReasoningText?.(),this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput()}handleServiceEvent(e){if(!this.initialized||this.closing||void 0===this.protocolVersion)return;const t=this.replayGates.get(e.threadId);void 0===t?this.deliverLiveEvent(e):this.bufferReplayLiveEvent(t,e)||this.closeBrokenTransport()}deliverLiveEvent(e){if(!this.initialized||this.closing||void 0===this.protocolVersion)return!1;const t=l(this.protocolVersion);return t.serverRequests&&"approval.resolved"===e.event.type&&this.clientRequests.cancel(e.event.approvalId,B(e.event.resolution)),t.serverRequests&&"approval.required"===e.event.type&&this.requestApproval(e.threadId,e.event.approval),this.sendEvent(e)}sendEvent(e){if(void 0===this.protocolVersion)return!1;if(!l(this.protocolVersion).approvalResolvedEvents&&"approval.resolved"===e.event.type)return!0;const t={...e,sequence:Math.max(this.eventSequence,e.sequence)};this.eventSequence=t.sequence+1;const s=R(this.protocolVersion,t);try{return this.connection.send({jsonrpc:"2.0",method:i,params:s}),!0}catch{return this.closeBrokenTransport(),!1}}bufferReplayLiveEvent(e,t){const i=Buffer.byteLength(JSON.stringify(t),"utf8");return e.buffered.length>=G||e.bufferedBytes+i>z?(this.releaseReplayGate(e),!1):(e.buffered.push(t),e.bufferedBytes+=i,!0)}flushReplayGate(e){if(this.replayGates.get(e.threadId)!==e)return;let t=0;for(;t<e.buffered.length;){const i=e.buffered[t];if(t+=1,void 0!==i&&!this.deliverLiveEvent(i))return void this.releaseReplayGate(e)}this.releaseReplayGate(e)}releaseReplayGate(e){this.replayGates.get(e.threadId)===e&&this.replayGates.delete(e.threadId),e.buffered.length=0,e.bufferedBytes=0}closeBrokenTransport(){if(!this.closing){this.transportFailed=!0,this.closing=!0,this.replayGates.clear(),this.service.setUserInputAvailable(!1),this.detachResponder?.(),this.detachResponder=void 0,this.unsubscribeReasoningText?.(),this.unsubscribeFatalError(),this.unsubscribe(),this.unsubscribeUserInput();try{this.connection.close()}catch{}}}projectThreadSnapshot(e,i=!1){const s=this.protocolVersion;if(void 0===s)throw new S(t.initializeRequired,"投影 Thread Snapshot 前必须先完成 initialize");let r=[];if(!i&&l(s).serverRequestCapabilityNegotiation){if(void 0===this.detachResponder)throw new S(t.capabilityUnavailable,"Runtime Protocol 1.2 responder 已失效");const i=T(this.clientRequests).getPendingInteractionProjectionsForAttachment(this.detachResponder,e.thread.id)??this.clientRequests.getPendingInteractionProjections(this.responderId,e.thread.id);if(void 0===i)throw new S(t.capabilityUnavailable,"Runtime Protocol 1.2 responder attachment 已失效");r=i}if(i){const t=e=>e?.slice(0,c),i=t(e.thread.title),r=t(e.thread.model);return f(s,{...e,thread:{...e.thread,...void 0===i?{}:{title:i},...void 0===r?{}:{model:r}},messages:{items:[],nextBeforeSequence:null},operations:{items:[],nextBeforeSequence:null},pendingApprovals:[],pendingInteractions:[],recoveryProjection:!0})}return f(s,{...e,pendingInteractions:r})}handleUserInputInteraction(e){"settled"!==e.type?!this.closing&&this.initialized&&void 0!==this.protocolVersion&&l(this.protocolVersion).serverRequestCapabilityNegotiation?this.requestUserInput(e):this.service.cancelPendingUserInput(e.requestId,E):this.clientRequests.cancel(k(e.requestId),e.reason)}requestUserInput(t){const i=this.protocolVersion;if(void 0===i||!l(i).serverRequestCapabilityNegotiation)return void this.service.cancelPendingUserInput(t.requestId,E);const s={interactionId:d.parse(e()),threadId:t.threadId,turnId:t.turnId,expiresAt:t.expiresAt,sensitivity:"normal",...t.form};let r;try{r=this.clientRequests.request(a.userInputRequest,s,{key:k(t.requestId),scopeId:this.service.runtimeInstanceId,eligibleResponderId:this.responderId,protocolVersion:i,expiresAt:t.expiresAt})}catch{return void this.service.cancelPendingUserInput(t.requestId,E)}const n=r.result.then(e=>{try{const i=v(s,e);this.service.resolvePendingUserInput(t.requestId,i)}catch{this.service.cancelPendingUserInput(t.requestId,"客户端返回的用户输入不符合原始表单约束")}},e=>{const i=e instanceof P?"用户输入请求已超时":e instanceof C?e.reason:E;this.service.cancelPendingUserInput(t.requestId,i)});this.trackSettlement(n)}requestApproval(i,s){const r={threadId:i,turnId:s.turnId,approvalId:s.id};let n;try{const t=this.protocolVersion;if(void 0===t||"1.0"===t)throw new C("当前协议不支持 Server Request");const o=this.service.getPendingApprovalExpiresAt(r),c=l(t).serverRequestCapabilityNegotiation,h=c?(()=>{if(void 0===o)throw new C("Runtime Protocol 1.2 审批请求缺少绝对 expiresAt deadline");return o})():void 0,p=c?{interactionId:d.parse(e()),threadId:i,turnId:s.turnId,expiresAt:h,sensitivity:"normal",approval:s}:{threadId:i,approval:s};n=this.clientRequests.request(a.approvalRequest,p,{key:s.id,scopeId:this.service.runtimeInstanceId,eligibleResponderId:this.responderId,approvalId:s.id,protocolVersion:t,...void 0===h?{}:{expiresAt:h}})}catch{const e=Promise.resolve().then(()=>this.service.failPendingApprovalInteraction(r,U)).then(()=>{});return void this.trackSettlement(e)}const o=n.result.then(async e=>{const i="approve"===e.decision?{decision:"approve"}:{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}};try{this.service.resolvePendingApproval(r,i)}catch(e){e instanceof S&&e.rollCode===t.approvalNotFound||await this.service.failPendingApprovalInteraction(r,U)}},async e=>{if(this.closing&&!this.transportFailed)return;if(e instanceof P)return void await this.service.failPendingApprovalInteraction(r,"审批请求已到期",{status:"expired",reason:"审批请求已到期"});const t=e instanceof C?e.reason:U;await this.service.failPendingApprovalInteraction(r,t)});this.trackSettlement(o)}setClientCapabilities(e){if(void 0===this.protocolVersion||!l(this.protocolVersion).serverRequestCapabilityNegotiation||void 0===this.detachResponder)throw new S(t.capabilityUnavailable,"client.capabilities.set 仅可用于支持 capability negotiation 的 Runtime Protocol");const i=[...e.serverRequestMethods].sort();if(void 0!==this.capabilityRevision){const s=e.revision===this.capabilityRevision,r=i.length===this.capabilityMethods?.length&&i.every((e,t)=>e===this.capabilityMethods?.[t]);if(s&&r&&void 0!==this.capabilityResult)return{result:this.capabilityResult};if(e.revision<=this.capabilityRevision)throw new S(t.capabilityRevisionConflict,`Capability revision ${String(e.revision)} 与当前 revision ${String(this.capabilityRevision)} 冲突`)}const s=b(e),r=`Runtime 客户端已在 capability revision ${String(e.revision)} 撤销处理能力`,n=T(this.clientRequests),o=n.setServerRequestMethodsForAttachment(this.detachResponder,s.acceptedServerRequestMethods,r)??n.setServerRequestMethodsForResponder(this.responderId,s.acceptedServerRequestMethods,r);if(!1===o)throw new S(t.capabilityUnavailable,"Runtime 客户端 responder 已失效");return this.service.setUserInputAvailable(s.acceptedServerRequestMethods.includes(a.userInputRequest)),this.capabilityRevision=e.revision,this.capabilityMethods=i,this.capabilityResult=s,{result:s,afterResponse:e=>{e&&o()}}}trackSettlement(e){this.settlementTasks.add(e),e.finally(()=>this.settlementTasks.delete(e)).catch(()=>{})}}
@@ -1,4 +1,4 @@
1
- import { RUNTIME_METHODS, approvalIdSchema, type ApprovalResolution, type InitializeParams, type InitializeResult, type RuntimeEventEnvelopeV16, type RuntimeEventsResumeParams, type RuntimeEventsResumeResult, type RuntimeInstanceId, type LatestRuntimeMethodParams, type LatestRuntimeMethodResult, type RuntimeMethodParams, type RuntimeMethodResult, type RuntimeProtocolErrorDataV14, type ThreadSnapshotV16Full, type ThreadId, type TurnId, type UserInputForm, type UserInputResult } from "@roll-agent/protocol";
1
+ import { RUNTIME_METHODS, approvalIdSchema, type ApprovalResolution, type InitializeParams, type InitializeResult, type RuntimeEventEnvelopeV16, type RuntimeEventsResumeParams, type RuntimeEventsResumeResult, type RuntimeInstanceId, type RuntimeModelConfigFailure, type RuntimeModelConfigIdentity, type RuntimeModelConfigStatus, type LatestRuntimeMethodParams, type LatestRuntimeMethodResult, type RuntimeMethodParams, type RuntimeMethodResult, type RuntimeProtocolErrorDataV14, type ThreadSnapshotV16Full, type ThreadId, type TurnId, type UserInputForm, type UserInputResult } from "@roll-agent/protocol";
2
2
  import type { AgentSession } from "../engine/agent-session.ts";
3
3
  import { AttachmentStore } from "./attachment-store.ts";
4
4
  import type { SessionUserInputRequestId } from "../interaction/user-input-interaction-manager.ts";
@@ -12,6 +12,18 @@ export interface RuntimeServiceEngine {
12
12
  }): Promise<RuntimeServiceSession>;
13
13
  resumeSession(threadId: string): Promise<RuntimeServiceSession>;
14
14
  }
15
+ /** The host rereads its bound source and applies a model without replacing the engine. */
16
+ export interface RuntimeModelConfigHost {
17
+ readonly initial: RuntimeModelConfigIdentity;
18
+ reload(): {
19
+ readonly status: "applied";
20
+ readonly model: RuntimeModelConfigIdentity;
21
+ readonly reasoning?: ReasoningPresentation["kind"];
22
+ } | {
23
+ readonly status: "failed";
24
+ readonly reason: RuntimeModelConfigFailure;
25
+ };
26
+ }
15
27
  export interface RuntimeServiceOptions {
16
28
  readonly serverName?: string;
17
29
  readonly serverVersion?: string;
@@ -20,6 +32,7 @@ export interface RuntimeServiceOptions {
20
32
  readonly idempotencyCacheEntries?: number;
21
33
  /** Accept only source-classified deltas of this kind. Text requires a separate local subscription. */
22
34
  readonly providerReasoning?: ReasoningPresentation["kind"];
35
+ readonly modelConfig?: RuntimeModelConfigHost;
23
36
  /**
24
37
  * Optional host-controlled projection. Raw model reasoning is never sent to public clients.
25
38
  * Supplying this enables the `reasoning-summary` capability.
@@ -67,7 +80,7 @@ export declare class RuntimeServiceError extends Error {
67
80
  readonly details?: unknown;
68
81
  });
69
82
  }
70
- type MutationRuntimeMethod = typeof RUNTIME_METHODS.threadCreate | typeof RUNTIME_METHODS.threadRename | typeof RUNTIME_METHODS.threadDelete | typeof RUNTIME_METHODS.threadDetach | typeof RUNTIME_METHODS.turnStart | typeof RUNTIME_METHODS.turnCancel | typeof RUNTIME_METHODS.approvalRespond | typeof RUNTIME_METHODS.attachmentStage | typeof RUNTIME_METHODS.attachmentChunk | typeof RUNTIME_METHODS.attachmentCommit | typeof RUNTIME_METHODS.attachmentRelease;
83
+ type MutationRuntimeMethod = typeof RUNTIME_METHODS.modelConfigReload | typeof RUNTIME_METHODS.threadCreate | typeof RUNTIME_METHODS.threadRename | typeof RUNTIME_METHODS.threadDelete | typeof RUNTIME_METHODS.threadDetach | typeof RUNTIME_METHODS.turnStart | typeof RUNTIME_METHODS.turnCancel | typeof RUNTIME_METHODS.approvalRespond | typeof RUNTIME_METHODS.attachmentStage | typeof RUNTIME_METHODS.attachmentChunk | typeof RUNTIME_METHODS.attachmentCommit | typeof RUNTIME_METHODS.attachmentRelease;
71
84
  export declare class MutationRequestCache {
72
85
  private readonly maxEntries;
73
86
  private readonly inFlight;
@@ -94,7 +107,10 @@ export declare class RuntimeService {
94
107
  private readonly maxFrameBytes;
95
108
  private readonly idempotencyCacheEntries;
96
109
  private readonly reasoningSummaryProjector;
97
- private readonly providerReasoning;
110
+ private providerReasoning;
111
+ private readonly modelConfig;
112
+ private modelConfigStatus;
113
+ private modelConfigTurnFailed;
98
114
  private reasoningTextSubscription;
99
115
  private readonly attachmentStore;
100
116
  private readonly sessions;
@@ -126,6 +142,10 @@ export declare class RuntimeService {
126
142
  deleteThread(params: RuntimeMethodParams<"thread.delete">): Promise<RuntimeMethodResult<"thread.delete">>;
127
143
  detachThread(params: RuntimeMethodParams<"thread.detach">): Promise<RuntimeMethodResult<"thread.detach">>;
128
144
  threadCapabilities(params: RuntimeMethodParams<"thread.capabilities">): Promise<RuntimeMethodResult<"thread.capabilities">>;
145
+ getModelConfig(): RuntimeModelConfigStatus;
146
+ reloadModelConfig(params: LatestRuntimeMethodParams<"runtime.modelConfig.reload">): Promise<RuntimeModelConfigStatus>;
147
+ private applyPendingModelConfig;
148
+ private assertModelConfigReady;
129
149
  startTurn(params: LatestRuntimeMethodParams<"turn.start">): Promise<LatestRuntimeMethodResult<"turn.start">>;
130
150
  cancelTurn(params: RuntimeMethodParams<"turn.cancel">): Promise<RuntimeMethodResult<"turn.cancel">>;
131
151
  respondApproval(params: LatestRuntimeMethodParams<"approval.respond">): Promise<LatestRuntimeMethodResult<"approval.respond">>;
@@ -1 +1 @@
1
- import{createHash as e,randomUUID as t}from"node:crypto";import{APPROVAL_DIFF_PREVIEW_KEY as r,initializeResultSchema as s,APPROVAL_EXPLANATION_PREVIEW_KEY as n,RUNTIME_ERROR_CODES as i,RUNTIME_FEATURES_V13 as a,RUNTIME_METHODS as o,RUNTIME_PROTOCOL_VERSION as d,RUNTIME_V14_MAX_ATTACHMENT_BYTES as h,RUNTIME_V14_MAX_ATTACHMENT_CHUNK_BYTES as u,RUNTIME_V14_MAX_STAGED_ATTACHMENTS as c,RUNTIME_V14_MAX_TURN_ATTACHMENTS as l,SUPPORTED_RUNTIME_PROTOCOL_VERSIONS as p,approvalExplanationSchema as m,approvalIdSchema as I,normalizeUserInputResultForForm as v,operationIdSchema as g,parseLatestRuntimeMethodResult as f,runtimeDurableEventV16Schema as y,runtimeEphemeralEventV16Schema as T,runtimeInstanceIdSchema as w,runtimeProtocolVersionSchema as S,streamIdSchema as A,threadIdSchema as q,userInputFormSchema as x}from"@roll-agent/protocol";import{AttachmentStore as R}from"./attachment-store.js";import{createSafeCapabilitySnapshot as b}from"../engine/capability-manifest.js";import{TurnReasoningDisplay as O}from"./turn-reasoning-display.js";import{isSensitiveFieldName as E,redactSecretText as P,toRedactedToolExecutionRecordSummary as C}from"../tool-bridge/tool-execution-record.js";import{RuntimeEventCursorExpiredError as U,RuntimeEventCursorGapError as N,ThreadStore as F}from"../store/thread-store.js";const k=4194304,B=500,j=1e4,M=16e3,D=64,V=128,$=16,J=new Set(["_meta","authorization","cookie","password","provideroptions","raw","secret","token"]);export class RuntimeServiceError extends Error{rollCode;retryable;details;constructor(e,t,r={}){super(t),this.name="RuntimeServiceError",this.rollCode=e,this.retryable=r.retryable??!1,this.details=r.details}}function L(t){const r=JSON.stringify(t);if(void 0===r)throw new RuntimeServiceError(i.invalidParams,"无法为 mutation 请求生成稳定指纹");return e("sha256").update(r).digest("hex")}export class MutationRequestCache{maxEntries;inFlight=new Map;settled=new Map;constructor(e){if(!Number.isInteger(e)||e<1)throw new Error("idempotencyCacheEntries must be a positive integer");this.maxEntries=e}run(e,t,r,s){const n=L(r),i=this.inFlight.get(e);if(void 0!==i)return this.assertMatchingRequest(e,t,n,i),i.response.then(e=>f(t,e));const a=this.settled.get(e);if(void 0!==a)return this.assertMatchingRequest(e,t,n,a),this.settled.delete(e),this.settled.set(e,a),a.response.then(e=>f(t,e));const o=Promise.resolve().then(s),d={method:t,fingerprint:n,response:o};return this.inFlight.set(e,d),o.then(()=>{this.settle(e,d)},()=>{this.settle(e,d)}),o}async settlePending(){await Promise.allSettled([...this.inFlight.values()].map(e=>e.response))}clear(){this.inFlight.clear(),this.settled.clear()}assertMatchingRequest(e,t,r,s){if(s.method!==t||s.fingerprint!==r)throw new RuntimeServiceError(i.invalidParams,`requestId "${e}" 已用于不同请求`)}settle(e,t){if(this.inFlight.get(e)===t)for(this.inFlight.delete(e),this.settled.set(e,t);this.settled.size>this.maxEntries;){const e=this.settled.keys().next().value;if(void 0===e)return;this.settled.delete(e)}}}function _(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function z(e,t,r=0){if(void 0!==t&&(J.has(t.toLowerCase())||E(t)))return"[redacted]";if(r>=$)return"[nested value omitted]";if(null===e||"boolean"==typeof e)return e;if("string"==typeof e){const t=P(e);return t.length<=M?t:`${t.slice(0,M)}\n[value clipped]`}if("number"==typeof e)return Number.isFinite(e)?e:String(e);if(Array.isArray(e)){const t=e.slice(0,D).map(e=>z(e,void 0,r+1));return e.length>D&&t.push(`[${String(e.length-D)} items omitted]`),t}if(_(e)){const t={};for(const[s,n]of Object.entries(e).slice(0,V))void 0!==n&&(t[s]=z(n,s,r+1));return Object.keys(e).length>V&&(t._omitted=`${String(Object.keys(e).length-V)} keys omitted`),t}return void 0===e?null:String(e)}function W(e){return e instanceof Error?e.message:String(e)}function G(e){const t=e.content;return"string"==typeof t?t:Array.isArray(t)?t.flatMap(e=>_(e)&&"text"===e.type&&"string"==typeof e.text?[e.text]:[]).join(""):""}function H(e){const t=e.endsWith("==")?2:e.endsWith("=")?1:0;return Math.max(0,3*Math.floor(e.length/4)-t)}function K(e){const t=e.content;return"user"===e.role&&Array.isArray(t)?t.flatMap(e=>_(e)?"file"===e.type&&"string"==typeof e.mediaType&&"string"==typeof e.data?[{type:"attachment",mediaType:e.mediaType,bytes:H(e.data)}]:"image"===e.type&&"string"==typeof e.image?[{type:"attachment",mediaType:"image/*",bytes:H(e.image)}]:[]:[]):[]}function Q(e){return q.parse(e)}function X(e,t){return{id:Q(t.id),...void 0!==t.title?{title:t.title}:{},...void 0!==t.model?{model:t.model}:{},createdAt:t.createdAt,updatedAt:t.updatedAt,messageCount:e.countTranscriptMessages(t.id)}}function Y(e){if("user"!==e.message.role&&"assistant"!==e.message.role)return;const t=P(G(e.message));return{sequence:e.sequence,role:e.message.role,createdAt:e.createdAt,parts:[...t.length>0?[{type:"text",text:t}]:[],...K(e.message)]}}function Z(e,t={status:"not_provided"}){const r=C(e);return{id:g.parse(r.id),sequence:e.sequence,toolCallId:r.toolCallId,agentName:r.agentName,toolName:r.toolName,createdAt:r.createdAt,outcome:{kind:r.outcome.kind,..."reason"in r.outcome&&void 0!==r.outcome.reason?{reason:r.outcome.reason}:{}},display:z(r.display,void 0),appOutput:t}}function ee(e,t){const s=z(t.input,void 0),i=void 0===t.explanation?void 0:m.safeParse(P(t.explanation)),a=void 0===t.diff?void 0:z(t.diff,void 0),o=_(s)?{...s,...!0===i?.success?{[n]:i.data}:{},...void 0!==a?{[r]:a}:{},...void 0!==t.fullDiff?{fullDiffAvailable:!0}:{}}:s;return{id:I.parse(t.approvalId),turnId:e,agentName:t.agentName,toolName:t.toolName,preview:o,...void 0!==t.reason?{reason:P(t.reason)}:{}}}export class RuntimeService{closePromise;openingSessions=new Map;closingThreads=new Set;ownership=new Map;turnJobs=new Map;runtimeInstanceId;engine;store;serverName;serverVersion;runtimeVersion;maxFrameBytes;idempotencyCacheEntries;reasoningSummaryProjector;providerReasoning;reasoningTextSubscription;attachmentStore;sessions=new Map;activeTurns=new Map;activeTurnOwners=new Map;settledTurnOwners=new Map;pendingApprovals=new Map;pendingUserInputs=new Map;listeners=new Set;fatalErrorListeners=new Set;userInputListeners=new Set;mutationRequests;sequence=0;closing=!1;userInputAvailable=!1;constructor(e,r,s={}){this.engine=e,this.store=r,this.serverName=s.serverName??"roll-runtime",this.serverVersion=s.serverVersion??"1.0",this.runtimeVersion=s.runtimeVersion??"development",this.maxFrameBytes=s.maxFrameBytes??k,this.idempotencyCacheEntries=s.idempotencyCacheEntries??j,this.mutationRequests=new MutationRequestCache(this.idempotencyCacheEntries),this.reasoningSummaryProjector=s.reasoningSummaryProjector,this.providerReasoning=s.providerReasoning,this.attachmentStore=s.attachmentStore,this.runtimeInstanceId=w.parse(t())}initialize(e){let t;this.assertOpen();for(const r of e.protocolVersions){const e=S.safeParse(r);if(e.success){t=e.data;break}}if(void 0===t)throw new RuntimeServiceError(i.protocolVersionUnsupported,`不支持客户端协议版本:${e.protocolVersions.join(", ")}`,{details:{supportedVersions:p}});const r=a.filter(e=>"reasoning-summary"!==e||"summary"===this.providerReasoning||void 0===this.providerReasoning&&void 0!==this.reasoningSummaryProjector),n={protocolVersion:t,runtimeInstanceId:this.runtimeInstanceId,server:{name:this.serverName,version:this.serverVersion,runtimeVersion:this.runtimeVersion},features:r,limits:{maxFrameBytes:this.maxFrameBytes,maxPageSize:B,idempotencyCacheEntries:this.idempotencyCacheEntries}};if("1.4"===t||"1.5"===t||"1.6"===t){const e=this.attachmentStore?.limits;return s.parse({...n,protocolVersion:t,features:[...r,...void 0!==this.attachmentStore?["attachments"]:[],..."1.5"===t||"1.6"===t?["app-output"]:[]],limits:{...n.limits,eventReplay:!0,maxAttachmentBytes:e?.maxAttachmentBytes??h,maxAttachmentChunkBytes:e?.maxAttachmentChunkBytes??u,maxTurnAttachments:l,maxStagedAttachments:e?.maxStagedAttachments??c}})}return"1.3"===t?{...n,protocolVersion:t,limits:{...n.limits,eventReplay:!0}}:{...n,protocolVersion:t,limits:{...n.limits,eventReplay:!1}}}subscribeReasoningText(e){if(this.assertOpen(),"text"!==this.providerReasoning||this.reasoningTextSubscription)return;const t={deliver:e};return this.reasoningTextSubscription=t,()=>{this.reasoningTextSubscription===t&&(this.reasoningTextSubscription=void 0)}}onEvent(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}onFatalError(e){return this.fatalErrorListeners.add(e),()=>{this.fatalErrorListeners.delete(e)}}onUserInputInteraction(e){return this.userInputListeners.add(e),()=>{this.userInputListeners.delete(e)}}setUserInputAvailable(e){if(this.userInputAvailable!==e){this.userInputAvailable=e,e||this.cancelAllPendingUserInputs("当前客户端已撤销用户输入处理能力");for(const t of this.sessions.values())t.setUserInputAvailable?.(e)}}listThreads(e){this.assertOpen();const t=void 0===e.cursor?0:Number(e.cursor);if(!Number.isSafeInteger(t)||t<0)throw new RuntimeServiceError(i.invalidParams,"thread.list cursor 超出安全整数范围");const r=this.store.listThreads({origin:"interactive"}),s=r.slice(t,t+e.limit).map(e=>X(this.store,e)),n=t+s.length;return{items:s,nextCursor:n<r.length?String(n):null}}async createThread(e){return this.mutationRequests.run(e.requestId,o.threadCreate,e,async()=>{this.assertOpen();const t=await this.engine.createSession(void 0!==e.title?{title:e.title}:{});return this.rememberSession(t),{thread:X(this.store,this.requireThread(t.id))}})}async openThread(e){return this.assertOpen(),"scheduled"!==this.requireThread(e.threadId).origin.kind&&await this.requireSession(e.threadId),this.snapshotThread({threadId:e.threadId,limit:100})}snapshotThread(e){this.assertOpen();const t=this.requireThread(e.threadId),r=this.store.listRecentTranscriptMessages(e.threadId,{...void 0!==e.messageBeforeSequence?{beforeSequence:e.messageBeforeSequence}:{},limit:e.limit}),s=this.store.listRecentToolExecutions(e.threadId,{...void 0!==e.operationBeforeSequence?{beforeSequence:e.operationBeforeSequence}:{},limit:e.limit}),n=this.activeTurns.get(e.threadId),i=void 0===n?void 0:{id:n.turnId,status:n.status,startedAt:n.startedAt};return{thread:X(this.store,t),messages:{items:r.entries.flatMap(e=>{const t=Y(e);return void 0===t?[]:[t]}),nextBeforeSequence:r.nextBeforeSequence??null},operations:{items:s.entries.map(t=>Z(t,this.store.getAppOutputDescriptor(e.threadId,t.id))),nextBeforeSequence:s.nextBeforeSequence??null},...void 0!==i?{activeTurn:i}:{},pendingApprovals:[...this.pendingApprovals.values()].filter(t=>t.threadId===e.threadId).map(e=>e.approval),transcriptCompleteness:this.store.getTranscriptCompleteness(e.threadId),eventCursor:this.store.getRuntimeEventCursor(e.threadId),contextRecovery:null}}resumeEvents(e){this.assertOpen(),this.requireThread(e.threadId);try{return this.store.resumeRuntimeEvents(e.threadId,e.afterCursor)}catch(t){if(t instanceof U)throw new RuntimeServiceError(i.eventCursorExpired,"Runtime Event cursor 已超出当前 Thread 的保留窗口,请回退到 thread.snapshot",{details:{threadId:e.threadId}});if(t instanceof N)throw new RuntimeServiceError(i.eventCursorGap,"Runtime Event cursor 与当前 Thread 事件日志不连续,请回退到 thread.snapshot",{details:{threadId:e.threadId}});throw t}}async renameThread(e){return this.mutationRequests.run(e.requestId,o.threadRename,e,()=>(this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.store.updateTitle(e.threadId,e.title),{thread:X(this.store,this.requireThread(e.threadId))}))}async deleteThread(e){return this.mutationRequests.run(e.requestId,o.threadDelete,e,async()=>{if(this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))throw new RuntimeServiceError(i.threadBusy,`Thread "${e.threadId}" 仍有活动 Turn`,{retryable:!0});const t=this.store.retainThread(e.threadId);this.closingThreads.add(e.threadId);let r=!1;try{await Promise.allSettled([this.openingSessions.get(e.threadId)]);const s=this.sessions.get(e.threadId);return await(s?.close()),await this.turnJobs.get(e.threadId),this.finishThread(e.threadId),r=!0,t?.assert(),this.store.deleteThread(e.threadId),{deleted:!0}}finally{r&&(t?.release(),this.closingThreads.delete(e.threadId))}})}async detachThread(e){return this.mutationRequests.run(e.requestId,o.threadDetach,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.closingThreads.has(e.threadId))return{detached:!1};this.closingThreads.add(e.threadId),await Promise.allSettled([this.openingSessions.get(e.threadId)]);const t=this.sessions.get(e.threadId);return void 0!==t||this.ownership.has(e.threadId)?(await(t?.close()),await this.turnJobs.get(e.threadId),this.finishThread(e.threadId),this.closingThreads.delete(e.threadId),{detached:!0}):(this.closingThreads.delete(e.threadId),{detached:!1})})}async threadCapabilities(e){const t=await this.requireSession(e.threadId);return{manifest:z(b(t.getCapabilityManifest(),t.getCapabilityTurnContext()),void 0)}}async startTurn(e){return this.mutationRequests.run(e.requestId,o.turnStart,e,async()=>{if(this.assertOpen(),"scheduled"===this.requireThread(e.threadId).origin.kind)throw new RuntimeServiceError(i.invalidParams,"定时执行会话是只读记录;请基于该次运行创建新对话");const t=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==t){if(t===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(i.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId)||this.turnJobs.has(e.threadId))throw new RuntimeServiceError(i.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});const r=await this.requireSession(e.threadId),s=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==s){if(s===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(i.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId)||this.turnJobs.has(e.threadId))throw new RuntimeServiceError(i.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});this.assertThreadOpen(e.threadId);const n=this.resolveTurnAttachments(e),a={ownership:this.ownership.get(e.threadId),threadId:e.threadId,turnId:e.turnId,session:r,startedAt:(new Date).toISOString(),status:"running",interactionFailure:void 0},o=this.createTurnReasoningDisplay(a);this.activeTurns.set(e.threadId,a),this.activeTurnOwners.set(e.turnId,e.threadId);try{this.emit(a.ownership,e.threadId,e.turnId,{type:"turn.started"})}catch(t){throw this.activeTurns.delete(e.threadId),this.activeTurnOwners.delete(e.turnId),t}const d=this.driveTurn(a,e.input.text,n,o);return this.turnJobs.set(e.threadId,d),d.then(()=>{this.turnJobs.get(e.threadId)===d&&this.turnJobs.delete(e.threadId)},()=>{}),{accepted:!0,turnId:e.turnId}})}async cancelTurn(e){return this.mutationRequests.run(e.requestId,o.turnCancel,e,()=>{this.assertOpen();const t=this.activeTurns.get(e.threadId);if(void 0===t||t.turnId!==e.turnId)throw new RuntimeServiceError(i.turnNotFound,`Turn "${e.turnId}" 不存在或已结束`);t.status="cancelling";let r=!1;try{this.cancelPendingUserInputsForTurn(e.turnId,"Turn 已由客户端取消"),this.cancelPendingApprovalsForTurn(e.turnId,{status:"cancelled",reason:"Turn 已由客户端取消"})}finally{r=t.session.cancel()}return{cancelling:r}})}async respondApproval(e){return this.mutationRequests.run(e.requestId,o.approvalRespond,e,()=>this.resolvePendingApproval(e,"approve"===e.decision?{decision:"approve"}:{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}}))}resolvePendingApproval(e,t){this.assertOpen();const r=this.requirePendingApproval(e),s="approve"===t.decision?r.session.approve(e.approvalId,t.scope):r.session.reject(e.approvalId,t.reason);if(this.pendingApprovals.delete(e.approvalId),!s)throw this.emitApprovalResolved(r,{status:"cancelled",reason:"审批已失效"}),new RuntimeServiceError(i.approvalNotFound,`Approval "${e.approvalId}" 已失效`);return this.emitApprovalResolved(r,"approve"===t.decision?{status:"resolved",decision:"approve"}:{status:"resolved",decision:"reject",...void 0!==t.reason?{reason:t.reason}:{}}),{resolved:!0}}getPendingApprovalExpiresAt(e){return this.findPendingApproval(e)?.expiresAt}async stageAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentStage,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId),this.requireThread(e.threadId);const t=this.requireAttachmentStore().stage({threadId:e.threadId,fileName:e.fileName,mediaType:e.mediaType,bytes:e.bytes,sha256:e.sha256,source:e.source,sourcePath:e.sourcePath});return t.ok||this.throwAttachmentFailure(t),{attachmentId:t.attachmentId,state:t.state,...void 0!==t.descriptor?{descriptor:t.descriptor}:{}}})}async appendAttachmentChunk(e){return this.mutationRequests.run(e.requestId,o.attachmentChunk,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId);const t=this.requireAttachmentStore().appendChunk({threadId:e.threadId,attachmentId:e.attachmentId,sequence:e.sequence,dataBase64:e.dataBase64});return t.ok||this.throwAttachmentFailure(t),{receivedBytes:t.receivedBytes,nextSequence:t.nextSequence}})}async commitAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentCommit,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId);const t=this.requireAttachmentStore().commit({threadId:e.threadId,attachmentId:e.attachmentId});return t.ok||this.throwAttachmentFailure(t),{descriptor:t.descriptor}})}async releaseAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentRelease,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId);return{released:this.requireAttachmentStore().release({threadId:e.threadId,attachmentId:e.attachmentId}).released}})}requireAttachmentStore(){if(void 0===this.attachmentStore)throw new RuntimeServiceError(i.capabilityUnavailable,"Runtime 未配置附件存储,attachments 能力不可用");return this.attachmentStore}throwAttachmentFailure(e){throw new RuntimeServiceError(e.code,e.message,{...void 0!==e.retryable?{retryable:e.retryable}:{}})}resolveTurnAttachments(e){const t=e.input.attachments;if(void 0===t||0===t.length)return;const r=this.requireAttachmentStore(),s=new Set,n=[];for(const a of t){if(s.has(a))throw new RuntimeServiceError(i.invalidParams,`附件 "${a}" 在同一 Turn 中重复引用`);s.add(a);const t=r.readCommitted({threadId:e.threadId,attachmentId:a});t.ok||this.throwAttachmentFailure(t),n.push({data:t.dataBase64,mediaType:t.descriptor.mediaType})}return n}cancelPendingApproval(e,t){const r=this.findPendingApproval(e);return void 0!==r&&(this.pendingApprovals.delete(e.approvalId),this.emitApprovalResolved(r,t),!0)}async failPendingApprovalInteraction(e,t,r={status:"cancelled",reason:t}){const s=this.findPendingApproval(e);if(void 0===s)return!1;const n=this.activeTurns.get(e.threadId);void 0!==n&&n.turnId===e.turnId&&(n.interactionFailure=t),this.pendingApprovals.delete(e.approvalId);try{this.emitApprovalResolved(s,r)}finally{s.session.cancel()}return!0}resolvePendingUserInput(e,t){const r=this.pendingUserInputs.get(e);if(void 0===r)return!1;let s;try{s=v(r.form,t)}catch{return this.cancelPendingUserInput(e,"用户输入不符合原始表单约束"),!1}this.pendingUserInputs.delete(e);const n=r.session.resolveUserInput?.(e,s)??!1;if(!n){const t="用户输入请求已失效";return r.session.cancelUserInput?.(e,t),this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:t}),!1}return this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:"submitted"===s.status?"用户已提交输入":"用户已取消输入"}),n}cancelPendingUserInput(e,t){const r=this.pendingUserInputs.get(e);if(void 0===r)return!1;this.pendingUserInputs.delete(e);const s=r.session.cancelUserInput?.(e,t)??!1;return this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:t}),s}getApprovalPreview(e){this.assertOpen();const t=this.requirePendingApproval(e);if(void 0===t.fullDiff||void 0!==t.expiresAt&&Date.parse(t.expiresAt)<=Date.now())throw new RuntimeServiceError(i.approvalNotFound,"完整预览不存在或审批已失效");const r=t.fullDiff.slice(e.offset,e.offset+e.maxChars),s=e.offset+r.length;return{text:r,totalChars:t.fullDiff.length,nextOffset:s<t.fullDiff.length?s:null}}getOperation(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);return{operation:void 0===t?null:Z(t,this.store.getAppOutputDescriptor(e.threadId,t.id))}}getOperationResult(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);if(void 0===t)return{result:null};const r=this.store.getAppOutput(e.threadId,e.operationId)??{status:"not_provided"};return{result:{threadId:e.threadId,operationId:e.operationId,agentName:t.agentName,toolName:t.toolName,createdAt:t.createdAt,output:r}}}close(){if(void 0!==this.closePromise)return this.closePromise;this.closing=!0,this.setUserInputAvailable(!1);for(const e of this.activeTurns.values())e.session.cancel();return this.closePromise=(async()=>{await this.mutationRequests.settlePending(),await Promise.allSettled(this.openingSessions.values());const e=(await Promise.allSettled([...this.sessions.values()].map(e=>e.close()))).filter(e=>"rejected"===e.status);if(e.length>0)throw new AggregateError(e.map(e=>e.reason),"Runtime session cleanup failed");await Promise.all(this.turnJobs.values());for(const e of this.ownership.keys())this.finishThread(e);this.sessions.clear(),this.activeTurns.clear(),this.activeTurnOwners.clear(),this.settledTurnOwners.clear(),this.pendingApprovals.clear(),this.pendingUserInputs.clear(),this.userInputListeners.clear(),this.reasoningTextSubscription=void 0,this.attachmentStore?.close(),this.mutationRequests.clear(),this.fatalErrorListeners.clear()})(),this.closePromise}assertOpen(){if(this.closing)throw new RuntimeServiceError(i.runtimeClosing,"Runtime 正在关闭",{retryable:!0})}requireThread(e){const t=this.store.getThread(e);if(void 0===t)throw new RuntimeServiceError(i.threadNotFound,`Thread "${e}" 不存在`);return t}async requireSession(e){this.assertThreadOpen(e),this.requireThread(e);const t=this.sessions.get(e);if(void 0!==t)return t.assertWritable?.(),this.ownership.get(e)?.assert(),t;const r=this.openingSessions.get(e);if(void 0!==r)return r;const s=this.engine.resumeSession(e).then(t=>(this.rememberSession(t),this.assertThreadOpen(e),t)).finally(()=>this.openingSessions.delete(e));return this.openingSessions.set(e,s),s}assertThreadOpen(e){if(this.assertOpen(),this.closingThreads.has(e))throw new RuntimeServiceError(i.threadBusy,"会话正在关闭,请等待清理完成",{retryable:!0})}retainOwnership(e){this.assertThreadOpen(e);const t=this.ownership.get(e);if(void 0!==t)return void t.assert();const r=this.store.retainThread(e);void 0!==r&&this.ownership.set(e,r)}finishThread(e){this.ownership.get(e)?.assert(),this.attachmentStore?.releaseThread(q.parse(e)),this.ownership.get(e)?.release(),this.ownership.delete(e),this.sessions.delete(e)}rememberSession(e){if(this.sessions.get(e.id)===e)return;const t=this.ownership.get(e.id)??this.store.retainThread(e.id);void 0!==t&&(t.assert(),this.ownership.set(e.id,t)),e.addCloseBarrier?.(async()=>{if(await this.turnJobs.get(e.id),t?.assert(),this.sessions.get(e.id)!==e)throw new RuntimeServiceError(i.threadBusy,"会话已关闭,不能释放后续会话的占用");this.finishThread(e.id)}),e.setUserInputAvailable?.(this.userInputAvailable),this.sessions.set(e.id,e)}emit(e,t,r,s){e?.assert();const n=(new Date).toISOString(),i={protocolVersion:d,runtimeInstanceId:this.runtimeInstanceId,sequence:this.sequence,timestamp:n,threadId:t,...void 0!==r?{turnId:r}:{}},a=y.safeParse(s),o=a.success?(()=>{let e;try{e=this.store.appendRuntimeEvent({threadId:t,...void 0===r?{}:{turnId:r},timestamp:n,event:a.data})}catch(e){throw this.notifyFatalError(e),e}return{...i,durability:"durable",eventId:e.eventId,cursor:e.cursor,event:a.data}})():{...i,durability:"ephemeral",event:T.parse(s)};this.sequence+=1;const h="reasoning.text.delta"===s.type?this.reasoningTextSubscription?[this.reasoningTextSubscription.deliver]:[]:this.listeners;for(const e of h)try{e(o)}catch{}}notifyFatalError(e){for(const e of this.activeTurns.values())try{e.session.cancel()}catch{}for(const t of this.fatalErrorListeners)try{t(e)}catch{}}createTurnReasoningDisplay(e){const t=this.providerReasoning,r=this.reasoningSummaryProjector;if(void 0===t&&!r)return;const s=this.reasoningTextSubscription;if("text"===t&&!s)return;const n="text"===t?"reasoning.text.delta":"reasoning.summary.delta",i=()=>"text"!==t||s===this.reasoningTextSubscription;return new O(e=>{if(i())return void 0===t?r?.(e.delta):e.presentation===t?e.delta:void 0},(t,r)=>{i()&&this.emit(e.ownership,e.threadId,e.turnId,{type:n,reasoningId:t,delta:r})},void 0===t)}async driveTurn(e,t,r,s){const n={reasoning:s,streamId:void 0,text:"",terminalEvent:void 0},i=void 0!==r&&r.length>0?{text:t,attachments:r}:t;let a;try{for await(const t of e.session.send(i))this.projectSessionEvent(e,n,t);a=n.terminalEvent??{type:"turn.completed"}}catch(e){n.reasoning?.reset(),a=n.terminalEvent??{type:"turn.failed",stage:"execute",message:P(W(e))}}finally{this.activeTurns.get(e.threadId)===e&&this.activeTurns.delete(e.threadId),this.settleTurnOwner(e)}void 0!==n.streamId&&this.projectSessionEvent(e,n,{type:"message-finish",text:n.text}),this.emitTurnTerminal(e,a)}findTurnOwner(e,t){const r=this.activeTurnOwners.get(e);if(void 0!==r)return r;const s=this.settledTurnOwners.get(e);return s===t&&(this.settledTurnOwners.delete(e),this.settledTurnOwners.set(e,s)),s}settleTurnOwner(e){if(this.activeTurnOwners.get(e.turnId)===e.threadId&&(this.activeTurnOwners.delete(e.turnId),!this.closing))for(this.settledTurnOwners.set(e.turnId,e.threadId);this.settledTurnOwners.size>this.idempotencyCacheEntries;){const e=this.settledTurnOwners.keys().next().value;if(void 0===e)return;this.settledTurnOwners.delete(e)}}projectSessionEvent(e,r,s){switch(s.type){case"message-start":{if(r.reasoning?.reset(),void 0!==r.streamId)return;const s=A.parse(t());return r.streamId=s,r.text="",void this.emit(e.ownership,e.threadId,e.turnId,{type:"message.started",streamId:s})}case"text-delta":{const n=r.streamId??A.parse(t());return void 0===r.streamId&&(r.streamId=n,this.emit(e.ownership,e.threadId,e.turnId,{type:"message.started",streamId:n})),r.text+=s.delta,void this.emit(e.ownership,e.threadId,e.turnId,{type:"message.delta",streamId:n,delta:s.delta})}case"message-finish":{r.reasoning?.finish();const n=r.streamId??A.parse(t());return void 0===r.streamId&&this.emit(e.ownership,e.threadId,e.turnId,{type:"message.started",streamId:n}),this.emit(e.ownership,e.threadId,e.turnId,{type:"message.completed",streamId:n,text:P(r.text||s.text)}),r.streamId=void 0,void(r.text="")}case"reasoning-start":return void r.reasoning?.start(s.reasoningId);case"reasoning-delta":return void r.reasoning?.append(s);case"reasoning-end":return void r.reasoning?.end(s.reasoningId);case"tool-call":return void this.emit(e.ownership,e.threadId,e.turnId,{type:"tool.started",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,input:z(s.input,void 0)});case"tool-output-delta":return void this.emit(e.ownership,e.threadId,e.turnId,{type:"tool.output",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,stream:s.stream,delta:P(s.delta)});case"tool-result":return void this.emit(e.ownership,e.threadId,e.turnId,{type:"tool.completed",appOutput:void 0===s.executionId?{status:"not_provided"}:this.store.getAppOutputDescriptor(e.threadId,s.executionId)??{status:"not_provided"},toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,...void 0!==s.executionId?{operationId:g.parse(s.executionId)}:{},...void 0!==s.outcome?{outcome:this.projectToolOutcome(s.outcome)}:{},display:z(s.display,void 0)});case"confirmation-required":{const t=ee(e.turnId,s);return this.pendingApprovals.set(t.id,{ownership:e.ownership,threadId:e.threadId,session:e.session,approval:t,expiresAt:s.expiresAt,fullDiff:void 0===s.fullDiff?void 0:P(s.fullDiff)}),void this.emit(e.ownership,e.threadId,e.turnId,{type:"approval.required",approval:t})}case"user-input-required":{if(!this.userInputAvailable||void 0===e.session.resolveUserInput||void 0===e.session.cancelUserInput)return void e.session.cancelUserInput?.(s.requestId,"当前客户端未协商用户输入处理能力");const t=x.parse(s.form),r=x.parse(t),n={requestId:s.requestId,threadId:e.threadId,turnId:e.turnId,session:e.session,form:t,expiresAt:s.expiresAt};this.pendingUserInputs.set(s.requestId,n),e.status="waiting-for-user";return void(this.emitUserInputInteraction({type:"required",requestId:s.requestId,threadId:e.threadId,turnId:e.turnId,form:r,expiresAt:s.expiresAt})||this.cancelPendingUserInput(s.requestId,"当前没有可处理用户输入请求的客户端"))}case"user-input-settled":{const e=this.pendingUserInputs.get(s.requestId);if(void 0===e)return;return this.pendingUserInputs.delete(s.requestId),this.restoreRunningStatus(e),void this.emitUserInputInteraction({type:"settled",requestId:e.requestId,threadId:e.threadId,turnId:e.turnId,reason:"submitted"===s.status?"用户输入请求已结算":"用户输入请求已取消"})}case"turn-cancelled":r.reasoning?.reset(),r.terminalEvent??=void 0===e.interactionFailure?{type:"turn.cancelled",reason:s.reason,message:P(s.message)}:{type:"turn.failed",stage:"execute",message:P(e.interactionFailure)};break;case"error":r.reasoning?.reset(),r.terminalEvent??={type:"turn.failed",stage:s.stage,message:P(s.message)};break;case"context-recovery":this.emit(e.ownership,e.threadId,e.turnId,{type:"context.recovery.changed",contextRecovery:s.state});break;case"model-step-start":case"model-recovery":r.reasoning?.reset();break;case"step-finish":r.reasoning?.finish()}}projectToolOutcome(e){return{kind:e.kind,..."reason"in e&&void 0!==e.reason?{reason:P(e.reason)}:{}}}findPendingApproval(e){const t=this.pendingApprovals.get(e.approvalId);if(void 0!==t&&t.threadId===e.threadId&&t.approval.turnId===e.turnId)return t}requirePendingApproval(e){const t=this.findPendingApproval(e);if(void 0===t)throw new RuntimeServiceError(i.approvalNotFound,`Approval "${e.approvalId}" 不存在或已失效`);return t}emitApprovalResolved(e,t){const r="reason"in t&&void 0!==t.reason?{...t,reason:P(t.reason)}:t;this.emit(e.ownership,e.threadId,e.approval.turnId,{type:"approval.resolved",approvalId:e.approval.id,resolution:r})}cancelPendingApprovalsForTurn(e,t){const r=[...this.pendingApprovals.values()].filter(t=>t.approval.turnId===e);let s;for(const e of r)try{this.cancelPendingApproval({threadId:e.threadId,turnId:e.approval.turnId,approvalId:e.approval.id},t)}catch(e){s??=e}if(void 0!==s)throw s}cancelPendingUserInputsForTurn(e,t){const r=[...this.pendingUserInputs.values()].filter(t=>t.turnId===e).map(e=>e.requestId);for(const e of r)this.cancelPendingUserInput(e,t)}cancelAllPendingUserInputs(e){for(const t of[...this.pendingUserInputs.keys()])this.cancelPendingUserInput(t,e)}restoreRunningStatus(e){const t=this.activeTurns.get(e.threadId);void 0!==t&&t.turnId===e.turnId&&"waiting-for-user"===t.status&&(t.status="running")}emitUserInputInteraction(e){let t=!1;for(const r of this.userInputListeners)try{r(e),t=!0}catch{}return t}emitTurnTerminal(e,t){this.cancelPendingUserInputsForTurn(e.turnId,`Turn 已终止:${t.type}`),this.cancelPendingApprovalsForTurn(e.turnId,"turn.cancelled"===t.type&&"timeout"===t.reason?{status:"expired",reason:"Turn 已超时"}:{status:"cancelled",reason:`Turn 已终止:${t.type}`}),this.emit(e.ownership,e.threadId,e.turnId,t)}}
1
+ import{createHash as e,randomUUID as t}from"node:crypto";import{APPROVAL_DIFF_PREVIEW_KEY as r,initializeResultSchema as s,APPROVAL_EXPLANATION_PREVIEW_KEY as n,RUNTIME_ERROR_CODES as i,RUNTIME_FEATURES_V13 as a,RUNTIME_METHODS as o,RUNTIME_PROTOCOL_VERSION as d,RUNTIME_V14_MAX_ATTACHMENT_BYTES as h,RUNTIME_V14_MAX_ATTACHMENT_CHUNK_BYTES as u,RUNTIME_V14_MAX_STAGED_ATTACHMENTS as l,RUNTIME_V14_MAX_TURN_ATTACHMENTS as c,SUPPORTED_RUNTIME_PROTOCOL_VERSIONS as p,approvalExplanationSchema as m,approvalIdSchema as v,normalizeUserInputResultForForm as I,operationIdSchema as g,parseLatestRuntimeMethodResult as f,runtimeDurableEventV16Schema as y,runtimeEphemeralEventV16Schema as T,runtimeInstanceIdSchema as w,runtimeProtocolVersionSchema as S,streamIdSchema as A,threadIdSchema as q,userInputFormSchema as x}from"@roll-agent/protocol";import{AttachmentStore as R}from"./attachment-store.js";import{createSafeCapabilitySnapshot as b}from"../engine/capability-manifest.js";import{TurnReasoningDisplay as C}from"./turn-reasoning-display.js";import{isSensitiveFieldName as O,redactSecretText as E,toRedactedToolExecutionRecordSummary as P}from"../tool-bridge/tool-execution-record.js";import{RuntimeEventCursorExpiredError as U,RuntimeEventCursorGapError as N,ThreadStore as F}from"../store/thread-store.js";const M=4194304,k=500,B=1e4,j=16e3,D=64,V=128,$=16,J=new Set(["_meta","authorization","cookie","password","provideroptions","raw","secret","token"]);export class RuntimeServiceError extends Error{rollCode;retryable;details;constructor(e,t,r={}){super(t),this.name="RuntimeServiceError",this.rollCode=e,this.retryable=r.retryable??!1,this.details=r.details}}function L(t){const r=JSON.stringify(t);if(void 0===r)throw new RuntimeServiceError(i.invalidParams,"无法为 mutation 请求生成稳定指纹");return e("sha256").update(r).digest("hex")}export class MutationRequestCache{maxEntries;inFlight=new Map;settled=new Map;constructor(e){if(!Number.isInteger(e)||e<1)throw new Error("idempotencyCacheEntries must be a positive integer");this.maxEntries=e}run(e,t,r,s){const n=L(r),i=this.inFlight.get(e);if(void 0!==i)return this.assertMatchingRequest(e,t,n,i),i.response.then(e=>f(t,e));const a=this.settled.get(e);if(void 0!==a)return this.assertMatchingRequest(e,t,n,a),this.settled.delete(e),this.settled.set(e,a),a.response.then(e=>f(t,e));const o=Promise.resolve().then(s),d={method:t,fingerprint:n,response:o};return this.inFlight.set(e,d),o.then(()=>{this.settle(e,d)},()=>{this.settle(e,d)}),o}async settlePending(){await Promise.allSettled([...this.inFlight.values()].map(e=>e.response))}clear(){this.inFlight.clear(),this.settled.clear()}assertMatchingRequest(e,t,r,s){if(s.method!==t||s.fingerprint!==r)throw new RuntimeServiceError(i.invalidParams,`requestId "${e}" 已用于不同请求`)}settle(e,t){if(this.inFlight.get(e)===t)for(this.inFlight.delete(e),this.settled.set(e,t);this.settled.size>this.maxEntries;){const e=this.settled.keys().next().value;if(void 0===e)return;this.settled.delete(e)}}}function z(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function _(e,t,r=0){if(void 0!==t&&(J.has(t.toLowerCase())||O(t)))return"[redacted]";if(r>=$)return"[nested value omitted]";if(null===e||"boolean"==typeof e)return e;if("string"==typeof e){const t=E(e);return t.length<=j?t:`${t.slice(0,j)}\n[value clipped]`}if("number"==typeof e)return Number.isFinite(e)?e:String(e);if(Array.isArray(e)){const t=e.slice(0,D).map(e=>_(e,void 0,r+1));return e.length>D&&t.push(`[${String(e.length-D)} items omitted]`),t}if(z(e)){const t={};for(const[s,n]of Object.entries(e).slice(0,V))void 0!==n&&(t[s]=_(n,s,r+1));return Object.keys(e).length>V&&(t._omitted=`${String(Object.keys(e).length-V)} keys omitted`),t}return void 0===e?null:String(e)}function W(e){return e instanceof Error?e.message:String(e)}function G(e){const t=e.content;return"string"==typeof t?t:Array.isArray(t)?t.flatMap(e=>z(e)&&"text"===e.type&&"string"==typeof e.text?[e.text]:[]).join(""):""}function H(e){const t=e.endsWith("==")?2:e.endsWith("=")?1:0;return Math.max(0,3*Math.floor(e.length/4)-t)}function K(e){const t=e.content;return"user"===e.role&&Array.isArray(t)?t.flatMap(e=>z(e)?"file"===e.type&&"string"==typeof e.mediaType&&"string"==typeof e.data?[{type:"attachment",mediaType:e.mediaType,bytes:H(e.data)}]:"image"===e.type&&"string"==typeof e.image?[{type:"attachment",mediaType:"image/*",bytes:H(e.image)}]:[]:[]):[]}function Q(e){return q.parse(e)}function X(e,t){return{id:Q(t.id),...void 0!==t.title?{title:t.title}:{},...void 0!==t.model?{model:t.model}:{},createdAt:t.createdAt,updatedAt:t.updatedAt,messageCount:e.countTranscriptMessages(t.id)}}function Y(e){if("user"!==e.message.role&&"assistant"!==e.message.role)return;const t=E(G(e.message));return{sequence:e.sequence,role:e.message.role,createdAt:e.createdAt,parts:[...t.length>0?[{type:"text",text:t}]:[],...K(e.message)]}}function Z(e,t={status:"not_provided"}){const r=P(e);return{id:g.parse(r.id),sequence:e.sequence,toolCallId:r.toolCallId,agentName:r.agentName,toolName:r.toolName,createdAt:r.createdAt,outcome:{kind:r.outcome.kind,..."reason"in r.outcome&&void 0!==r.outcome.reason?{reason:r.outcome.reason}:{}},display:_(r.display,void 0),appOutput:t}}function ee(e,t){const s=_(t.input,void 0),i=void 0===t.explanation?void 0:m.safeParse(E(t.explanation)),a=void 0===t.diff?void 0:_(t.diff,void 0),o=z(s)?{...s,...!0===i?.success?{[n]:i.data}:{},...void 0!==a?{[r]:a}:{},...void 0!==t.fullDiff?{fullDiffAvailable:!0}:{}}:s;return{id:v.parse(t.approvalId),turnId:e,agentName:t.agentName,toolName:t.toolName,preview:o,...void 0!==t.reason?{reason:E(t.reason)}:{}}}export class RuntimeService{closePromise;openingSessions=new Map;closingThreads=new Set;ownership=new Map;turnJobs=new Map;runtimeInstanceId;engine;store;serverName;serverVersion;runtimeVersion;maxFrameBytes;idempotencyCacheEntries;reasoningSummaryProjector;providerReasoning;modelConfig;modelConfigStatus;modelConfigTurnFailed=!1;reasoningTextSubscription;attachmentStore;sessions=new Map;activeTurns=new Map;activeTurnOwners=new Map;settledTurnOwners=new Map;pendingApprovals=new Map;pendingUserInputs=new Map;listeners=new Set;fatalErrorListeners=new Set;userInputListeners=new Set;mutationRequests;sequence=0;closing=!1;userInputAvailable=!1;constructor(e,r,s={}){this.engine=e,this.store=r,this.serverName=s.serverName??"roll-runtime",this.serverVersion=s.serverVersion??"1.0",this.runtimeVersion=s.runtimeVersion??"development",this.maxFrameBytes=s.maxFrameBytes??M,this.idempotencyCacheEntries=s.idempotencyCacheEntries??B,this.mutationRequests=new MutationRequestCache(this.idempotencyCacheEntries),this.reasoningSummaryProjector=s.reasoningSummaryProjector,this.providerReasoning=s.providerReasoning,this.modelConfig=s.modelConfig,void 0!==s.modelConfig&&(this.modelConfigStatus={status:"applied",revision:0,active:{...s.modelConfig.initial,revision:0,reasoning:this.providerReasoning??null}}),this.attachmentStore=s.attachmentStore,this.runtimeInstanceId=w.parse(t())}initialize(e){let t;this.assertOpen();for(const r of e.protocolVersions){const e=S.safeParse(r);if(e.success){t=e.data;break}}if(void 0===t)throw new RuntimeServiceError(i.protocolVersionUnsupported,`不支持客户端协议版本:${e.protocolVersions.join(", ")}`,{details:{supportedVersions:p}});const r=a.filter(e=>"reasoning-summary"!==e||"summary"===this.providerReasoning||void 0===this.providerReasoning&&void 0!==this.reasoningSummaryProjector),n={protocolVersion:t,runtimeInstanceId:this.runtimeInstanceId,server:{name:this.serverName,version:this.serverVersion,runtimeVersion:this.runtimeVersion},features:r,limits:{maxFrameBytes:this.maxFrameBytes,maxPageSize:k,idempotencyCacheEntries:this.idempotencyCacheEntries}};if("1.4"===t||"1.5"===t||"1.6"===t){const e=this.attachmentStore?.limits;return s.parse({...n,protocolVersion:t,features:[...r,...void 0!==this.attachmentStore?["attachments"]:[],..."1.5"===t||"1.6"===t?["app-output"]:[]],limits:{...n.limits,eventReplay:!0,maxAttachmentBytes:e?.maxAttachmentBytes??h,maxAttachmentChunkBytes:e?.maxAttachmentChunkBytes??u,maxTurnAttachments:c,maxStagedAttachments:e?.maxStagedAttachments??l}})}return"1.3"===t?{...n,protocolVersion:t,limits:{...n.limits,eventReplay:!0}}:{...n,protocolVersion:t,limits:{...n.limits,eventReplay:!1}}}subscribeReasoningText(e){if(this.assertOpen(),"text"!==this.providerReasoning||this.reasoningTextSubscription)return;const t={deliver:e};return this.reasoningTextSubscription=t,()=>{this.reasoningTextSubscription===t&&(this.reasoningTextSubscription=void 0)}}onEvent(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}onFatalError(e){return this.fatalErrorListeners.add(e),()=>{this.fatalErrorListeners.delete(e)}}onUserInputInteraction(e){return this.userInputListeners.add(e),()=>{this.userInputListeners.delete(e)}}setUserInputAvailable(e){if(this.userInputAvailable!==e){this.userInputAvailable=e,e||this.cancelAllPendingUserInputs("当前客户端已撤销用户输入处理能力");for(const t of this.sessions.values())t.setUserInputAvailable?.(e)}}listThreads(e){this.assertOpen();const t=void 0===e.cursor?0:Number(e.cursor);if(!Number.isSafeInteger(t)||t<0)throw new RuntimeServiceError(i.invalidParams,"thread.list cursor 超出安全整数范围");const r=this.store.listThreads({origin:"interactive"}),s=r.slice(t,t+e.limit).map(e=>X(this.store,e)),n=t+s.length;return{items:s,nextCursor:n<r.length?String(n):null}}async createThread(e){return this.mutationRequests.run(e.requestId,o.threadCreate,e,async()=>{this.assertOpen();const t=await this.engine.createSession(void 0!==e.title?{title:e.title}:{});return this.rememberSession(t),{thread:X(this.store,this.requireThread(t.id))}})}async openThread(e){return this.assertOpen(),"scheduled"!==this.requireThread(e.threadId).origin.kind&&await this.requireSession(e.threadId),this.snapshotThread({threadId:e.threadId,limit:100})}snapshotThread(e){this.assertOpen();const t=this.requireThread(e.threadId),r=this.store.listRecentTranscriptMessages(e.threadId,{...void 0!==e.messageBeforeSequence?{beforeSequence:e.messageBeforeSequence}:{},limit:e.limit}),s=this.store.listRecentToolExecutions(e.threadId,{...void 0!==e.operationBeforeSequence?{beforeSequence:e.operationBeforeSequence}:{},limit:e.limit}),n=this.activeTurns.get(e.threadId),i=void 0===n?void 0:{id:n.turnId,status:n.status,startedAt:n.startedAt};return{thread:X(this.store,t),messages:{items:r.entries.flatMap(e=>{const t=Y(e);return void 0===t?[]:[t]}),nextBeforeSequence:r.nextBeforeSequence??null},operations:{items:s.entries.map(t=>Z(t,this.store.getAppOutputDescriptor(e.threadId,t.id))),nextBeforeSequence:s.nextBeforeSequence??null},...void 0!==i?{activeTurn:i}:{},pendingApprovals:[...this.pendingApprovals.values()].filter(t=>t.threadId===e.threadId).map(e=>e.approval),transcriptCompleteness:this.store.getTranscriptCompleteness(e.threadId),eventCursor:this.store.getRuntimeEventCursor(e.threadId),contextRecovery:null}}resumeEvents(e){this.assertOpen(),this.requireThread(e.threadId);try{return this.store.resumeRuntimeEvents(e.threadId,e.afterCursor)}catch(t){if(t instanceof U)throw new RuntimeServiceError(i.eventCursorExpired,"Runtime Event cursor 已超出当前 Thread 的保留窗口,请回退到 thread.snapshot",{details:{threadId:e.threadId}});if(t instanceof N)throw new RuntimeServiceError(i.eventCursorGap,"Runtime Event cursor 与当前 Thread 事件日志不连续,请回退到 thread.snapshot",{details:{threadId:e.threadId}});throw t}}async renameThread(e){return this.mutationRequests.run(e.requestId,o.threadRename,e,()=>(this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.store.updateTitle(e.threadId,e.title),{thread:X(this.store,this.requireThread(e.threadId))}))}async deleteThread(e){return this.mutationRequests.run(e.requestId,o.threadDelete,e,async()=>{if(this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.activeTurns.has(e.threadId))throw new RuntimeServiceError(i.threadBusy,`Thread "${e.threadId}" 仍有活动 Turn`,{retryable:!0});const t=this.store.retainThread(e.threadId);this.closingThreads.add(e.threadId);let r=!1;try{await Promise.allSettled([this.openingSessions.get(e.threadId)]);const s=this.sessions.get(e.threadId);return await(s?.close()),await this.turnJobs.get(e.threadId),this.finishThread(e.threadId),r=!0,t?.assert(),this.store.deleteThread(e.threadId),{deleted:!0}}finally{r&&(t?.release(),this.closingThreads.delete(e.threadId))}})}async detachThread(e){return this.mutationRequests.run(e.requestId,o.threadDetach,e,async()=>{if(this.assertOpen(),this.requireThread(e.threadId),this.closingThreads.has(e.threadId))return{detached:!1};this.closingThreads.add(e.threadId),await Promise.allSettled([this.openingSessions.get(e.threadId)]);const t=this.sessions.get(e.threadId);return void 0!==t||this.ownership.has(e.threadId)?(await(t?.close()),await this.turnJobs.get(e.threadId),this.finishThread(e.threadId),this.closingThreads.delete(e.threadId),{detached:!0}):(this.closingThreads.delete(e.threadId),{detached:!1})})}async threadCapabilities(e){const t=await this.requireSession(e.threadId);return{manifest:_(b(t.getCapabilityManifest(),t.getCapabilityTurnContext()),void 0)}}getModelConfig(){if(this.assertOpen(),void 0===this.modelConfigStatus)throw new RuntimeServiceError(i.capabilityUnavailable,"当前 Runtime 没有可重新加载的模型配置来源");return this.modelConfigStatus}reloadModelConfig(e){return this.mutationRequests.run(e.requestId,o.modelConfigReload,e,()=>{const{active:e,revision:t}=this.getModelConfig();return this.modelConfigStatus={status:"pending",active:e,revision:t+1},this.applyPendingModelConfig(),this.getModelConfig()})}applyPendingModelConfig(){const e=this.modelConfig,t=this.modelConfigStatus;if(!this.closing&&void 0!==e&&"pending"===t?.status)if(this.modelConfigTurnFailed)this.modelConfigStatus={...t,status:"failed",reason:"apply-failed"};else if(!(this.activeTurns.size>0||this.turnJobs.size>0))try{const r=e.reload();if("failed"===r.status)return void(this.modelConfigStatus={...t,status:"failed",reason:r.reason});this.providerReasoning=r.reasoning,this.modelConfigStatus={status:"applied",revision:t.revision,active:{...r.model,revision:t.revision,reasoning:r.reasoning??null}}}catch{this.modelConfigStatus={...t,status:"failed",reason:"apply-failed"}}}assertModelConfigReady(){const e=this.modelConfigStatus?.status;if("pending"===e||"failed"===e)throw new RuntimeServiceError(i.capabilityUnavailable,"pending"===e?"模型配置待应用,请等待当前轮次结束":"模型配置应用失败,请重试应用",{retryable:!0,details:{modelConfigStatus:e}})}async startTurn(e){return this.mutationRequests.run(e.requestId,o.turnStart,e,async()=>{if(this.assertOpen(),"scheduled"===this.requireThread(e.threadId).origin.kind)throw new RuntimeServiceError(i.invalidParams,"定时执行会话是只读记录;请基于该次运行创建新对话");const t=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==t){if(t===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(i.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId)||this.turnJobs.has(e.threadId))throw new RuntimeServiceError(i.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});this.assertModelConfigReady();const r=await this.requireSession(e.threadId),s=this.findTurnOwner(e.turnId,e.threadId);if(void 0!==s){if(s===e.threadId)return{accepted:!0,turnId:e.turnId};throw new RuntimeServiceError(i.turnAlreadyActive,`Turn "${e.turnId}" 已属于另一个 Thread`)}if(this.activeTurns.has(e.threadId)||this.turnJobs.has(e.threadId))throw new RuntimeServiceError(i.turnAlreadyActive,`Thread "${e.threadId}" 已有活动 Turn`,{retryable:!0});this.assertThreadOpen(e.threadId),this.assertModelConfigReady();const n=this.resolveTurnAttachments(e),a={ownership:this.ownership.get(e.threadId),threadId:e.threadId,turnId:e.turnId,session:r,startedAt:(new Date).toISOString(),status:"running",interactionFailure:void 0},o=this.createTurnReasoningDisplay(a);this.activeTurns.set(e.threadId,a),this.activeTurnOwners.set(e.turnId,e.threadId);try{this.emit(a.ownership,e.threadId,e.turnId,{type:"turn.started"})}catch(t){throw this.activeTurns.delete(e.threadId),this.activeTurnOwners.delete(e.turnId),t}const d=this.driveTurn(a,e.input.text,n,o);return this.turnJobs.set(e.threadId,d),d.then(()=>{this.turnJobs.get(e.threadId)===d&&this.turnJobs.delete(e.threadId),this.applyPendingModelConfig()},()=>{this.modelConfigTurnFailed=!0,this.applyPendingModelConfig()}),{accepted:!0,turnId:e.turnId}})}async cancelTurn(e){return this.mutationRequests.run(e.requestId,o.turnCancel,e,()=>{this.assertOpen();const t=this.activeTurns.get(e.threadId);if(void 0===t||t.turnId!==e.turnId)throw new RuntimeServiceError(i.turnNotFound,`Turn "${e.turnId}" 不存在或已结束`);t.status="cancelling";let r=!1;try{this.cancelPendingUserInputsForTurn(e.turnId,"Turn 已由客户端取消"),this.cancelPendingApprovalsForTurn(e.turnId,{status:"cancelled",reason:"Turn 已由客户端取消"})}finally{r=t.session.cancel()}return{cancelling:r}})}async respondApproval(e){return this.mutationRequests.run(e.requestId,o.approvalRespond,e,()=>this.resolvePendingApproval(e,"approve"===e.decision?{decision:"approve"}:{decision:"reject",...void 0!==e.reason?{reason:e.reason}:{}}))}resolvePendingApproval(e,t){this.assertOpen();const r=this.requirePendingApproval(e),s="approve"===t.decision?r.session.approve(e.approvalId,t.scope):r.session.reject(e.approvalId,t.reason);if(this.pendingApprovals.delete(e.approvalId),!s)throw this.emitApprovalResolved(r,{status:"cancelled",reason:"审批已失效"}),new RuntimeServiceError(i.approvalNotFound,`Approval "${e.approvalId}" 已失效`);return this.emitApprovalResolved(r,"approve"===t.decision?{status:"resolved",decision:"approve"}:{status:"resolved",decision:"reject",...void 0!==t.reason?{reason:t.reason}:{}}),{resolved:!0}}getPendingApprovalExpiresAt(e){return this.findPendingApproval(e)?.expiresAt}async stageAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentStage,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId),this.requireThread(e.threadId);const t=this.requireAttachmentStore().stage({threadId:e.threadId,fileName:e.fileName,mediaType:e.mediaType,bytes:e.bytes,sha256:e.sha256,source:e.source,sourcePath:e.sourcePath});return t.ok||this.throwAttachmentFailure(t),{attachmentId:t.attachmentId,state:t.state,...void 0!==t.descriptor?{descriptor:t.descriptor}:{}}})}async appendAttachmentChunk(e){return this.mutationRequests.run(e.requestId,o.attachmentChunk,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId);const t=this.requireAttachmentStore().appendChunk({threadId:e.threadId,attachmentId:e.attachmentId,sequence:e.sequence,dataBase64:e.dataBase64});return t.ok||this.throwAttachmentFailure(t),{receivedBytes:t.receivedBytes,nextSequence:t.nextSequence}})}async commitAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentCommit,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId);const t=this.requireAttachmentStore().commit({threadId:e.threadId,attachmentId:e.attachmentId});return t.ok||this.throwAttachmentFailure(t),{descriptor:t.descriptor}})}async releaseAttachment(e){return this.mutationRequests.run(e.requestId,o.attachmentRelease,e,()=>{this.assertThreadOpen(e.threadId),this.requireThread(e.threadId),this.retainOwnership(e.threadId);return{released:this.requireAttachmentStore().release({threadId:e.threadId,attachmentId:e.attachmentId}).released}})}requireAttachmentStore(){if(void 0===this.attachmentStore)throw new RuntimeServiceError(i.capabilityUnavailable,"Runtime 未配置附件存储,attachments 能力不可用");return this.attachmentStore}throwAttachmentFailure(e){throw new RuntimeServiceError(e.code,e.message,{...void 0!==e.retryable?{retryable:e.retryable}:{}})}resolveTurnAttachments(e){const t=e.input.attachments;if(void 0===t||0===t.length)return;const r=this.requireAttachmentStore(),s=new Set,n=[];for(const a of t){if(s.has(a))throw new RuntimeServiceError(i.invalidParams,`附件 "${a}" 在同一 Turn 中重复引用`);s.add(a);const t=r.readCommitted({threadId:e.threadId,attachmentId:a});t.ok||this.throwAttachmentFailure(t),n.push({data:t.dataBase64,mediaType:t.descriptor.mediaType})}return n}cancelPendingApproval(e,t){const r=this.findPendingApproval(e);return void 0!==r&&(this.pendingApprovals.delete(e.approvalId),this.emitApprovalResolved(r,t),!0)}async failPendingApprovalInteraction(e,t,r={status:"cancelled",reason:t}){const s=this.findPendingApproval(e);if(void 0===s)return!1;const n=this.activeTurns.get(e.threadId);void 0!==n&&n.turnId===e.turnId&&(n.interactionFailure=t),this.pendingApprovals.delete(e.approvalId);try{this.emitApprovalResolved(s,r)}finally{s.session.cancel()}return!0}resolvePendingUserInput(e,t){const r=this.pendingUserInputs.get(e);if(void 0===r)return!1;let s;try{s=I(r.form,t)}catch{return this.cancelPendingUserInput(e,"用户输入不符合原始表单约束"),!1}this.pendingUserInputs.delete(e);const n=r.session.resolveUserInput?.(e,s)??!1;if(!n){const t="用户输入请求已失效";return r.session.cancelUserInput?.(e,t),this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:t}),!1}return this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:"submitted"===s.status?"用户已提交输入":"用户已取消输入"}),n}cancelPendingUserInput(e,t){const r=this.pendingUserInputs.get(e);if(void 0===r)return!1;this.pendingUserInputs.delete(e);const s=r.session.cancelUserInput?.(e,t)??!1;return this.restoreRunningStatus(r),this.emitUserInputInteraction({type:"settled",requestId:e,threadId:r.threadId,turnId:r.turnId,reason:t}),s}getApprovalPreview(e){this.assertOpen();const t=this.requirePendingApproval(e);if(void 0===t.fullDiff||void 0!==t.expiresAt&&Date.parse(t.expiresAt)<=Date.now())throw new RuntimeServiceError(i.approvalNotFound,"完整预览不存在或审批已失效");const r=t.fullDiff.slice(e.offset,e.offset+e.maxChars),s=e.offset+r.length;return{text:r,totalChars:t.fullDiff.length,nextOffset:s<t.fullDiff.length?s:null}}getOperation(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);return{operation:void 0===t?null:Z(t,this.store.getAppOutputDescriptor(e.threadId,t.id))}}getOperationResult(e){this.assertOpen(),this.requireThread(e.threadId);const t=this.store.getToolExecution(e.threadId,e.operationId);if(void 0===t)return{result:null};const r=this.store.getAppOutput(e.threadId,e.operationId)??{status:"not_provided"};return{result:{threadId:e.threadId,operationId:e.operationId,agentName:t.agentName,toolName:t.toolName,createdAt:t.createdAt,output:r}}}close(){if(void 0!==this.closePromise)return this.closePromise;this.closing=!0,this.setUserInputAvailable(!1);for(const e of this.activeTurns.values())e.session.cancel();return this.closePromise=(async()=>{await this.mutationRequests.settlePending(),await Promise.allSettled(this.openingSessions.values());const e=(await Promise.allSettled([...this.sessions.values()].map(e=>e.close()))).filter(e=>"rejected"===e.status);if(e.length>0)throw new AggregateError(e.map(e=>e.reason),"Runtime session cleanup failed");await Promise.all(this.turnJobs.values());for(const e of this.ownership.keys())this.finishThread(e);this.sessions.clear(),this.activeTurns.clear(),this.activeTurnOwners.clear(),this.settledTurnOwners.clear(),this.pendingApprovals.clear(),this.pendingUserInputs.clear(),this.userInputListeners.clear(),this.reasoningTextSubscription=void 0,this.attachmentStore?.close(),this.mutationRequests.clear(),this.fatalErrorListeners.clear()})(),this.closePromise}assertOpen(){if(this.closing)throw new RuntimeServiceError(i.runtimeClosing,"Runtime 正在关闭",{retryable:!0})}requireThread(e){const t=this.store.getThread(e);if(void 0===t)throw new RuntimeServiceError(i.threadNotFound,`Thread "${e}" 不存在`);return t}async requireSession(e){this.assertThreadOpen(e),this.requireThread(e);const t=this.sessions.get(e);if(void 0!==t)return t.assertWritable?.(),this.ownership.get(e)?.assert(),t;const r=this.openingSessions.get(e);if(void 0!==r)return r;const s=this.engine.resumeSession(e).then(t=>(this.rememberSession(t),this.assertThreadOpen(e),t)).finally(()=>this.openingSessions.delete(e));return this.openingSessions.set(e,s),s}assertThreadOpen(e){if(this.assertOpen(),this.closingThreads.has(e))throw new RuntimeServiceError(i.threadBusy,"会话正在关闭,请等待清理完成",{retryable:!0})}retainOwnership(e){this.assertThreadOpen(e);const t=this.ownership.get(e);if(void 0!==t)return void t.assert();const r=this.store.retainThread(e);void 0!==r&&this.ownership.set(e,r)}finishThread(e){this.ownership.get(e)?.assert(),this.attachmentStore?.releaseThread(q.parse(e)),this.ownership.get(e)?.release(),this.ownership.delete(e),this.sessions.delete(e)}rememberSession(e){if(this.sessions.get(e.id)===e)return;const t=this.ownership.get(e.id)??this.store.retainThread(e.id);void 0!==t&&(t.assert(),this.ownership.set(e.id,t)),e.addCloseBarrier?.(async()=>{if(await this.turnJobs.get(e.id),t?.assert(),this.sessions.get(e.id)!==e)throw new RuntimeServiceError(i.threadBusy,"会话已关闭,不能释放后续会话的占用");this.finishThread(e.id)}),e.setUserInputAvailable?.(this.userInputAvailable),this.sessions.set(e.id,e)}emit(e,t,r,s){e?.assert();const n=(new Date).toISOString(),i={protocolVersion:d,runtimeInstanceId:this.runtimeInstanceId,sequence:this.sequence,timestamp:n,threadId:t,...void 0!==r?{turnId:r}:{}},a=y.safeParse(s),o=a.success?(()=>{let e;try{e=this.store.appendRuntimeEvent({threadId:t,...void 0===r?{}:{turnId:r},timestamp:n,event:a.data})}catch(e){throw this.notifyFatalError(e),e}return{...i,durability:"durable",eventId:e.eventId,cursor:e.cursor,event:a.data}})():{...i,durability:"ephemeral",event:T.parse(s)};this.sequence+=1;const h="reasoning.text.delta"===s.type?this.reasoningTextSubscription?[this.reasoningTextSubscription.deliver]:[]:this.listeners;for(const e of h)try{e(o)}catch{}}notifyFatalError(e){for(const e of this.activeTurns.values())try{e.session.cancel()}catch{}for(const t of this.fatalErrorListeners)try{t(e)}catch{}}createTurnReasoningDisplay(e){const t=this.providerReasoning,r=this.reasoningSummaryProjector;if(void 0===t&&!r)return;const s=this.reasoningTextSubscription;if("text"===t&&!s)return;const n="text"===t?"reasoning.text.delta":"reasoning.summary.delta",i=()=>"text"!==t||s===this.reasoningTextSubscription;return new C(e=>{if(i())return void 0===t?r?.(e.delta):e.presentation===t?e.delta:void 0},(t,r)=>{i()&&this.emit(e.ownership,e.threadId,e.turnId,{type:n,reasoningId:t,delta:r})},void 0===t)}async driveTurn(e,t,r,s){const n={reasoning:s,streamId:void 0,text:"",terminalEvent:void 0},i=void 0!==r&&r.length>0?{text:t,attachments:r}:t;let a;try{for await(const t of e.session.send(i))this.projectSessionEvent(e,n,t);a=n.terminalEvent??{type:"turn.completed"}}catch(e){n.reasoning?.reset(),a=n.terminalEvent??{type:"turn.failed",stage:"execute",message:E(W(e))}}finally{this.activeTurns.get(e.threadId)===e&&this.activeTurns.delete(e.threadId),this.settleTurnOwner(e)}void 0!==n.streamId&&this.projectSessionEvent(e,n,{type:"message-finish",text:n.text}),this.emitTurnTerminal(e,a)}findTurnOwner(e,t){const r=this.activeTurnOwners.get(e);if(void 0!==r)return r;const s=this.settledTurnOwners.get(e);return s===t&&(this.settledTurnOwners.delete(e),this.settledTurnOwners.set(e,s)),s}settleTurnOwner(e){if(this.activeTurnOwners.get(e.turnId)===e.threadId&&(this.activeTurnOwners.delete(e.turnId),!this.closing))for(this.settledTurnOwners.set(e.turnId,e.threadId);this.settledTurnOwners.size>this.idempotencyCacheEntries;){const e=this.settledTurnOwners.keys().next().value;if(void 0===e)return;this.settledTurnOwners.delete(e)}}projectSessionEvent(e,r,s){switch(s.type){case"message-start":{if(r.reasoning?.reset(),void 0!==r.streamId)return;const s=A.parse(t());return r.streamId=s,r.text="",void this.emit(e.ownership,e.threadId,e.turnId,{type:"message.started",streamId:s})}case"text-delta":{const n=r.streamId??A.parse(t());return void 0===r.streamId&&(r.streamId=n,this.emit(e.ownership,e.threadId,e.turnId,{type:"message.started",streamId:n})),r.text+=s.delta,void this.emit(e.ownership,e.threadId,e.turnId,{type:"message.delta",streamId:n,delta:s.delta})}case"message-finish":{r.reasoning?.finish();const n=r.streamId??A.parse(t());return void 0===r.streamId&&this.emit(e.ownership,e.threadId,e.turnId,{type:"message.started",streamId:n}),this.emit(e.ownership,e.threadId,e.turnId,{type:"message.completed",streamId:n,text:E(r.text||s.text)}),r.streamId=void 0,void(r.text="")}case"reasoning-start":return void r.reasoning?.start(s.reasoningId);case"reasoning-delta":return void r.reasoning?.append(s);case"reasoning-end":return void r.reasoning?.end(s.reasoningId);case"tool-call":return void this.emit(e.ownership,e.threadId,e.turnId,{type:"tool.started",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,input:_(s.input,void 0)});case"tool-output-delta":return void this.emit(e.ownership,e.threadId,e.turnId,{type:"tool.output",toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,stream:s.stream,delta:E(s.delta)});case"tool-result":return void this.emit(e.ownership,e.threadId,e.turnId,{type:"tool.completed",appOutput:void 0===s.executionId?{status:"not_provided"}:this.store.getAppOutputDescriptor(e.threadId,s.executionId)??{status:"not_provided"},toolCallId:s.toolCallId,agentName:s.agentName,toolName:s.toolName,...void 0!==s.executionId?{operationId:g.parse(s.executionId)}:{},...void 0!==s.outcome?{outcome:this.projectToolOutcome(s.outcome)}:{},display:_(s.display,void 0)});case"confirmation-required":{const t=ee(e.turnId,s);return this.pendingApprovals.set(t.id,{ownership:e.ownership,threadId:e.threadId,session:e.session,approval:t,expiresAt:s.expiresAt,fullDiff:void 0===s.fullDiff?void 0:E(s.fullDiff)}),void this.emit(e.ownership,e.threadId,e.turnId,{type:"approval.required",approval:t})}case"user-input-required":{if(!this.userInputAvailable||void 0===e.session.resolveUserInput||void 0===e.session.cancelUserInput)return void e.session.cancelUserInput?.(s.requestId,"当前客户端未协商用户输入处理能力");const t=x.parse(s.form),r=x.parse(t),n={requestId:s.requestId,threadId:e.threadId,turnId:e.turnId,session:e.session,form:t,expiresAt:s.expiresAt};this.pendingUserInputs.set(s.requestId,n),e.status="waiting-for-user";return void(this.emitUserInputInteraction({type:"required",requestId:s.requestId,threadId:e.threadId,turnId:e.turnId,form:r,expiresAt:s.expiresAt})||this.cancelPendingUserInput(s.requestId,"当前没有可处理用户输入请求的客户端"))}case"user-input-settled":{const e=this.pendingUserInputs.get(s.requestId);if(void 0===e)return;return this.pendingUserInputs.delete(s.requestId),this.restoreRunningStatus(e),void this.emitUserInputInteraction({type:"settled",requestId:e.requestId,threadId:e.threadId,turnId:e.turnId,reason:"submitted"===s.status?"用户输入请求已结算":"用户输入请求已取消"})}case"turn-cancelled":r.reasoning?.reset(),r.terminalEvent??=void 0===e.interactionFailure?{type:"turn.cancelled",reason:s.reason,message:E(s.message)}:{type:"turn.failed",stage:"execute",message:E(e.interactionFailure)};break;case"error":r.reasoning?.reset(),r.terminalEvent??={type:"turn.failed",stage:s.stage,message:E(s.message)};break;case"context-recovery":this.emit(e.ownership,e.threadId,e.turnId,{type:"context.recovery.changed",contextRecovery:s.state});break;case"model-step-start":case"model-recovery":r.reasoning?.reset();break;case"step-finish":r.reasoning?.finish()}}projectToolOutcome(e){return{kind:e.kind,..."reason"in e&&void 0!==e.reason?{reason:E(e.reason)}:{}}}findPendingApproval(e){const t=this.pendingApprovals.get(e.approvalId);if(void 0!==t&&t.threadId===e.threadId&&t.approval.turnId===e.turnId)return t}requirePendingApproval(e){const t=this.findPendingApproval(e);if(void 0===t)throw new RuntimeServiceError(i.approvalNotFound,`Approval "${e.approvalId}" 不存在或已失效`);return t}emitApprovalResolved(e,t){const r="reason"in t&&void 0!==t.reason?{...t,reason:E(t.reason)}:t;this.emit(e.ownership,e.threadId,e.approval.turnId,{type:"approval.resolved",approvalId:e.approval.id,resolution:r})}cancelPendingApprovalsForTurn(e,t){const r=[...this.pendingApprovals.values()].filter(t=>t.approval.turnId===e);let s;for(const e of r)try{this.cancelPendingApproval({threadId:e.threadId,turnId:e.approval.turnId,approvalId:e.approval.id},t)}catch(e){s??=e}if(void 0!==s)throw s}cancelPendingUserInputsForTurn(e,t){const r=[...this.pendingUserInputs.values()].filter(t=>t.turnId===e).map(e=>e.requestId);for(const e of r)this.cancelPendingUserInput(e,t)}cancelAllPendingUserInputs(e){for(const t of[...this.pendingUserInputs.keys()])this.cancelPendingUserInput(t,e)}restoreRunningStatus(e){const t=this.activeTurns.get(e.threadId);void 0!==t&&t.turnId===e.turnId&&"waiting-for-user"===t.status&&(t.status="running")}emitUserInputInteraction(e){let t=!1;for(const r of this.userInputListeners)try{r(e),t=!0}catch{}return t}emitTurnTerminal(e,t){this.cancelPendingUserInputsForTurn(e.turnId,`Turn 已终止:${t.type}`),this.cancelPendingApprovalsForTurn(e.turnId,"turn.cancelled"===t.type&&"timeout"===t.reason?{status:"expired",reason:"Turn 已超时"}:{status:"cancelled",reason:`Turn 已终止:${t.type}`}),this.emit(e.ownership,e.threadId,e.turnId,t)}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/runtime",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -40,7 +40,7 @@
40
40
  "dependencies": {
41
41
  "@ai-sdk/provider": "4.0.31",
42
42
  "@modelcontextprotocol/sdk": "1.32.0",
43
- "@roll-agent/protocol": "0.11.0",
43
+ "@roll-agent/protocol": "0.12.0",
44
44
  "@vscode/ripgrep": "1.18.0",
45
45
  "ai": "7.0.146",
46
46
  "diff": "9.0.0",