| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import { isDeepStrictEqual } from 'node:util'; |
| |
| import type { |
| FormAnswerAckEvent, |
| FormRequestEvent, |
| SandboxBoundaryDecisionAckEvent, |
| SandboxBoundaryRequestEvent, |
| SessionEvent, |
| UserQuestionAnswerAckEvent, |
| UserQuestionRequestEvent, |
| } from '@maka/core/events'; |
| import type { |
| InteractionCanonicalPermissionOutcome, |
| InteractionClosureReason, |
| InteractionFormResult, |
| InteractionPermissionRequest, |
| } from '@maka/core/interaction'; |
| import type { SandboxBoundarySettlement } from '@maka/core/sandbox-boundary'; |
| import type { |
| HostedInteractionBridge, |
| HostedFormSettlement, |
| HostedSandboxBoundarySettlement, |
| HostedUserQuestionAnswer, |
| HostedUserQuestionSettlement, |
| } from '@maka/core/backend-types'; |
| |
| export type RuntimeInteractionClosureReason = InteractionClosureReason; |
| |
| export type RuntimeInteractionRunClosureReason = Extract< |
| RuntimeInteractionClosureReason, |
| 'turn_stopped' | 'turn_terminal' |
| >; |
| |
| export type RuntimeUserQuestionClosureReason = Exclude<InteractionClosureReason, 'timed_out'>; |
| |
| export interface RuntimeInteractionContinuationIdentity { |
| readonly requestId: string; |
| readonly turnId: string; |
| readonly runId: string; |
| } |
| |
| export interface RuntimeInteractionRunIdentity { |
| readonly sessionId: string; |
| readonly turnId: string; |
| readonly runId: string; |
| } |
| |
| export type RuntimeUserQuestionAnswer = HostedUserQuestionAnswer; |
| |
| export type RuntimeUserQuestionOutcome = |
| | { kind: 'question_answer'; answer: RuntimeUserQuestionAnswer } |
| | { kind: 'closure'; reason: RuntimeUserQuestionClosureReason }; |
| |
| export type RuntimeFormOutcome = |
| | { kind: 'form_answer'; answer: InteractionFormResult } |
| | { kind: 'closure'; reason: RuntimeUserQuestionClosureReason }; |
| |
| export type RuntimeSandboxBoundaryOutcome = |
| | { kind: 'sandbox_boundary_decision'; settlement: SandboxBoundarySettlement } |
| | { kind: 'closure'; reason: RuntimeUserQuestionClosureReason }; |
| |
| export type RuntimeInteractionFatalError = |
| | RuntimeInteractionFailStopError |
| | RuntimeInteractionInvariantError; |
| |
| export interface RuntimeUserQuestionContinuation |
| extends RuntimeInteractionContinuationIdentity, |
| HostedUserQuestionSettlement { |
| waitForPublication(): Promise<void>; |
| } |
| |
| export interface RuntimeFormContinuation |
| extends RuntimeInteractionContinuationIdentity, |
| HostedFormSettlement { |
| waitForPublication(): Promise<void>; |
| } |
| |
| export interface RuntimeSandboxBoundaryContinuation |
| extends RuntimeInteractionContinuationIdentity, |
| HostedSandboxBoundarySettlement { |
| waitForPublication(): Promise<void>; |
| } |
| |
| export interface RuntimeInteractionContinuationAuthority { |
| acceptUserQuestionRequest(input: { |
| request: UserQuestionRequestEvent; |
| continuation: RuntimeUserQuestionContinuation; |
| }): Promise<void>; |
| acceptFormRequest(input: { |
| request: FormRequestEvent; |
| continuation: RuntimeFormContinuation; |
| }): Promise<void>; |
| acceptSandboxBoundaryRequest(input: { |
| request: SandboxBoundaryRequestEvent; |
| continuation: RuntimeSandboxBoundaryContinuation; |
| }): Promise<void>; |
| } |
| |
| export interface RuntimeInteractionRunFacet |
| extends RuntimeInteractionContinuationAuthority, |
| RuntimeInteractionRunIdentity {} |
| |
| export interface RuntimeInteractionRunOwner extends RuntimeInteractionRunFacet { |
| withdrawFormRequest(requestId: string): Promise<void>; |
| close(reason: RuntimeInteractionRunClosureReason): Promise<void>; |
| release(): void; |
| } |
| |
| export interface CanonicalPermissionOutcomeRecord { |
| readonly sessionId: string; |
| readonly runId: string; |
| readonly turnId: string; |
| readonly requestId: string; |
| readonly request: InteractionPermissionRequest; |
| readonly outcome: InteractionCanonicalPermissionOutcome; |
| } |
| |
| export interface CanonicalPermissionOutcomeReader { |
| readPermissionOutcome(requestId: string): Promise<CanonicalPermissionOutcomeRecord | undefined>; |
| } |
| |
| export interface RuntimeInteractionAuthority { |
| bindRun(identity: RuntimeInteractionRunIdentity): RuntimeInteractionRunOwner; |
| } |
| |
| export class RuntimeInteractionClosedError extends Error { |
| readonly name = 'RuntimeInteractionClosedError'; |
| |
| constructor( |
| readonly requestId: string, |
| readonly reason: RuntimeInteractionClosureReason, |
| ) { |
| super(`Interaction request ${requestId} was already closed: ${reason}`); |
| } |
| } |
| |
| export type RuntimeInteractionAdmissionRejectionReason = |
| | 'capacity_exceeded' |
| | 'invalid_request' |
| | 'not_published' |
| | 'run_closed' |
| | 'request_settled' |
| | 'authority_draining'; |
| |
| export class RuntimeInteractionAdmissionRejectedError extends Error { |
| readonly name = 'RuntimeInteractionAdmissionRejectedError'; |
| readonly closureReason: RuntimeInteractionRunClosureReason | undefined; |
| |
| constructor( |
| readonly requestId: string, |
| readonly reason: RuntimeInteractionAdmissionRejectionReason, |
| authorityFailureOrClosureReason?: unknown, |
| ) { |
| const closureReason = |
| reason === 'run_closed' |
| ? (authorityFailureOrClosureReason as RuntimeInteractionRunClosureReason) |
| : undefined; |
| super( |
| reason === 'run_closed' |
| ? `Interaction request ${requestId} was not admitted because its run closed: ${closureReason}` |
| : `Interaction request ${requestId} was not admitted: ${reason}`, |
| reason === 'not_published' ? { cause: authorityFailureOrClosureReason } : undefined, |
| ); |
| this.closureReason = closureReason; |
| } |
| } |
| |
| export class RuntimeInteractionInvariantError extends Error { |
| readonly name = 'RuntimeInteractionInvariantError'; |
| } |
| |
| export class RuntimeInteractionFailStopError extends Error { |
| readonly name = 'RuntimeInteractionFailStopError'; |
| |
| constructor( |
| message: string, |
| readonly authorityFailure: unknown, |
| ) { |
| super(message, { cause: authorityFailure }); |
| } |
| } |
| |
| /** |
| * Whether shutdown, rather than the work itself, is what refused this admission. |
| * |
| * A draining authority turns anything it rejects into a cancellation: the run |
| * did not fail, it was never allowed to proceed. Callers use this to settle the |
| * run as cancelled and to keep the rejection from reading as a Host fault. |
| */ |
| export function isShutdownCancelledInteractionAdmission(error: unknown): boolean { |
| if ( |
| error instanceof RuntimeInteractionAdmissionRejectedError && |
| error.reason === 'authority_draining' |
| ) { |
| return true; |
| } |
| return ( |
| error instanceof RuntimeInteractionFailStopError && |
| error.authorityFailure instanceof RuntimeInteractionAdmissionRejectedError && |
| error.authorityFailure.reason === 'authority_draining' |
| ); |
| } |
| |
| type LocalClosureFinalizer = () => void; |
| |
| type HostedInteractionRequestEvent = |
| | UserQuestionRequestEvent |
| | FormRequestEvent |
| | SandboxBoundaryRequestEvent; |
| type HostedInteractionSettlementAckEvent = |
| | UserQuestionAnswerAckEvent |
| | FormAnswerAckEvent |
| | SandboxBoundaryDecisionAckEvent; |
| |
| export function isHostedInteractionRequestEvent( |
| event: SessionEvent, |
| ): event is HostedInteractionRequestEvent { |
| return ( |
| event.type === 'user_question_request' || |
| event.type === 'form_request' || |
| event.type === 'sandbox_boundary_request' |
| ); |
| } |
| |
| export function isHostedInteractionSettlementAckEvent( |
| event: SessionEvent, |
| ): event is HostedInteractionSettlementAckEvent { |
| return ( |
| event.type === 'user_question_answer_ack' || |
| event.type === 'form_answer_ack' || |
| event.type === 'sandbox_boundary_decision_ack' |
| ); |
| } |
| type RuntimeHostedInteractionOutcome = |
| | RuntimeUserQuestionOutcome |
| | RuntimeFormOutcome |
| | RuntimeSandboxBoundaryOutcome; |
| |
| interface TrackedContinuationBase { |
| readonly requestId: string; |
| readonly request: HostedInteractionRequestEvent; |
| readonly publicationBarrier: Promise<void>; |
| completePublicationBarrier(): void; |
| admissionState: 'pending' | 'settled' | undefined; |
| published: boolean; |
| settlementStarted: boolean; |
| settled: boolean; |
| outcome?: RuntimeHostedInteractionOutcome; |
| settlementPromise?: Promise<void>; |
| } |
| |
| interface TrackedQuestionContinuation extends TrackedContinuationBase { |
| readonly continuation: RuntimeUserQuestionContinuation; |
| } |
| |
| interface TrackedFormContinuation extends TrackedContinuationBase { |
| readonly request: FormRequestEvent; |
| readonly continuation: RuntimeFormContinuation; |
| } |
| |
| interface TrackedSandboxBoundaryContinuation extends TrackedContinuationBase { |
| readonly request: SandboxBoundaryRequestEvent; |
| readonly continuation: RuntimeSandboxBoundaryContinuation; |
| } |
| |
| type TrackedContinuation = |
| | TrackedQuestionContinuation |
| | TrackedFormContinuation |
| | TrackedSandboxBoundaryContinuation; |
| |
| /** Exact-Run bridge between RuntimeKernel and backend Interaction producers. */ |
| export class RuntimeInteractionRunBinding implements HostedInteractionBridge { |
| private closeReason: RuntimeInteractionRunClosureReason | undefined; |
| private closePromise: Promise<void> | undefined; |
| private localSettlementPromise: Promise<void> | undefined; |
| private localClosuresSettled = false; |
| private publicationsSealed = false; |
| private released = false; |
| private readonly localClosureFinalizers: LocalClosureFinalizer[] = []; |
| private readonly continuations = new Map<string, TrackedContinuation>(); |
| |
| constructor(readonly owner: RuntimeInteractionRunOwner) {} |
| |
| get sessionId(): string { |
| return this.owner.sessionId; |
| } |
| |
| get turnId(): string { |
| return this.owner.turnId; |
| } |
| |
| get runId(): string { |
| return this.owner.runId; |
| } |
| |
| async canResumeAfterSettlementAck(event: HostedInteractionSettlementAckEvent): Promise<boolean> { |
| const settled = this.continuations.get(event.requestId); |
| if ( |
| !settled || |
| settled.request.turnId !== event.turnId || |
| settled.request.toolUseId !== event.toolUseId || |
| !settled.settlementPromise |
| ) { |
| throw new RuntimeInteractionInvariantError( |
| `Interaction acknowledgement ${event.requestId} has no exact local settlement`, |
| ); |
| } |
| await settled.settlementPromise; |
| if (!settled.settled || !settlementMatchesAck(settled, event)) { |
| throw new RuntimeInteractionInvariantError( |
| `Interaction acknowledgement ${event.requestId} has no exact local settlement`, |
| ); |
| } |
| for (const tracked of this.continuations.values()) { |
| if (tracked === settled) continue; |
| if (!tracked.settled) return false; |
| } |
| return true; |
| } |
| |
| async admitUserQuestionRequest(input: { |
| request: UserQuestionRequestEvent; |
| settlement: HostedUserQuestionSettlement; |
| }): Promise<void> { |
| const tracked = this.trackQuestion(input.request, input.settlement); |
| try { |
| await this.owner.acceptUserQuestionRequest({ |
| request: input.request, |
| continuation: tracked.continuation, |
| }); |
| } catch (error) { |
| tracked.completePublicationBarrier(); |
| if (!tracked.settlementStarted) this.continuations.delete(tracked.requestId); |
| throw error; |
| } |
| if (tracked.settlementStarted) { |
| try { |
| await tracked.settlementPromise; |
| throw new RuntimeInteractionInvariantError( |
| `Question ${tracked.requestId} settled during pending-only admission`, |
| ); |
| } finally { |
| tracked.completePublicationBarrier(); |
| } |
| } |
| tracked.admissionState = 'pending'; |
| if (this.publicationsSealed) { |
| tracked.completePublicationBarrier(); |
| throw new RuntimeInteractionInvariantError( |
| `Question ${tracked.requestId} completed admission after Interaction publication sealed`, |
| ); |
| } |
| } |
| |
| async admitFormRequest(input: { |
| request: FormRequestEvent; |
| settlement: HostedFormSettlement; |
| }): Promise<void> { |
| const tracked = this.trackForm(input.request, input.settlement); |
| try { |
| await this.owner.acceptFormRequest({ |
| request: input.request, |
| continuation: tracked.continuation, |
| }); |
| } catch (error) { |
| tracked.completePublicationBarrier(); |
| if (!tracked.settlementStarted) this.continuations.delete(tracked.requestId); |
| throw error; |
| } |
| if (tracked.settlementStarted) { |
| try { |
| await tracked.settlementPromise; |
| throw new RuntimeInteractionInvariantError( |
| `Form ${tracked.requestId} settled during pending-only admission`, |
| ); |
| } finally { |
| tracked.completePublicationBarrier(); |
| } |
| } |
| tracked.admissionState = 'pending'; |
| if (this.publicationsSealed) { |
| tracked.completePublicationBarrier(); |
| throw new RuntimeInteractionInvariantError( |
| `Form ${tracked.requestId} completed admission after Interaction publication sealed`, |
| ); |
| } |
| } |
| |
| withdrawFormRequest(requestId: string): Promise<void> { |
| return this.owner.withdrawFormRequest(requestId); |
| } |
| |
| async admitSandboxBoundaryRequest(input: { |
| request: SandboxBoundaryRequestEvent; |
| settlement: HostedSandboxBoundarySettlement; |
| }): Promise<void> { |
| const tracked = this.trackSandboxBoundary(input.request, input.settlement); |
| try { |
| await this.owner.acceptSandboxBoundaryRequest({ |
| request: input.request, |
| continuation: tracked.continuation, |
| }); |
| } catch (error) { |
| tracked.completePublicationBarrier(); |
| if (!tracked.settlementStarted) this.continuations.delete(tracked.requestId); |
| throw error; |
| } |
| if (tracked.settlementStarted) { |
| try { |
| await tracked.settlementPromise; |
| throw new RuntimeInteractionInvariantError( |
| `Sandbox boundary ${tracked.requestId} settled during pending-only admission`, |
| ); |
| } finally { |
| tracked.completePublicationBarrier(); |
| } |
| } |
| tracked.admissionState = 'pending'; |
| if (this.publicationsSealed) { |
| tracked.completePublicationBarrier(); |
| throw new RuntimeInteractionInvariantError( |
| `Sandbox boundary ${tracked.requestId} completed admission after Interaction publication sealed`, |
| ); |
| } |
| } |
| |
| assertPendingAdmission(request: HostedInteractionRequestEvent): void { |
| const tracked = this.continuations.get(request.requestId); |
| // This synchronous guard is the publication linearization point. Close |
| // blocks new tracking but lets its pre-existing exact admissions reach |
| // this point; settlement still invalidates publication synchronously. |
| if ( |
| this.publicationsSealed || |
| !tracked || |
| tracked.admissionState !== 'pending' || |
| tracked.settlementStarted || |
| tracked.settled || |
| tracked.published || |
| !isDeepStrictEqual(tracked.request, request) |
| ) { |
| this.sealPublications(); |
| throw new RuntimeInteractionInvariantError( |
| `Interaction request ${request.requestId} has no exact pending admission for Run ${this.runId}`, |
| ); |
| } |
| tracked.published = true; |
| tracked.completePublicationBarrier(); |
| } |
| |
| sealPublications(): void { |
| if (this.publicationsSealed) return; |
| this.publicationsSealed = true; |
| for (const tracked of this.continuations.values()) { |
| if ( |
| tracked.admissionState === 'pending' && |
| !tracked.published && |
| !tracked.settlementStarted |
| ) { |
| tracked.completePublicationBarrier(); |
| } |
| } |
| } |
| |
| close(reason: RuntimeInteractionRunClosureReason): Promise<void> { |
| if (this.released) { |
| throw new RuntimeInteractionInvariantError( |
| `Interaction Run ${this.runId} was released before close`, |
| ); |
| } |
| if (this.closePromise) return this.closePromise; |
| this.closeReason = reason; |
| const publicationBarriers = [...this.continuations.values()].map( |
| (tracked) => tracked.publicationBarrier, |
| ); |
| this.closePromise = Promise.all(publicationBarriers) |
| .then(() => this.owner.close(reason)) |
| .catch((error: unknown) => { |
| if ( |
| error instanceof RuntimeInteractionAdmissionRejectedError || |
| error instanceof RuntimeInteractionClosedError || |
| error instanceof RuntimeInteractionInvariantError || |
| error instanceof RuntimeInteractionFailStopError |
| ) { |
| throw error; |
| } |
| throw new RuntimeInteractionFailStopError( |
| `Could not durably close Interaction Run ${this.runId}`, |
| error, |
| ); |
| }); |
| return this.closePromise; |
| } |
| |
| deferLocalClosure(finalizer: LocalClosureFinalizer): void { |
| if (this.localSettlementPromise || this.localClosuresSettled || this.released) { |
| throw new RuntimeInteractionInvariantError( |
| `Interaction Run ${this.runId} registered local closure after settlement`, |
| ); |
| } |
| this.localClosureFinalizers.push(finalizer); |
| } |
| |
| settleLocalClosures(): Promise<void> { |
| if (!this.closePromise) { |
| throw new RuntimeInteractionInvariantError( |
| `Interaction Run ${this.runId} settled local continuations before durable close`, |
| ); |
| } |
| this.localSettlementPromise ??= (async () => { |
| await this.closePromise; |
| for (const finalizer of this.localClosureFinalizers) finalizer(); |
| const escaped = [...this.continuations.values()].filter((tracked) => !tracked.settled); |
| if (escaped.length > 0) { |
| throw new RuntimeInteractionInvariantError( |
| `Interaction Run ${this.runId} closed with unsettled continuations: ${escaped |
| .map((tracked) => tracked.requestId) |
| .join(', ')}`, |
| ); |
| } |
| this.localClosuresSettled = true; |
| })(); |
| return this.localSettlementPromise; |
| } |
| |
| release(): void { |
| if (!this.closePromise || !this.localClosuresSettled) { |
| throw new RuntimeInteractionInvariantError( |
| `Interaction Run ${this.runId} released before durable close and local settlement`, |
| ); |
| } |
| if (this.released) return; |
| try { |
| this.owner.release(); |
| } catch (error) { |
| throw error instanceof RuntimeInteractionInvariantError || |
| error instanceof RuntimeInteractionFailStopError |
| ? error |
| : new RuntimeInteractionFailStopError( |
| `Could not release Interaction Run ${this.runId}`, |
| error, |
| ); |
| } |
| this.released = true; |
| this.continuations.clear(); |
| } |
| |
| private trackQuestion( |
| request: UserQuestionRequestEvent, |
| local: HostedUserQuestionSettlement, |
| ): TrackedQuestionContinuation { |
| this.assertNewContinuation(request); |
| let tracked!: TrackedQuestionContinuation; |
| const publication = createInteractionPublicationBarrier(); |
| const continuation: RuntimeUserQuestionContinuation = Object.freeze({ |
| requestId: request.requestId, |
| turnId: this.turnId, |
| runId: this.runId, |
| waitForPublication: () => publication.publicationBarrier, |
| applyAnswer: (answer: RuntimeUserQuestionAnswer) => |
| this.settleTracked( |
| tracked, |
| () => local.applyAnswer(answer), |
| { kind: 'question_answer', answer }, |
| 'question answer', |
| ), |
| applyClosure: (reason: RuntimeUserQuestionClosureReason) => |
| this.settleTracked( |
| tracked, |
| () => local.applyClosure(reason), |
| { kind: 'closure', reason }, |
| 'question closure', |
| ), |
| }); |
| tracked = { |
| requestId: request.requestId, |
| request, |
| continuation, |
| ...publication, |
| admissionState: undefined, |
| published: false, |
| settlementStarted: false, |
| settled: false, |
| }; |
| this.continuations.set(request.requestId, tracked); |
| return tracked; |
| } |
| |
| private trackForm( |
| request: FormRequestEvent, |
| local: HostedFormSettlement, |
| ): TrackedFormContinuation { |
| this.assertNewContinuation(request); |
| let tracked!: TrackedFormContinuation; |
| const publication = createInteractionPublicationBarrier(); |
| const continuation: RuntimeFormContinuation = Object.freeze({ |
| requestId: request.requestId, |
| turnId: this.turnId, |
| runId: this.runId, |
| waitForPublication: () => publication.publicationBarrier, |
| applyAnswer: (answer: InteractionFormResult) => |
| this.settleTracked( |
| tracked, |
| () => local.applyAnswer(answer), |
| { kind: 'form_answer', answer }, |
| 'form answer', |
| ), |
| applyClosure: (reason: RuntimeUserQuestionClosureReason) => |
| this.settleTracked( |
| tracked, |
| () => local.applyClosure(reason), |
| { kind: 'closure', reason }, |
| 'form closure', |
| ), |
| }); |
| tracked = { |
| requestId: request.requestId, |
| request, |
| continuation, |
| ...publication, |
| admissionState: undefined, |
| published: false, |
| settlementStarted: false, |
| settled: false, |
| }; |
| this.continuations.set(request.requestId, tracked); |
| return tracked; |
| } |
| |
| private trackSandboxBoundary( |
| request: SandboxBoundaryRequestEvent, |
| local: HostedSandboxBoundarySettlement, |
| ): TrackedSandboxBoundaryContinuation { |
| this.assertNewContinuation(request); |
| let tracked!: TrackedSandboxBoundaryContinuation; |
| const publication = createInteractionPublicationBarrier(); |
| const continuation: RuntimeSandboxBoundaryContinuation = Object.freeze({ |
| requestId: request.requestId, |
| turnId: this.turnId, |
| runId: this.runId, |
| waitForPublication: () => publication.publicationBarrier, |
| applyDecision: (settlement: SandboxBoundarySettlement) => |
| this.settleTracked( |
| tracked, |
| () => local.applyDecision(settlement), |
| { kind: 'sandbox_boundary_decision', settlement }, |
| 'sandbox boundary decision', |
| ), |
| applyClosure: (reason: RuntimeUserQuestionClosureReason) => |
| this.settleTracked( |
| tracked, |
| () => local.applyClosure(reason), |
| { kind: 'closure', reason }, |
| 'sandbox boundary closure', |
| ), |
| }); |
| tracked = { |
| requestId: request.requestId, |
| request, |
| continuation, |
| ...publication, |
| admissionState: undefined, |
| published: false, |
| settlementStarted: false, |
| settled: false, |
| }; |
| this.continuations.set(request.requestId, tracked); |
| return tracked; |
| } |
| |
| private assertNewContinuation(request: HostedInteractionRequestEvent): void { |
| if (this.closeReason !== undefined) { |
| throw new RuntimeInteractionAdmissionRejectedError( |
| request.requestId, |
| 'run_closed', |
| this.closeReason, |
| ); |
| } |
| if (this.publicationsSealed || this.released) { |
| throw new RuntimeInteractionInvariantError( |
| `Interaction request ${request.requestId} registered after Run ${this.runId} sealed Interaction publication`, |
| ); |
| } |
| if (request.turnId !== this.turnId) { |
| throw new RuntimeInteractionInvariantError( |
| `Interaction request ${request.requestId} has mismatched Run identity`, |
| ); |
| } |
| if (this.continuations.has(request.requestId)) { |
| throw new RuntimeInteractionInvariantError( |
| `Interaction request ${request.requestId} registered more than once`, |
| ); |
| } |
| } |
| |
| private settleTracked( |
| tracked: TrackedContinuation, |
| apply: () => Promise<void>, |
| outcome: RuntimeHostedInteractionOutcome, |
| operation: string, |
| ): Promise<void> { |
| if (tracked.settlementStarted) { |
| return Promise.reject( |
| new RuntimeInteractionInvariantError( |
| `Interaction ${operation} did not exact-take ${tracked.requestId}`, |
| ), |
| ); |
| } |
| tracked.settlementStarted = true; |
| const settlement = Promise.resolve() |
| .then(apply) |
| .then(() => { |
| tracked.outcome = outcome; |
| tracked.settled = true; |
| }) |
| .finally(() => tracked.completePublicationBarrier()); |
| tracked.settlementPromise = settlement; |
| return settlement; |
| } |
| } |
| |
| function createInteractionPublicationBarrier(): Pick< |
| TrackedContinuationBase, |
| 'publicationBarrier' | 'completePublicationBarrier' |
| > { |
| let complete!: () => void; |
| const publicationBarrier = new Promise<void>((resolve) => { |
| complete = resolve; |
| }); |
| return { |
| publicationBarrier, |
| completePublicationBarrier: complete, |
| }; |
| } |
| |
| function settlementMatchesAck( |
| tracked: TrackedContinuation, |
| event: HostedInteractionSettlementAckEvent, |
| ): boolean { |
| const outcome = tracked.outcome; |
| if (!outcome) return false; |
| if (event.type === 'user_question_answer_ack') return outcome.kind === 'question_answer'; |
| if (event.type === 'form_answer_ack') return outcome.kind === 'form_answer'; |
| if (outcome.kind !== 'sandbox_boundary_decision') return false; |
| const { request, boundary } = outcome.settlement; |
| return ( |
| event.decision === (request.status === 'denied' ? 'deny' : 'allow') && |
| event.status === request.status && |
| event.revision === boundary.revision |
| ); |
| } |
| |
| export async function bindRuntimeInteractionRun( |
| authority: RuntimeInteractionAuthority, |
| identity: RuntimeInteractionRunIdentity, |
| ): Promise<RuntimeInteractionRunBinding> { |
| let owner: RuntimeInteractionRunOwner; |
| try { |
| owner = authority.bindRun(identity); |
| } catch (error) { |
| throw error instanceof RuntimeInteractionInvariantError || |
| error instanceof RuntimeInteractionFailStopError |
| ? error |
| : new RuntimeInteractionFailStopError( |
| `Could not bind Interaction Run ${identity.runId}`, |
| error, |
| ); |
| } |
| if ( |
| owner.sessionId !== identity.sessionId || |
| owner.turnId !== identity.turnId || |
| owner.runId !== identity.runId |
| ) { |
| const mismatch = new RuntimeInteractionInvariantError( |
| 'Interaction authority returned a mismatched Run', |
| ); |
| try { |
| await owner.close('turn_terminal'); |
| owner.release(); |
| } catch (error) { |
| throw new RuntimeInteractionFailStopError( |
| `Could not reclaim mismatched Interaction Run ${identity.runId}`, |
| new AggregateError([mismatch, error]), |
| ); |
| } |
| throw mismatch; |
| } |
| return new RuntimeInteractionRunBinding(owner); |
| } |