@agen-ai/agent-runtime 0.4.0 → 0.4.2

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.
Files changed (45) hide show
  1. package/README.md +200 -9
  2. package/dist/adapterValidation.d.ts +4 -2
  3. package/dist/adapterValidation.js +140 -51
  4. package/dist/artifacts.js +0 -1
  5. package/dist/contractErrors.d.ts +2 -2
  6. package/dist/contractErrors.js +11 -7
  7. package/dist/hostBindings/opening.d.ts +2 -0
  8. package/dist/hostBindings/opening.js +58 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +2 -0
  11. package/dist/nativeContinuations/admission.d.ts +17 -0
  12. package/dist/nativeContinuations/admission.js +250 -0
  13. package/dist/nativeContinuations/index.d.ts +2 -0
  14. package/dist/nativeContinuations/index.js +1 -0
  15. package/dist/nativeContinuations/types.d.ts +52 -0
  16. package/dist/nativeContinuations/types.js +0 -0
  17. package/dist/outputValidation.js +3 -2
  18. package/dist/providerCatalog.d.ts +1 -1
  19. package/dist/providerDriver.d.ts +4 -1
  20. package/dist/providerDriver.js +0 -1
  21. package/dist/providerInstanceRegistry.d.ts +4 -4
  22. package/dist/providerInstanceRegistry.js +2 -1
  23. package/dist/sessionAdmission.d.ts +12 -0
  24. package/dist/sessionAdmission.js +54 -0
  25. package/dist/sessionInteractions/index.d.ts +2 -0
  26. package/dist/sessionInteractions/index.js +1 -0
  27. package/dist/sessionInteractions/lifetime.d.ts +81 -0
  28. package/dist/sessionInteractions/lifetime.js +383 -0
  29. package/dist/sessionInteractions/request.d.ts +43 -0
  30. package/dist/sessionInteractions/request.js +381 -0
  31. package/dist/sessionInteractions/types.d.ts +110 -0
  32. package/dist/sessionInteractions/types.js +10 -0
  33. package/dist/sessionInteractions/validation.d.ts +25 -0
  34. package/dist/sessionInteractions/validation.js +180 -0
  35. package/dist/sessionValidation.d.ts +82 -3
  36. package/dist/sessionValidation.js +240 -181
  37. package/dist/sessions.d.ts +34 -0
  38. package/dist/testing/conformance.d.ts +3 -1
  39. package/dist/testing/conformance.js +180 -86
  40. package/dist/testing/fakeProvider.js +5 -6
  41. package/dist/turnExecution/execution.d.ts +23 -6
  42. package/dist/turnExecution/execution.js +136 -32
  43. package/dist/turnExecution/requests.d.ts +15 -3
  44. package/dist/turnExecution/requests.js +93 -28
  45. package/package.json +2 -2
@@ -44,8 +44,15 @@ import {
44
44
  validateAgentProviderOutputForContext
45
45
  } from "./outputValidation.js";
46
46
  import { validateAgentTurnSteeringResult } from "./steeringValidation.js";
47
- import { createValidatedAgentTurnExecution } from "./turnExecution/execution.js";
47
+ import { createValidatedAgentTurnExecution, adoptValidatedAgentTurnExecution } from "./turnExecution/execution.js";
48
48
  import { validateAgentProviderTurnInterruptionResult } from "./turnExecution/receipts.js";
49
+ import { createAgentTurnRequestAdmission } from "./turnExecution/requests.js";
50
+ import { createAgentSessionAdmission } from "./sessionAdmission.js";
51
+ function validateCloseReason(reason) {
52
+ if (reason !== void 0 && !["idle", "shutdown", "replaced", "contract_rejected", "error", "other"].includes(reason)) {
53
+ throw new TypeError("Provider session close reason is unsupported.");
54
+ }
55
+ }
49
56
  function validateSessionPorts(capabilities, session) {
50
57
  if (!hasExactOwnKeys(session, [
51
58
  "binding",
@@ -158,7 +165,7 @@ function invalidTurnSequence(providerKey, message) {
158
165
  );
159
166
  }
160
167
  function completeTurnSequence(input) {
161
- if (input.pendingRequests.size > 0) {
168
+ if (input.requests.hasUnclaimedRequest()) {
162
169
  invalidTurnSequence(
163
170
  input.providerKey,
164
171
  "Provider completed a turn while an interaction request remained pending."
@@ -171,6 +178,7 @@ const FINAL_DIFF_TRAILING_EVENT_TYPES = [
171
178
  "collaboration.updated",
172
179
  "operation.updated",
173
180
  "provider.diagnostic",
181
+ "request.unavailable",
174
182
  "resource.updated",
175
183
  "runtime.error",
176
184
  "runtime.warning",
@@ -221,29 +229,19 @@ function observeItemLifecycle(input) {
221
229
  terminal: event.type === "item.completed" || TERMINAL_ITEM_STATUSES.has(event.payload.status)
222
230
  });
223
231
  }
232
+ function hasLiveApprovalSubject(request, state) {
233
+ if (request.requestKind !== "approval") return true;
234
+ if (request.subject.kind === "plan") return state.proposedPlans.get(request.subject.artifactId) === request.requestId;
235
+ const item = state.observedItems.get(request.subject.itemId);
236
+ return item !== void 0 && !item.terminal && (item.status === "pending" || item.status === "in_progress" || item.status === "unknown" && item.inProgressObserved);
237
+ }
224
238
  function assertApprovalSubjectCorrelation(input) {
225
- const { request } = input;
226
- if (request.requestKind !== "approval") return;
227
- if (request.subject.kind === "plan") {
228
- if (input.state.proposedPlans.get(request.subject.artifactId) !== request.requestId) {
229
- invalidTurnSequence(
230
- input.providerKey,
231
- "Provider approval request does not identify a proposed plan from this turn."
232
- );
233
- }
234
- return;
235
- }
236
- const item = input.state.observedItems.get(request.subject.itemId);
237
- const itemIsLive = item !== void 0 && !item.terminal && (item.status === "pending" || item.status === "in_progress" || item.status === "unknown" && item.inProgressObserved);
238
- if (!itemIsLive) {
239
- invalidTurnSequence(
240
- input.providerKey,
241
- "Provider approval request does not identify a live item from this turn."
242
- );
239
+ if (!hasLiveApprovalSubject(input.request, input.state)) {
240
+ invalidTurnSequence(input.providerKey, input.request.requestKind === "approval" && input.request.subject.kind === "plan" ? "Provider approval request does not identify a proposed plan from this turn." : "Provider approval request does not identify a live item from this turn.");
243
241
  }
244
242
  }
245
243
  function assertPendingApprovalSubjectCorrelations(input) {
246
- for (const pending of input.pendingRequests.values()) {
244
+ for (const pending of input.requests.unclaimedRequests()) {
247
245
  assertApprovalSubjectCorrelation({
248
246
  providerKey: input.providerKey,
249
247
  request: pending.request,
@@ -299,9 +297,7 @@ function observeTurnEvent(input) {
299
297
  const {
300
298
  contextUsageState,
301
299
  event,
302
- openedRequestIds,
303
- turnRequestIds,
304
- pendingRequests,
300
+ requests,
305
301
  providerKey,
306
302
  state
307
303
  } = input;
@@ -327,7 +323,7 @@ function observeTurnEvent(input) {
327
323
  }
328
324
  if (state.fileChangeMode === "final_diff") {
329
325
  if (event.type === "turn.diff.updated") {
330
- if (pendingRequests.size > 0) {
326
+ if (requests.hasUnclaimedRequest()) {
331
327
  invalidTurnSequence(
332
328
  providerKey,
333
329
  "Provider emitted a terminal diff while an interaction request remained pending."
@@ -351,7 +347,7 @@ function observeTurnEvent(input) {
351
347
  observeItemLifecycle({ providerKey, event, state });
352
348
  assertPendingApprovalSubjectCorrelations({
353
349
  providerKey,
354
- pendingRequests,
350
+ requests,
355
351
  state
356
352
  });
357
353
  if (event.payload.itemKind === "context_compaction" && event.type === "item.completed" && event.payload.status === "completed") {
@@ -364,7 +360,7 @@ function observeTurnEvent(input) {
364
360
  state.proposedPlans.set(event.payload.artifactId, event.payload.requestId);
365
361
  assertPendingApprovalSubjectCorrelations({
366
362
  providerKey,
367
- pendingRequests,
363
+ requests,
368
364
  state
369
365
  });
370
366
  }
@@ -376,34 +372,20 @@ function observeTurnEvent(input) {
376
372
  });
377
373
  }
378
374
  if (event.type === "request.opened") {
379
- if (pendingRequests.size > 0) {
380
- invalidTurnSequence(
381
- providerKey,
382
- "Provider opened more than one pending request for a session."
383
- );
384
- }
385
- const requestId = event.payload.request.requestId;
386
- if (openedRequestIds.has(requestId)) {
387
- invalidTurnSequence(
388
- providerKey,
389
- "Provider reused a request ID within a session."
390
- );
391
- }
392
375
  assertApprovalSubjectCorrelation({
393
376
  providerKey,
394
377
  request: event.payload.request,
395
378
  state
396
379
  });
397
- openedRequestIds.add(requestId);
398
- turnRequestIds.add(requestId);
399
- pendingRequests.set(requestId, {
380
+ requests.open({
400
381
  request: event.payload.request,
401
382
  turnId: event.turnId
402
383
  });
403
384
  }
385
+ if (event.type === "request.unavailable") requests.unavailable(event.payload.requestId);
404
386
  if (event.type === "turn.state_changed") {
405
387
  const waiting = event.payload.state === "waiting_for_request";
406
- if (waiting && !turnRequestIds.has(event.payload.requestId)) {
388
+ if (waiting && !requests.hasOpened(event.payload.requestId)) {
407
389
  invalidTurnSequence(
408
390
  providerKey,
409
391
  "Provider entered a waiting state for a request it did not open."
@@ -411,8 +393,8 @@ function observeTurnEvent(input) {
411
393
  }
412
394
  }
413
395
  if (event.type === "turn.completed") {
414
- if (event.payload.outcome !== "completed") pendingRequests.clear();
415
- completeTurnSequence({ providerKey, state, pendingRequests });
396
+ if (event.payload.outcome !== "completed") requests.withdraw();
397
+ completeTurnSequence({ providerKey, state, requests });
416
398
  }
417
399
  }
418
400
  function observeTurnOutput(input) {
@@ -503,8 +485,7 @@ function observeTurnOutput(input) {
503
485
  event: input.output.event,
504
486
  state: input.state,
505
487
  pendingRequests: input.pendingRequests,
506
- openedRequestIds: input.openedRequestIds,
507
- turnRequestIds: input.turnRequestIds,
488
+ requests: input.requests,
508
489
  contextUsageState: input.contextUsageState
509
490
  });
510
491
  }
@@ -536,14 +517,20 @@ function observeSessionOperationOutput(input) {
536
517
  );
537
518
  }
538
519
  }
520
+ const pendingRequests = /* @__PURE__ */ new Map();
539
521
  observeTurnOutput({
540
522
  capabilities: input.capabilities,
541
523
  providerKey: input.providerKey,
542
524
  output: input.output,
543
525
  state: input.state,
544
- pendingRequests: /* @__PURE__ */ new Map(),
545
- openedRequestIds: /* @__PURE__ */ new Set(),
546
- turnRequestIds: /* @__PURE__ */ new Set(),
526
+ pendingRequests,
527
+ requests: createAgentTurnRequestAdmission({
528
+ providerKey: input.providerKey,
529
+ sessionAdmission: createAgentSessionAdmission(input.providerKey),
530
+ pendingRequests,
531
+ canRestore: () => false,
532
+ canRestoreRequest: () => false
533
+ }),
547
534
  contextUsageState: input.contextUsageState,
548
535
  interactionState: input.interactionState
549
536
  });
@@ -632,6 +619,146 @@ function assertAgentTurnInputCapability(input) {
632
619
  });
633
620
  if (textIssue) rejectReference(`${textIssue.target} must use ${textIssue.format} text`);
634
621
  }
622
+ function createAgentSessionValidationOwner(capabilities, sessionId) {
623
+ const providerKey = capabilities.providerKey;
624
+ const sessionLifetime = new AbortController();
625
+ const sessionAdmission = createAgentSessionAdmission(providerKey);
626
+ const contextUsageState = {
627
+ latestByScope: /* @__PURE__ */ new Map(),
628
+ cumulativeByScope: /* @__PURE__ */ new Map(),
629
+ compactionReadyScopes: /* @__PURE__ */ new Set()
630
+ };
631
+ const interactionState = {
632
+ operationResults: /* @__PURE__ */ new Map(),
633
+ operationInvocations: /* @__PURE__ */ new Map(),
634
+ collaborationNodes: /* @__PURE__ */ new Map(),
635
+ collaborationSpawnReservations: /* @__PURE__ */ new Map(),
636
+ generatedResources: /* @__PURE__ */ new Map()
637
+ };
638
+ let closed = false;
639
+ let unusable = false;
640
+ let closing;
641
+ const requireOpen = () => {
642
+ if (closed)
643
+ throwAgentProviderContractError(
644
+ providerKey,
645
+ "session_closed",
646
+ "Provider session is closed."
647
+ );
648
+ };
649
+ const requireUsable = () => {
650
+ requireOpen();
651
+ if (unusable) {
652
+ throwAgentProviderContractError(
653
+ providerKey,
654
+ "session_unusable",
655
+ "Provider session cannot be reused after an incomplete operation."
656
+ );
657
+ }
658
+ };
659
+ const outputContext = (turnId) => ({
660
+ capabilities,
661
+ providerKey,
662
+ sessionId,
663
+ ...turnId === void 0 ? {} : { turnId }
664
+ });
665
+ function installExecution(token, turnId, source) {
666
+ const pendingRequests = /* @__PURE__ */ new Map();
667
+ const state = {
668
+ fileChangeMode: capabilities.output.fileChanges,
669
+ observedItems: /* @__PURE__ */ new Map(),
670
+ proposedPlans: /* @__PURE__ */ new Map(),
671
+ generatedResourceIds: /* @__PURE__ */ new Set(),
672
+ started: false,
673
+ terminal: false,
674
+ finalDiffObserved: false
675
+ };
676
+ const inFlightOperations = /* @__PURE__ */ new Set();
677
+ const common = {
678
+ providerKey,
679
+ pendingRequests,
680
+ sessionAdmission,
681
+ inFlightOperations,
682
+ requireUsable,
683
+ isTerminal: () => state.terminal,
684
+ canRestoreRequest: (pending) => hasLiveApprovalSubject(pending.request, state),
685
+ observe(candidate, requests) {
686
+ const output = validateAgentProviderOutputForContext(candidate, outputContext(turnId));
687
+ observeTurnOutput({
688
+ capabilities,
689
+ providerKey,
690
+ output,
691
+ state,
692
+ pendingRequests,
693
+ requests,
694
+ contextUsageState,
695
+ interactionState
696
+ });
697
+ return output;
698
+ },
699
+ assertCompleted() {
700
+ if (!state.started || !state.terminal || pendingRequests.size > 0) {
701
+ invalidTurnSequence(providerKey, "Provider turn output must remain open until actual terminal completion.");
702
+ }
703
+ },
704
+ release() {
705
+ if (source.kind === "caller") sessionAdmission.releaseExecution(token);
706
+ },
707
+ retire() {
708
+ unusable = true;
709
+ },
710
+ abandon: () => closing ? closing() : Promise.reject(new Error("Session cleanup is waiting for its opening owner."))
711
+ };
712
+ const owner = source.kind === "caller" ? createValidatedAgentTurnExecution({ ...common, turnInput: source.input, start: source.start }) : adoptValidatedAgentTurnExecution({ ...common, turnId, createCandidate: source.createCandidate });
713
+ try {
714
+ sessionAdmission.installExecution(token, { turnId, pendingRequests, state, inFlightOperations, owner });
715
+ } catch (cause) {
716
+ owner.stop(cause instanceof Error ? cause : new Error("Execution installation failed.", { cause }));
717
+ throw cause;
718
+ }
719
+ return owner;
720
+ }
721
+ return {
722
+ sessionAdmission,
723
+ sessionLifetime,
724
+ contextUsageState,
725
+ interactionState,
726
+ outputContext,
727
+ requireUsable,
728
+ get activeTurn() {
729
+ return sessionAdmission.execution;
730
+ },
731
+ get closed() {
732
+ return closed;
733
+ },
734
+ get unusable() {
735
+ return unusable;
736
+ },
737
+ fence() {
738
+ unusable = true;
739
+ },
740
+ stop() {
741
+ unusable = true;
742
+ sessionAdmission.stop();
743
+ sessionLifetime.abort(new DOMException("The provider session was closed.", "AbortError"));
744
+ },
745
+ completeClose() {
746
+ closed = true;
747
+ },
748
+ installClose(close) {
749
+ closing = close;
750
+ },
751
+ reserveExecution: sessionAdmission.reserveExecution,
752
+ ownsExecution: sessionAdmission.ownsExecution,
753
+ releaseExecution: sessionAdmission.releaseExecution,
754
+ installNativeExecution(token, turnId, createCandidate) {
755
+ return installExecution(token, turnId, { kind: "native", createCandidate });
756
+ },
757
+ installCallerExecution(token, turnInput, start) {
758
+ return installExecution(token, turnInput.turnId, { kind: "caller", input: turnInput, start });
759
+ }
760
+ };
761
+ }
635
762
  function validateAgentProviderSession(input) {
636
763
  const { capabilities, sessionId } = input;
637
764
  const providerKey = capabilities.providerKey;
@@ -667,57 +794,22 @@ function validateAgentProviderSession(input) {
667
794
  );
668
795
  }
669
796
  validateSessionPorts(capabilities, input.candidate);
670
- const sessionLifetime = new AbortController();
797
+ const validationOwner = input.validationOwner ?? createAgentSessionValidationOwner(capabilities, sessionId);
798
+ const { sessionLifetime, contextUsageState, interactionState, outputContext } = validationOwner;
799
+ const collaborationNodes = interactionState.collaborationNodes;
800
+ const collaborationSpawnReservations = interactionState.collaborationSpawnReservations;
801
+ const requireUsable = () => {
802
+ input.sessionInteractionOwner?.requireUsable();
803
+ validationOwner.requireUsable();
804
+ };
671
805
  const environment = validateAgentEnvironmentObservationPort({
672
806
  providerKey,
673
807
  capability: capabilities.environment.session,
674
808
  port: input.candidate.environment,
675
809
  signal: sessionLifetime.signal
676
810
  });
677
- const openedRequestIds = /* @__PURE__ */ new Set();
678
- const contextUsageState = {
679
- latestByScope: /* @__PURE__ */ new Map(),
680
- cumulativeByScope: /* @__PURE__ */ new Map(),
681
- compactionReadyScopes: /* @__PURE__ */ new Set()
682
- };
683
- const interactionState = {
684
- operationResults: /* @__PURE__ */ new Map(),
685
- operationInvocations: /* @__PURE__ */ new Map(),
686
- collaborationNodes: /* @__PURE__ */ new Map(),
687
- collaborationSpawnReservations: /* @__PURE__ */ new Map(),
688
- generatedResources: /* @__PURE__ */ new Map()
689
- };
690
- const collaborationNodes = interactionState.collaborationNodes;
691
- const collaborationSpawnReservations = interactionState.collaborationSpawnReservations;
692
- let activeTurn = null;
693
811
  let closePromise = null;
694
- let closed = false;
695
812
  let providerClosed = false;
696
- let unusable = false;
697
- const requireOpen = () => {
698
- if (closed)
699
- throwAgentProviderContractError(
700
- providerKey,
701
- "session_closed",
702
- "Provider session is closed."
703
- );
704
- };
705
- const requireUsable = () => {
706
- requireOpen();
707
- if (unusable) {
708
- throwAgentProviderContractError(
709
- providerKey,
710
- "session_unusable",
711
- "Provider session cannot be reused after an incomplete operation."
712
- );
713
- }
714
- };
715
- const outputContext = (turnId) => ({
716
- capabilities,
717
- providerKey,
718
- sessionId,
719
- ...turnId === void 0 ? {} : { turnId }
720
- });
721
813
  const delegateProviderMutation = async (input2) => {
722
814
  let executionStarted = false;
723
815
  const onProviderExecutionStarted = () => {
@@ -742,6 +834,7 @@ function validateAgentProviderSession(input) {
742
834
  await input2.observeOutput?.(candidate);
743
835
  };
744
836
  try {
837
+ requireUsable();
745
838
  const candidate = await input2.invoke(onProviderExecutionStarted, onOutput);
746
839
  if (!executionStarted) {
747
840
  throwAgentProviderContractError(
@@ -753,7 +846,7 @@ function validateAgentProviderSession(input) {
753
846
  return input2.validate(candidate);
754
847
  } catch (error) {
755
848
  if (!executionStarted) throw error;
756
- unusable = true;
849
+ validationOwner.fence();
757
850
  throw new AgentProviderDelegatedOperationError(
758
851
  providerKey,
759
852
  input2.operation,
@@ -779,63 +872,17 @@ function validateAgentProviderSession(input) {
779
872
  `Provider ${providerKey} does not support ${parsedInput.interactionMode} turn interaction mode.`
780
873
  );
781
874
  }
782
- if (activeTurn !== null) {
783
- throwAgentProviderContractError(providerKey, "concurrent_turn", "Provider session already has an active turn.");
784
- }
785
- const pendingRequests = /* @__PURE__ */ new Map();
786
- const turnRequestIds = /* @__PURE__ */ new Set();
787
- const state = {
788
- fileChangeMode: capabilities.output.fileChanges,
789
- observedItems: /* @__PURE__ */ new Map(),
790
- proposedPlans: /* @__PURE__ */ new Map(),
791
- generatedResourceIds: /* @__PURE__ */ new Set(),
792
- started: false,
793
- terminal: false,
794
- finalDiffObserved: false
795
- };
796
- const inFlightOperations = /* @__PURE__ */ new Set();
797
- const owner = createValidatedAgentTurnExecution({
798
- providerKey,
799
- turnInput: {
875
+ const token = validationOwner.reserveExecution();
876
+ try {
877
+ return validationOwner.installCallerExecution(token, {
800
878
  ...parsedInput,
801
879
  ...turnInput.signal === void 0 ? {} : { signal: turnInput.signal },
802
880
  ...turnInput.onProviderExecutionStarted === void 0 ? {} : { onProviderExecutionStarted: turnInput.onProviderExecutionStarted }
803
- },
804
- pendingRequests,
805
- inFlightOperations,
806
- start: (inputTurn) => input.candidate.startTurn(inputTurn),
807
- requireUsable,
808
- isTerminal: () => state.terminal,
809
- observe(candidate) {
810
- const output = validateAgentProviderOutputForContext(candidate, outputContext(parsedInput.turnId));
811
- observeTurnOutput({
812
- capabilities,
813
- providerKey,
814
- output,
815
- state,
816
- pendingRequests,
817
- openedRequestIds,
818
- turnRequestIds,
819
- contextUsageState,
820
- interactionState
821
- });
822
- return output;
823
- },
824
- assertCompleted() {
825
- if (!state.started || !state.terminal || pendingRequests.size > 0) {
826
- invalidTurnSequence(providerKey, "Provider turn output must remain open until actual terminal completion.");
827
- }
828
- },
829
- release() {
830
- if (activeTurn?.owner === owner) activeTurn = null;
831
- },
832
- retire() {
833
- unusable = true;
834
- },
835
- abandon: () => close({ reason: "other" })
836
- });
837
- activeTurn = { turnId: parsedInput.turnId, pendingRequests, state, inFlightOperations, owner };
838
- return owner.execution;
881
+ }, (inputTurn) => input.candidate.startTurn(inputTurn)).execution;
882
+ } catch (error) {
883
+ validationOwner.releaseExecution(token);
884
+ throw error;
885
+ }
839
886
  };
840
887
  const declaredInterruption = input.candidate.interruption;
841
888
  const interruption = declaredInterruption.kind === "unsupported" ? Object.freeze({ kind: "unsupported" }) : Object.freeze({
@@ -848,7 +895,7 @@ function validateAgentProviderSession(input) {
848
895
  reason: interruptionInput.reason,
849
896
  ...interruptionInput.requestedAt === void 0 ? {} : { requestedAt: interruptionInput.requestedAt }
850
897
  });
851
- const targetedActiveTurn = activeTurn?.turnId === parsed.turnId ? activeTurn : null;
898
+ const targetedActiveTurn = validationOwner.activeTurn?.turnId === parsed.turnId ? validationOwner.activeTurn : null;
852
899
  const targetedTurn = targetedActiveTurn;
853
900
  if (targetedTurn === null || targetedTurn.state.terminal || !targetedTurn.owner.hasEntered()) {
854
901
  throwAgentProviderContractError(
@@ -873,7 +920,7 @@ function validateAgentProviderSession(input) {
873
920
  );
874
921
  } catch (error) {
875
922
  suspendedRequests.withdraw();
876
- unusable = true;
923
+ validationOwner.fence();
877
924
  throw error;
878
925
  }
879
926
  if (result.status === "failed") {
@@ -892,7 +939,7 @@ function validateAgentProviderSession(input) {
892
939
  requireUsable();
893
940
  throwIfAgentOperationAborted(steeringInput.signal);
894
941
  const turnId = parseAgentTurnId(steeringInput.turnId);
895
- if (activeTurn?.turnId !== turnId || !activeTurn.owner.hasEntered() || activeTurn.state.terminal || activeTurn.pendingRequests.size > 0) {
942
+ if (validationOwner.activeTurn?.turnId !== turnId || !validationOwner.activeTurn.owner.hasEntered() || validationOwner.activeTurn.state.terminal || validationOwner.activeTurn.pendingRequests.size > 0) {
896
943
  throwAgentProviderContractError(
897
944
  providerKey,
898
945
  "active_turn_mismatch",
@@ -908,7 +955,7 @@ function validateAgentProviderSession(input) {
908
955
  parts: content.parts,
909
956
  providerKey
910
957
  });
911
- const targetedActiveTurn = activeTurn;
958
+ const targetedActiveTurn = validationOwner.activeTurn;
912
959
  const settleSteering = trackActiveTurnOperation(targetedActiveTurn);
913
960
  try {
914
961
  try {
@@ -920,10 +967,10 @@ function validateAgentProviderSession(input) {
920
967
  }),
921
968
  providerKey
922
969
  );
923
- if (result.status === "delivery_uncertain") unusable = true;
970
+ if (result.status === "delivery_uncertain") validationOwner.fence();
924
971
  return result;
925
972
  } catch (error) {
926
- unusable = true;
973
+ validationOwner.fence();
927
974
  throw new AgentProviderDelegatedOperationError(
928
975
  providerKey,
929
976
  "steer_turn",
@@ -1386,39 +1433,35 @@ function validateAgentProviderSession(input) {
1386
1433
  return inspection;
1387
1434
  }
1388
1435
  });
1389
- const close = (closeInput) => {
1390
- if (closed) return Promise.resolve();
1391
- if (closePromise) return closePromise;
1436
+ const closeOwned = (closeInput, explicit) => {
1437
+ if (validationOwner.closed && !input.sessionInteractionOwner) return Promise.resolve();
1438
+ if (!input.sessionInteractionOwner && closePromise) return closePromise;
1392
1439
  throwIfAgentOperationAborted(closeInput.signal);
1393
- if (closeInput.reason !== void 0 && ![
1394
- "idle",
1395
- "shutdown",
1396
- "replaced",
1397
- "contract_rejected",
1398
- "error",
1399
- "other"
1400
- ].includes(closeInput.reason)) {
1401
- throw new TypeError("Provider session close reason is unsupported.");
1402
- }
1403
- unusable = true;
1404
- sessionLifetime.abort(new DOMException("The provider session was closed.", "AbortError"));
1440
+ validateCloseReason(closeInput.reason);
1441
+ validationOwner.fence();
1442
+ validationOwner.stop();
1405
1443
  input.hostBindingLease?.stop();
1406
- const closingTurn = activeTurn;
1444
+ const closingTurn = validationOwner.activeTurn;
1407
1445
  closingTurn?.owner.stop(new DOMException("The provider session was closed.", "AbortError"));
1408
- closePromise = Promise.resolve().then(async () => {
1446
+ const cleanup = async () => {
1409
1447
  if (!providerClosed) {
1410
1448
  await input.candidate.close(closeInput);
1411
1449
  providerClosed = true;
1412
1450
  }
1451
+ await input.sessionInteractionOwner?.join();
1413
1452
  await closingTurn?.owner.join();
1414
1453
  await input.hostBindingLease?.release();
1415
- }).then(() => {
1416
- closed = true;
1417
- }).finally(() => {
1454
+ validationOwner.completeClose();
1455
+ };
1456
+ if (input.sessionInteractionOwner) return input.sessionInteractionOwner.close(cleanup, explicit);
1457
+ closePromise = Promise.resolve().then(cleanup).finally(() => {
1418
1458
  closePromise = null;
1419
1459
  });
1420
1460
  return closePromise;
1421
1461
  };
1462
+ const close = (closeInput) => closeOwned(closeInput, true);
1463
+ validationOwner.installClose(() => closeOwned({ reason: "error" }, false));
1464
+ input.sessionInteractionOwner?.installClose(() => closeOwned({ reason: "error" }, false));
1422
1465
  return Object.freeze({
1423
1466
  binding,
1424
1467
  startTurn,
@@ -1433,9 +1476,10 @@ function validateAgentProviderSession(input) {
1433
1476
  close
1434
1477
  });
1435
1478
  }
1436
- async function closeRejectedAgentProviderSession(session, error, hostBinding, opening) {
1479
+ async function closeRejectedAgentProviderSession(session, error, hostBinding, opening, interactions) {
1437
1480
  if (!session) throw error;
1438
1481
  hostBinding?.lease.stop();
1482
+ interactions?.stop();
1439
1483
  let providerClosed = false;
1440
1484
  const cleanup = async () => {
1441
1485
  if (!providerClosed) {
@@ -1445,24 +1489,26 @@ async function closeRejectedAgentProviderSession(session, error, hostBinding, op
1445
1489
  await session.close({ reason: "contract_rejected" });
1446
1490
  providerClosed = true;
1447
1491
  }
1492
+ await interactions?.join();
1448
1493
  await hostBinding?.lease.release();
1449
1494
  };
1450
1495
  try {
1451
- await cleanup();
1496
+ if (interactions) await interactions.close(cleanup, false);
1497
+ else await cleanup();
1452
1498
  } catch (cleanupError) {
1453
1499
  if (hostBinding) {
1454
1500
  const retained = createAgentHostBindingCleanupError({
1455
1501
  providerKey: hostBinding.providerKey,
1456
1502
  lease: hostBinding.lease,
1457
1503
  errors: [error, cleanupError],
1458
- retryCleanup: cleanup
1504
+ retryCleanup: () => interactions ? interactions.close(cleanup, false) : cleanup()
1459
1505
  });
1460
1506
  throw opening ? associateAgentSessionCleanupError(retained, opening.input) : retained;
1461
1507
  }
1462
1508
  if (opening) throw associateAgentSessionCleanupError(new AgentProviderSessionCleanupError({
1463
1509
  providerKey: opening.providerKey,
1464
1510
  errors: [error, cleanupError],
1465
- retryCleanup: cleanup
1511
+ retryCleanup: () => interactions ? interactions.close(cleanup, false) : cleanup()
1466
1512
  }), opening.input);
1467
1513
  throw new AggregateError(
1468
1514
  [error, cleanupError],
@@ -1499,8 +1545,14 @@ function bindingTracker(input) {
1499
1545
  "Provider branch reused its source binding."
1500
1546
  );
1501
1547
  }
1502
- input.observer(binding);
1503
1548
  observed = binding;
1549
+ try {
1550
+ input.interactions?.bind();
1551
+ input.observer(binding);
1552
+ } catch (cause) {
1553
+ input.interactions?.fail(cause);
1554
+ throw cause;
1555
+ }
1504
1556
  },
1505
1557
  requireMatch(binding) {
1506
1558
  if (!observed) {
@@ -1524,19 +1576,24 @@ async function openIdentityCreatingAgentProviderSession(input) {
1524
1576
  const tracker = bindingTracker({
1525
1577
  providerKey: input.capabilities.providerKey,
1526
1578
  observer: input.observer,
1579
+ ...input.sessionInteractionOwner === void 0 ? {} : { interactions: input.sessionInteractionOwner },
1527
1580
  ...input.sourceBinding === void 0 ? {} : { sourceBinding: input.sourceBinding }
1528
1581
  });
1529
1582
  let candidate = null;
1530
1583
  try {
1531
1584
  candidate = await input.open(tracker.observe);
1585
+ tracker.requireMatch(parseAgentSessionBinding(candidate.binding));
1532
1586
  const session = validateAgentProviderSession({
1533
1587
  capabilities: input.capabilities,
1534
1588
  sessionId: input.sessionId,
1535
1589
  candidate,
1590
+ ...input.validationOwner === void 0 ? {} : { validationOwner: input.validationOwner },
1591
+ ...input.sessionInteractionOwner === void 0 ? {} : { sessionInteractionOwner: input.sessionInteractionOwner },
1536
1592
  ...input.hostBindingLease === void 0 ? {} : { hostBindingLease: input.hostBindingLease },
1537
1593
  ...input.sourceBinding === void 0 ? {} : { sourceBinding: input.sourceBinding }
1538
1594
  });
1539
1595
  tracker.requireMatch(session.binding);
1596
+ input.sessionInteractionOwner?.adopt();
1540
1597
  return session;
1541
1598
  } catch (error) {
1542
1599
  return closeRejectedAgentProviderSession(
@@ -1546,12 +1603,14 @@ async function openIdentityCreatingAgentProviderSession(input) {
1546
1603
  providerKey: input.capabilities.providerKey,
1547
1604
  lease: input.hostBindingLease
1548
1605
  },
1549
- input.openingInput === void 0 ? void 0 : { providerKey: input.capabilities.providerKey, input: input.openingInput }
1606
+ input.openingInput === void 0 ? void 0 : { providerKey: input.capabilities.providerKey, input: input.openingInput },
1607
+ input.sessionInteractionOwner
1550
1608
  );
1551
1609
  }
1552
1610
  }
1553
1611
  export {
1554
1612
  closeRejectedAgentProviderSession,
1613
+ createAgentSessionValidationOwner,
1555
1614
  openIdentityCreatingAgentProviderSession,
1556
1615
  validateAgentProviderSession
1557
1616
  };