@agen-ai/agent-runtime 0.4.1 → 0.4.3

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 (37) hide show
  1. package/README.md +176 -74
  2. package/dist/adapterValidation.d.ts +4 -2
  3. package/dist/adapterValidation.js +108 -59
  4. package/dist/contractErrors.d.ts +1 -1
  5. package/dist/contractErrors.js +7 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.js +1 -0
  8. package/dist/nativeContinuations/admission.d.ts +17 -0
  9. package/dist/nativeContinuations/admission.js +250 -0
  10. package/dist/nativeContinuations/index.d.ts +2 -0
  11. package/dist/nativeContinuations/index.js +1 -0
  12. package/dist/nativeContinuations/types.d.ts +52 -0
  13. package/dist/nativeContinuations/types.js +0 -0
  14. package/dist/outputValidation.js +1 -0
  15. package/dist/providerCatalog.d.ts +1 -1
  16. package/dist/providerDriver.d.ts +4 -1
  17. package/dist/providerInstanceRegistry.d.ts +4 -4
  18. package/dist/sessionAdmission.d.ts +14 -0
  19. package/dist/sessionAdmission.js +67 -0
  20. package/dist/sessionInteractions/lifetime.d.ts +32 -3
  21. package/dist/sessionInteractions/lifetime.js +145 -13
  22. package/dist/sessionInteractions/request.d.ts +3 -2
  23. package/dist/sessionInteractions/request.js +98 -25
  24. package/dist/sessionInteractions/types.d.ts +68 -12
  25. package/dist/sessionInteractions/validation.d.ts +13 -9
  26. package/dist/sessionInteractions/validation.js +121 -60
  27. package/dist/sessionValidation.d.ts +80 -2
  28. package/dist/sessionValidation.js +348 -283
  29. package/dist/sessions.d.ts +33 -2
  30. package/dist/testing/conformance.d.ts +3 -1
  31. package/dist/testing/conformance.js +180 -85
  32. package/dist/testing/fakeProvider.js +2 -2
  33. package/dist/turnExecution/execution.d.ts +23 -6
  34. package/dist/turnExecution/execution.js +136 -32
  35. package/dist/turnExecution/requests.d.ts +15 -3
  36. package/dist/turnExecution/requests.js +93 -28
  37. package/package.json +2 -2
@@ -44,8 +44,10 @@ 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";
49
51
  function validateCloseReason(reason) {
50
52
  if (reason !== void 0 && !["idle", "shutdown", "replaced", "contract_rejected", "error", "other"].includes(reason)) {
51
53
  throw new TypeError("Provider session close reason is unsupported.");
@@ -163,7 +165,7 @@ function invalidTurnSequence(providerKey, message) {
163
165
  );
164
166
  }
165
167
  function completeTurnSequence(input) {
166
- if (input.pendingRequests.size > 0) {
168
+ if (input.requests.hasUnclaimedRequest()) {
167
169
  invalidTurnSequence(
168
170
  input.providerKey,
169
171
  "Provider completed a turn while an interaction request remained pending."
@@ -176,6 +178,7 @@ const FINAL_DIFF_TRAILING_EVENT_TYPES = [
176
178
  "collaboration.updated",
177
179
  "operation.updated",
178
180
  "provider.diagnostic",
181
+ "request.unavailable",
179
182
  "resource.updated",
180
183
  "runtime.error",
181
184
  "runtime.warning",
@@ -226,29 +229,19 @@ function observeItemLifecycle(input) {
226
229
  terminal: event.type === "item.completed" || TERMINAL_ITEM_STATUSES.has(event.payload.status)
227
230
  });
228
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
+ }
229
238
  function assertApprovalSubjectCorrelation(input) {
230
- const { request } = input;
231
- if (request.requestKind !== "approval") return;
232
- if (request.subject.kind === "plan") {
233
- if (input.state.proposedPlans.get(request.subject.artifactId) !== request.requestId) {
234
- invalidTurnSequence(
235
- input.providerKey,
236
- "Provider approval request does not identify a proposed plan from this turn."
237
- );
238
- }
239
- return;
240
- }
241
- const item = input.state.observedItems.get(request.subject.itemId);
242
- const itemIsLive = item !== void 0 && !item.terminal && (item.status === "pending" || item.status === "in_progress" || item.status === "unknown" && item.inProgressObserved);
243
- if (!itemIsLive) {
244
- invalidTurnSequence(
245
- input.providerKey,
246
- "Provider approval request does not identify a live item from this turn."
247
- );
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.");
248
241
  }
249
242
  }
250
243
  function assertPendingApprovalSubjectCorrelations(input) {
251
- for (const pending of input.pendingRequests.values()) {
244
+ for (const pending of input.requests.unclaimedRequests()) {
252
245
  assertApprovalSubjectCorrelation({
253
246
  providerKey: input.providerKey,
254
247
  request: pending.request,
@@ -304,9 +297,7 @@ function observeTurnEvent(input) {
304
297
  const {
305
298
  contextUsageState,
306
299
  event,
307
- openedRequestIds,
308
- turnRequestIds,
309
- pendingRequests,
300
+ requests,
310
301
  providerKey,
311
302
  state
312
303
  } = input;
@@ -332,7 +323,7 @@ function observeTurnEvent(input) {
332
323
  }
333
324
  if (state.fileChangeMode === "final_diff") {
334
325
  if (event.type === "turn.diff.updated") {
335
- if (pendingRequests.size > 0) {
326
+ if (requests.hasUnclaimedRequest()) {
336
327
  invalidTurnSequence(
337
328
  providerKey,
338
329
  "Provider emitted a terminal diff while an interaction request remained pending."
@@ -356,7 +347,7 @@ function observeTurnEvent(input) {
356
347
  observeItemLifecycle({ providerKey, event, state });
357
348
  assertPendingApprovalSubjectCorrelations({
358
349
  providerKey,
359
- pendingRequests,
350
+ requests,
360
351
  state
361
352
  });
362
353
  if (event.payload.itemKind === "context_compaction" && event.type === "item.completed" && event.payload.status === "completed") {
@@ -369,7 +360,7 @@ function observeTurnEvent(input) {
369
360
  state.proposedPlans.set(event.payload.artifactId, event.payload.requestId);
370
361
  assertPendingApprovalSubjectCorrelations({
371
362
  providerKey,
372
- pendingRequests,
363
+ requests,
373
364
  state
374
365
  });
375
366
  }
@@ -381,34 +372,20 @@ function observeTurnEvent(input) {
381
372
  });
382
373
  }
383
374
  if (event.type === "request.opened") {
384
- if (pendingRequests.size > 0) {
385
- invalidTurnSequence(
386
- providerKey,
387
- "Provider opened more than one pending request for a session."
388
- );
389
- }
390
- const requestId = event.payload.request.requestId;
391
- if (openedRequestIds.has(requestId)) {
392
- invalidTurnSequence(
393
- providerKey,
394
- "Provider reused a request ID within a session."
395
- );
396
- }
397
375
  assertApprovalSubjectCorrelation({
398
376
  providerKey,
399
377
  request: event.payload.request,
400
378
  state
401
379
  });
402
- openedRequestIds.add(requestId);
403
- turnRequestIds.add(requestId);
404
- pendingRequests.set(requestId, {
380
+ requests.open({
405
381
  request: event.payload.request,
406
382
  turnId: event.turnId
407
383
  });
408
384
  }
385
+ if (event.type === "request.unavailable") requests.unavailable(event.payload.requestId);
409
386
  if (event.type === "turn.state_changed") {
410
387
  const waiting = event.payload.state === "waiting_for_request";
411
- if (waiting && !turnRequestIds.has(event.payload.requestId)) {
388
+ if (waiting && !requests.hasOpened(event.payload.requestId)) {
412
389
  invalidTurnSequence(
413
390
  providerKey,
414
391
  "Provider entered a waiting state for a request it did not open."
@@ -416,8 +393,8 @@ function observeTurnEvent(input) {
416
393
  }
417
394
  }
418
395
  if (event.type === "turn.completed") {
419
- if (event.payload.outcome !== "completed") pendingRequests.clear();
420
- completeTurnSequence({ providerKey, state, pendingRequests });
396
+ if (event.payload.outcome !== "completed") requests.withdraw();
397
+ completeTurnSequence({ providerKey, state, requests });
421
398
  }
422
399
  }
423
400
  function observeTurnOutput(input) {
@@ -508,8 +485,7 @@ function observeTurnOutput(input) {
508
485
  event: input.output.event,
509
486
  state: input.state,
510
487
  pendingRequests: input.pendingRequests,
511
- openedRequestIds: input.openedRequestIds,
512
- turnRequestIds: input.turnRequestIds,
488
+ requests: input.requests,
513
489
  contextUsageState: input.contextUsageState
514
490
  });
515
491
  }
@@ -523,6 +499,7 @@ const SESSION_OPERATION_OBSERVATION_EVENT_TYPES = /* @__PURE__ */ new Set([
523
499
  "resource.updated",
524
500
  "runtime.warning",
525
501
  "runtime.error",
502
+ "runtime.retrying",
526
503
  "provider.diagnostic"
527
504
  ]);
528
505
  function observeSessionOperationOutput(input) {
@@ -541,14 +518,20 @@ function observeSessionOperationOutput(input) {
541
518
  );
542
519
  }
543
520
  }
521
+ const pendingRequests = /* @__PURE__ */ new Map();
544
522
  observeTurnOutput({
545
523
  capabilities: input.capabilities,
546
524
  providerKey: input.providerKey,
547
525
  output: input.output,
548
526
  state: input.state,
549
- pendingRequests: /* @__PURE__ */ new Map(),
550
- openedRequestIds: /* @__PURE__ */ new Set(),
551
- turnRequestIds: /* @__PURE__ */ new Set(),
527
+ pendingRequests,
528
+ requests: createAgentTurnRequestAdmission({
529
+ providerKey: input.providerKey,
530
+ sessionAdmission: createAgentSessionAdmission(input.providerKey),
531
+ pendingRequests,
532
+ canRestore: () => false,
533
+ canRestoreRequest: () => false
534
+ }),
552
535
  contextUsageState: input.contextUsageState,
553
536
  interactionState: input.interactionState
554
537
  });
@@ -637,6 +620,146 @@ function assertAgentTurnInputCapability(input) {
637
620
  });
638
621
  if (textIssue) rejectReference(`${textIssue.target} must use ${textIssue.format} text`);
639
622
  }
623
+ function createAgentSessionValidationOwner(capabilities, sessionId) {
624
+ const providerKey = capabilities.providerKey;
625
+ const sessionLifetime = new AbortController();
626
+ const sessionAdmission = createAgentSessionAdmission(providerKey);
627
+ const contextUsageState = {
628
+ latestByScope: /* @__PURE__ */ new Map(),
629
+ cumulativeByScope: /* @__PURE__ */ new Map(),
630
+ compactionReadyScopes: /* @__PURE__ */ new Set()
631
+ };
632
+ const interactionState = {
633
+ operationResults: /* @__PURE__ */ new Map(),
634
+ operationInvocations: /* @__PURE__ */ new Map(),
635
+ collaborationNodes: /* @__PURE__ */ new Map(),
636
+ collaborationSpawnReservations: /* @__PURE__ */ new Map(),
637
+ generatedResources: /* @__PURE__ */ new Map()
638
+ };
639
+ let closed = false;
640
+ let unusable = false;
641
+ let closing;
642
+ const requireOpen = () => {
643
+ if (closed)
644
+ throwAgentProviderContractError(
645
+ providerKey,
646
+ "session_closed",
647
+ "Provider session is closed."
648
+ );
649
+ };
650
+ const requireUsable = () => {
651
+ requireOpen();
652
+ if (unusable) {
653
+ throwAgentProviderContractError(
654
+ providerKey,
655
+ "session_unusable",
656
+ "Provider session cannot be reused after an incomplete operation."
657
+ );
658
+ }
659
+ };
660
+ const outputContext = (turnId) => ({
661
+ capabilities,
662
+ providerKey,
663
+ sessionId,
664
+ ...turnId === void 0 ? {} : { turnId }
665
+ });
666
+ function installExecution(token, turnId, source) {
667
+ const pendingRequests = /* @__PURE__ */ new Map();
668
+ const state = {
669
+ fileChangeMode: capabilities.output.fileChanges,
670
+ observedItems: /* @__PURE__ */ new Map(),
671
+ proposedPlans: /* @__PURE__ */ new Map(),
672
+ generatedResourceIds: /* @__PURE__ */ new Set(),
673
+ started: false,
674
+ terminal: false,
675
+ finalDiffObserved: false
676
+ };
677
+ const inFlightOperations = /* @__PURE__ */ new Set();
678
+ const common = {
679
+ providerKey,
680
+ pendingRequests,
681
+ sessionAdmission,
682
+ inFlightOperations,
683
+ requireUsable,
684
+ isTerminal: () => state.terminal,
685
+ canRestoreRequest: (pending) => hasLiveApprovalSubject(pending.request, state),
686
+ observe(candidate, requests) {
687
+ const output = validateAgentProviderOutputForContext(candidate, outputContext(turnId));
688
+ observeTurnOutput({
689
+ capabilities,
690
+ providerKey,
691
+ output,
692
+ state,
693
+ pendingRequests,
694
+ requests,
695
+ contextUsageState,
696
+ interactionState
697
+ });
698
+ return output;
699
+ },
700
+ assertCompleted() {
701
+ if (!state.started || !state.terminal || pendingRequests.size > 0) {
702
+ invalidTurnSequence(providerKey, "Provider turn output must remain open until actual terminal completion.");
703
+ }
704
+ },
705
+ release() {
706
+ if (source.kind === "caller") sessionAdmission.releaseExecution(token);
707
+ },
708
+ retire() {
709
+ unusable = true;
710
+ },
711
+ abandon: () => closing ? closing() : Promise.reject(new Error("Session cleanup is waiting for its opening owner."))
712
+ };
713
+ const owner = source.kind === "caller" ? createValidatedAgentTurnExecution({ ...common, turnInput: source.input, start: source.start }) : adoptValidatedAgentTurnExecution({ ...common, turnId, createCandidate: source.createCandidate });
714
+ try {
715
+ sessionAdmission.installExecution(token, { turnId, pendingRequests, state, inFlightOperations, owner });
716
+ } catch (cause) {
717
+ owner.stop(cause instanceof Error ? cause : new Error("Execution installation failed.", { cause }));
718
+ throw cause;
719
+ }
720
+ return owner;
721
+ }
722
+ return {
723
+ sessionAdmission,
724
+ sessionLifetime,
725
+ contextUsageState,
726
+ interactionState,
727
+ outputContext,
728
+ requireUsable,
729
+ get activeTurn() {
730
+ return sessionAdmission.execution;
731
+ },
732
+ get closed() {
733
+ return closed;
734
+ },
735
+ get unusable() {
736
+ return unusable;
737
+ },
738
+ fence() {
739
+ unusable = true;
740
+ },
741
+ stop() {
742
+ unusable = true;
743
+ sessionAdmission.stop();
744
+ sessionLifetime.abort(new DOMException("The provider session was closed.", "AbortError"));
745
+ },
746
+ completeClose() {
747
+ closed = true;
748
+ },
749
+ installClose(close) {
750
+ closing = close;
751
+ },
752
+ reserveExecution: sessionAdmission.reserveExecution,
753
+ ownsExecution: sessionAdmission.ownsExecution,
754
+ releaseExecution: sessionAdmission.releaseExecution,
755
+ installNativeExecution(token, turnId, createCandidate) {
756
+ return installExecution(token, turnId, { kind: "native", createCandidate });
757
+ },
758
+ installCallerExecution(token, turnInput, start) {
759
+ return installExecution(token, turnInput.turnId, { kind: "caller", input: turnInput, start });
760
+ }
761
+ };
762
+ }
640
763
  function validateAgentProviderSession(input) {
641
764
  const { capabilities, sessionId } = input;
642
765
  const providerKey = capabilities.providerKey;
@@ -672,58 +795,22 @@ function validateAgentProviderSession(input) {
672
795
  );
673
796
  }
674
797
  validateSessionPorts(capabilities, input.candidate);
675
- const sessionLifetime = new AbortController();
798
+ const validationOwner = input.validationOwner ?? createAgentSessionValidationOwner(capabilities, sessionId);
799
+ const { sessionLifetime, contextUsageState, interactionState, outputContext } = validationOwner;
800
+ const collaborationNodes = interactionState.collaborationNodes;
801
+ const collaborationSpawnReservations = interactionState.collaborationSpawnReservations;
802
+ const requireUsable = () => {
803
+ input.sessionInteractionOwner?.requireUsable();
804
+ validationOwner.requireUsable();
805
+ };
676
806
  const environment = validateAgentEnvironmentObservationPort({
677
807
  providerKey,
678
808
  capability: capabilities.environment.session,
679
809
  port: input.candidate.environment,
680
810
  signal: sessionLifetime.signal
681
811
  });
682
- const openedRequestIds = /* @__PURE__ */ new Set();
683
- const contextUsageState = {
684
- latestByScope: /* @__PURE__ */ new Map(),
685
- cumulativeByScope: /* @__PURE__ */ new Map(),
686
- compactionReadyScopes: /* @__PURE__ */ new Set()
687
- };
688
- const interactionState = {
689
- operationResults: /* @__PURE__ */ new Map(),
690
- operationInvocations: /* @__PURE__ */ new Map(),
691
- collaborationNodes: /* @__PURE__ */ new Map(),
692
- collaborationSpawnReservations: /* @__PURE__ */ new Map(),
693
- generatedResources: /* @__PURE__ */ new Map()
694
- };
695
- const collaborationNodes = interactionState.collaborationNodes;
696
- const collaborationSpawnReservations = interactionState.collaborationSpawnReservations;
697
- let activeTurn = null;
698
812
  let closePromise = null;
699
- let closed = false;
700
813
  let providerClosed = false;
701
- let unusable = false;
702
- const requireOpen = () => {
703
- if (closed)
704
- throwAgentProviderContractError(
705
- providerKey,
706
- "session_closed",
707
- "Provider session is closed."
708
- );
709
- };
710
- const requireUsable = () => {
711
- input.sessionInteractionOwner?.requireUsable();
712
- requireOpen();
713
- if (unusable) {
714
- throwAgentProviderContractError(
715
- providerKey,
716
- "session_unusable",
717
- "Provider session cannot be reused after an incomplete operation."
718
- );
719
- }
720
- };
721
- const outputContext = (turnId) => ({
722
- capabilities,
723
- providerKey,
724
- sessionId,
725
- ...turnId === void 0 ? {} : { turnId }
726
- });
727
814
  const delegateProviderMutation = async (input2) => {
728
815
  let executionStarted = false;
729
816
  const onProviderExecutionStarted = () => {
@@ -760,7 +847,7 @@ function validateAgentProviderSession(input) {
760
847
  return input2.validate(candidate);
761
848
  } catch (error) {
762
849
  if (!executionStarted) throw error;
763
- unusable = true;
850
+ validationOwner.fence();
764
851
  throw new AgentProviderDelegatedOperationError(
765
852
  providerKey,
766
853
  input2.operation,
@@ -786,63 +873,17 @@ function validateAgentProviderSession(input) {
786
873
  `Provider ${providerKey} does not support ${parsedInput.interactionMode} turn interaction mode.`
787
874
  );
788
875
  }
789
- if (activeTurn !== null) {
790
- throwAgentProviderContractError(providerKey, "concurrent_turn", "Provider session already has an active turn.");
791
- }
792
- const pendingRequests = /* @__PURE__ */ new Map();
793
- const turnRequestIds = /* @__PURE__ */ new Set();
794
- const state = {
795
- fileChangeMode: capabilities.output.fileChanges,
796
- observedItems: /* @__PURE__ */ new Map(),
797
- proposedPlans: /* @__PURE__ */ new Map(),
798
- generatedResourceIds: /* @__PURE__ */ new Set(),
799
- started: false,
800
- terminal: false,
801
- finalDiffObserved: false
802
- };
803
- const inFlightOperations = /* @__PURE__ */ new Set();
804
- const owner = createValidatedAgentTurnExecution({
805
- providerKey,
806
- turnInput: {
876
+ const token = validationOwner.reserveExecution();
877
+ try {
878
+ return validationOwner.installCallerExecution(token, {
807
879
  ...parsedInput,
808
880
  ...turnInput.signal === void 0 ? {} : { signal: turnInput.signal },
809
881
  ...turnInput.onProviderExecutionStarted === void 0 ? {} : { onProviderExecutionStarted: turnInput.onProviderExecutionStarted }
810
- },
811
- pendingRequests,
812
- inFlightOperations,
813
- start: (inputTurn) => input.candidate.startTurn(inputTurn),
814
- requireUsable,
815
- isTerminal: () => state.terminal,
816
- observe(candidate) {
817
- const output = validateAgentProviderOutputForContext(candidate, outputContext(parsedInput.turnId));
818
- observeTurnOutput({
819
- capabilities,
820
- providerKey,
821
- output,
822
- state,
823
- pendingRequests,
824
- openedRequestIds,
825
- turnRequestIds,
826
- contextUsageState,
827
- interactionState
828
- });
829
- return output;
830
- },
831
- assertCompleted() {
832
- if (!state.started || !state.terminal || pendingRequests.size > 0) {
833
- invalidTurnSequence(providerKey, "Provider turn output must remain open until actual terminal completion.");
834
- }
835
- },
836
- release() {
837
- if (activeTurn?.owner === owner) activeTurn = null;
838
- },
839
- retire() {
840
- unusable = true;
841
- },
842
- abandon: () => close({ reason: "other" })
843
- });
844
- activeTurn = { turnId: parsedInput.turnId, pendingRequests, state, inFlightOperations, owner };
845
- return owner.execution;
882
+ }, (inputTurn) => input.candidate.startTurn(inputTurn)).execution;
883
+ } catch (error) {
884
+ validationOwner.releaseExecution(token);
885
+ throw error;
886
+ }
846
887
  };
847
888
  const declaredInterruption = input.candidate.interruption;
848
889
  const interruption = declaredInterruption.kind === "unsupported" ? Object.freeze({ kind: "unsupported" }) : Object.freeze({
@@ -855,7 +896,7 @@ function validateAgentProviderSession(input) {
855
896
  reason: interruptionInput.reason,
856
897
  ...interruptionInput.requestedAt === void 0 ? {} : { requestedAt: interruptionInput.requestedAt }
857
898
  });
858
- const targetedActiveTurn = activeTurn?.turnId === parsed.turnId ? activeTurn : null;
899
+ const targetedActiveTurn = validationOwner.activeTurn?.turnId === parsed.turnId ? validationOwner.activeTurn : null;
859
900
  const targetedTurn = targetedActiveTurn;
860
901
  if (targetedTurn === null || targetedTurn.state.terminal || !targetedTurn.owner.hasEntered()) {
861
902
  throwAgentProviderContractError(
@@ -880,7 +921,7 @@ function validateAgentProviderSession(input) {
880
921
  );
881
922
  } catch (error) {
882
923
  suspendedRequests.withdraw();
883
- unusable = true;
924
+ validationOwner.fence();
884
925
  throw error;
885
926
  }
886
927
  if (result.status === "failed") {
@@ -899,7 +940,7 @@ function validateAgentProviderSession(input) {
899
940
  requireUsable();
900
941
  throwIfAgentOperationAborted(steeringInput.signal);
901
942
  const turnId = parseAgentTurnId(steeringInput.turnId);
902
- if (activeTurn?.turnId !== turnId || !activeTurn.owner.hasEntered() || activeTurn.state.terminal || activeTurn.pendingRequests.size > 0) {
943
+ if (validationOwner.activeTurn?.turnId !== turnId || !validationOwner.activeTurn.owner.hasEntered() || validationOwner.activeTurn.state.terminal || validationOwner.activeTurn.pendingRequests.size > 0) {
903
944
  throwAgentProviderContractError(
904
945
  providerKey,
905
946
  "active_turn_mismatch",
@@ -915,7 +956,7 @@ function validateAgentProviderSession(input) {
915
956
  parts: content.parts,
916
957
  providerKey
917
958
  });
918
- const targetedActiveTurn = activeTurn;
959
+ const targetedActiveTurn = validationOwner.activeTurn;
919
960
  const settleSteering = trackActiveTurnOperation(targetedActiveTurn);
920
961
  try {
921
962
  try {
@@ -927,10 +968,10 @@ function validateAgentProviderSession(input) {
927
968
  }),
928
969
  providerKey
929
970
  );
930
- if (result.status === "delivery_uncertain") unusable = true;
971
+ if (result.status === "delivery_uncertain") validationOwner.fence();
931
972
  return result;
932
973
  } catch (error) {
933
- unusable = true;
974
+ validationOwner.fence();
934
975
  throw new AgentProviderDelegatedOperationError(
935
976
  providerKey,
936
977
  "steer_turn",
@@ -1008,142 +1049,154 @@ function validateAgentProviderSession(input) {
1008
1049
  invokeOperation: async (operationInput) => {
1009
1050
  requireUsable();
1010
1051
  throwIfAgentOperationAborted(operationInput.signal);
1011
- let invocation;
1012
- let observationTurnId;
1052
+ const settle = validationOwner.sessionAdmission.trackOperation();
1053
+ const signal = operationInput.signal === void 0 ? sessionLifetime.signal : AbortSignal.any([operationInput.signal, sessionLifetime.signal]);
1054
+ operationInput = { ...operationInput, signal };
1013
1055
  try {
1014
- invocation = parseAgentOperationInvocation(operationInput.invocation);
1015
- observationTurnId = parseAgentTurnId(
1016
- operationInput.observationTurnId
1017
- );
1018
- } catch {
1019
- return throwAgentProviderContractError(
1020
- providerKey,
1021
- "input_operation_mismatch",
1022
- "Operation invocation is invalid."
1023
- );
1024
- }
1025
- const catalog = validateAgentOperationCatalogForCapabilities(
1026
- capabilities,
1027
- await declaredOperations.listOperations(
1028
- operationInput.signal === void 0 ? {} : { signal: operationInput.signal }
1029
- )
1030
- );
1031
- const descriptor = catalog.operations.find(
1032
- (operation) => operation.operationId === invocation.operationId
1033
- );
1034
- if (descriptor === void 0) {
1035
- return throwAgentProviderContractError(
1036
- providerKey,
1037
- "input_operation_mismatch",
1038
- "Operation invocation does not identify an offered operation."
1056
+ let invocation;
1057
+ let observationTurnId;
1058
+ try {
1059
+ invocation = parseAgentOperationInvocation(operationInput.invocation);
1060
+ observationTurnId = parseAgentTurnId(
1061
+ operationInput.observationTurnId
1062
+ );
1063
+ } catch {
1064
+ return throwAgentProviderContractError(
1065
+ providerKey,
1066
+ "input_operation_mismatch",
1067
+ "Operation invocation is invalid."
1068
+ );
1069
+ }
1070
+ const catalog = validateAgentOperationCatalogForCapabilities(
1071
+ capabilities,
1072
+ await declaredOperations.listOperations(
1073
+ operationInput.signal === void 0 ? {} : { signal: operationInput.signal }
1074
+ )
1039
1075
  );
1040
- }
1041
- try {
1042
- invocation = parseAgentOperationInvocationFor(
1043
- descriptor,
1044
- invocation
1076
+ const descriptor = catalog.operations.find(
1077
+ (operation) => operation.operationId === invocation.operationId
1045
1078
  );
1046
- } catch {
1047
- return throwAgentProviderContractError(
1048
- providerKey,
1049
- "input_operation_mismatch",
1050
- "Operation invocation is stale or outside the offered descriptor."
1051
- );
1052
- }
1053
- const existing = interactionState.operationInvocations.get(
1054
- invocation.invocationId
1055
- );
1056
- if (existing !== void 0) {
1057
- if (JSON.stringify(existing.invocation) !== JSON.stringify(invocation)) {
1079
+ if (descriptor === void 0) {
1058
1080
  return throwAgentProviderContractError(
1059
1081
  providerKey,
1060
1082
  "input_operation_mismatch",
1061
- "Operation invocation ID was reused with conflicting input."
1083
+ "Operation invocation does not identify an offered operation."
1062
1084
  );
1063
1085
  }
1064
- if (descriptor.idempotency === "required" && existing.result !== void 0) {
1065
- return existing.result;
1086
+ try {
1087
+ invocation = parseAgentOperationInvocationFor(
1088
+ descriptor,
1089
+ invocation
1090
+ );
1091
+ } catch {
1092
+ return throwAgentProviderContractError(
1093
+ providerKey,
1094
+ "input_operation_mismatch",
1095
+ "Operation invocation is stale or outside the offered descriptor."
1096
+ );
1066
1097
  }
1067
- return throwAgentProviderContractError(
1068
- providerKey,
1069
- "input_operation_mismatch",
1070
- "Operation invocation ID is already active or is not replayable."
1098
+ const existing = interactionState.operationInvocations.get(
1099
+ invocation.invocationId
1071
1100
  );
1072
- }
1073
- const trackedInvocation = {
1074
- invocation
1075
- };
1076
- const observationState = {
1077
- fileChangeMode: capabilities.output.fileChanges,
1078
- observedItems: /* @__PURE__ */ new Map(),
1079
- proposedPlans: /* @__PURE__ */ new Map(),
1080
- generatedResourceIds: /* @__PURE__ */ new Set(),
1081
- started: true,
1082
- terminal: false,
1083
- finalDiffObserved: false
1084
- };
1085
- interactionState.operationInvocations.set(
1086
- invocation.invocationId,
1087
- trackedInvocation
1088
- );
1089
- try {
1090
- const result = await delegateProviderMutation({
1091
- operation: "invoke_operation",
1092
- observer: operationInput.onProviderExecutionStarted,
1093
- invoke: (onProviderExecutionStarted, onOutput) => declaredOperations.invokeOperation({
1094
- invocation,
1095
- observationTurnId,
1096
- onProviderExecutionStarted,
1097
- onOutput,
1098
- ...operationInput.signal === void 0 ? {} : { signal: operationInput.signal }
1099
- }),
1100
- observeOutput: async (candidate) => {
1101
- const output = validateAgentProviderOutputForContext(
1102
- candidate,
1103
- {
1104
- ...outputContext(observationTurnId),
1105
- operationInvocationId: invocation.invocationId
1106
- }
1107
- );
1108
- observeSessionOperationOutput({
1109
- capabilities,
1110
- providerKey,
1111
- output,
1112
- state: observationState,
1113
- contextUsageState,
1114
- interactionState
1115
- });
1116
- await operationInput.onOutput?.(output);
1117
- },
1118
- validate: (candidate) => {
1119
- const result2 = validateAgentOperationResultTransition({
1101
+ if (existing !== void 0) {
1102
+ if (JSON.stringify(existing.invocation) !== JSON.stringify(invocation)) {
1103
+ return throwAgentProviderContractError(
1120
1104
  providerKey,
1121
- candidate: validateAgentOperationResultForInvocation(
1122
- providerKey,
1123
- descriptor,
1124
- invocation,
1125
- candidate
1126
- ),
1127
- previous: interactionState.operationResults.get(
1128
- invocation.invocationId
1129
- )
1130
- });
1131
- interactionState.operationResults.set(
1132
- result2.invocationId,
1133
- result2
1105
+ "input_operation_mismatch",
1106
+ "Operation invocation ID was reused with conflicting input."
1134
1107
  );
1135
- return result2;
1136
1108
  }
1137
- });
1138
- trackedInvocation.result = result;
1139
- return result;
1140
- } catch (error) {
1141
- if (!(error instanceof AgentProviderDelegatedOperationError)) {
1142
- interactionState.operationInvocations.delete(
1143
- invocation.invocationId
1109
+ if (descriptor.idempotency === "required" && existing.result !== void 0) {
1110
+ return existing.result;
1111
+ }
1112
+ return throwAgentProviderContractError(
1113
+ providerKey,
1114
+ "input_operation_mismatch",
1115
+ "Operation invocation ID is already active or is not replayable."
1144
1116
  );
1145
1117
  }
1146
- throw error;
1118
+ requireUsable();
1119
+ signal.throwIfAborted();
1120
+ const reservation = descriptor.timing === "idle_session" && descriptor.executionMode === "immediate" ? validationOwner.reserveExecution() : void 0;
1121
+ const trackedInvocation = {
1122
+ invocation
1123
+ };
1124
+ const observationState = {
1125
+ fileChangeMode: capabilities.output.fileChanges,
1126
+ observedItems: /* @__PURE__ */ new Map(),
1127
+ proposedPlans: /* @__PURE__ */ new Map(),
1128
+ generatedResourceIds: /* @__PURE__ */ new Set(),
1129
+ started: true,
1130
+ terminal: false,
1131
+ finalDiffObserved: false
1132
+ };
1133
+ interactionState.operationInvocations.set(
1134
+ invocation.invocationId,
1135
+ trackedInvocation
1136
+ );
1137
+ try {
1138
+ const result = await delegateProviderMutation({
1139
+ operation: "invoke_operation",
1140
+ observer: operationInput.onProviderExecutionStarted,
1141
+ invoke: (onProviderExecutionStarted, onOutput) => declaredOperations.invokeOperation({
1142
+ invocation,
1143
+ observationTurnId,
1144
+ onProviderExecutionStarted,
1145
+ onOutput,
1146
+ ...operationInput.signal === void 0 ? {} : { signal: operationInput.signal }
1147
+ }),
1148
+ observeOutput: async (candidate) => {
1149
+ const output = validateAgentProviderOutputForContext(
1150
+ candidate,
1151
+ {
1152
+ ...outputContext(observationTurnId),
1153
+ operationInvocationId: invocation.invocationId
1154
+ }
1155
+ );
1156
+ observeSessionOperationOutput({
1157
+ capabilities,
1158
+ providerKey,
1159
+ output,
1160
+ state: observationState,
1161
+ contextUsageState,
1162
+ interactionState
1163
+ });
1164
+ await operationInput.onOutput?.(output);
1165
+ },
1166
+ validate: (candidate) => {
1167
+ const result2 = validateAgentOperationResultTransition({
1168
+ providerKey,
1169
+ candidate: validateAgentOperationResultForInvocation(
1170
+ providerKey,
1171
+ descriptor,
1172
+ invocation,
1173
+ candidate
1174
+ ),
1175
+ previous: interactionState.operationResults.get(
1176
+ invocation.invocationId
1177
+ )
1178
+ });
1179
+ interactionState.operationResults.set(
1180
+ result2.invocationId,
1181
+ result2
1182
+ );
1183
+ return result2;
1184
+ }
1185
+ });
1186
+ trackedInvocation.result = result;
1187
+ return result;
1188
+ } catch (error) {
1189
+ if (!(error instanceof AgentProviderDelegatedOperationError)) {
1190
+ interactionState.operationInvocations.delete(
1191
+ invocation.invocationId
1192
+ );
1193
+ }
1194
+ throw error;
1195
+ } finally {
1196
+ if (reservation !== void 0) validationOwner.releaseExecution(reservation);
1197
+ }
1198
+ } finally {
1199
+ settle();
1147
1200
  }
1148
1201
  }
1149
1202
  });
@@ -1394,14 +1447,14 @@ function validateAgentProviderSession(input) {
1394
1447
  }
1395
1448
  });
1396
1449
  const closeOwned = (closeInput, explicit) => {
1397
- if (closed && !input.sessionInteractionOwner) return Promise.resolve();
1450
+ if (validationOwner.closed && !input.sessionInteractionOwner) return Promise.resolve();
1398
1451
  if (!input.sessionInteractionOwner && closePromise) return closePromise;
1399
1452
  throwIfAgentOperationAborted(closeInput.signal);
1400
1453
  validateCloseReason(closeInput.reason);
1401
- unusable = true;
1402
- sessionLifetime.abort(new DOMException("The provider session was closed.", "AbortError"));
1454
+ validationOwner.fence();
1455
+ validationOwner.stop();
1403
1456
  input.hostBindingLease?.stop();
1404
- const closingTurn = activeTurn;
1457
+ const closingTurn = validationOwner.activeTurn;
1405
1458
  closingTurn?.owner.stop(new DOMException("The provider session was closed.", "AbortError"));
1406
1459
  const cleanup = async () => {
1407
1460
  if (!providerClosed) {
@@ -1410,8 +1463,9 @@ function validateAgentProviderSession(input) {
1410
1463
  }
1411
1464
  await input.sessionInteractionOwner?.join();
1412
1465
  await closingTurn?.owner.join();
1466
+ await validationOwner.sessionAdmission.joinOperations();
1413
1467
  await input.hostBindingLease?.release();
1414
- closed = true;
1468
+ validationOwner.completeClose();
1415
1469
  };
1416
1470
  if (input.sessionInteractionOwner) return input.sessionInteractionOwner.close(cleanup, explicit);
1417
1471
  closePromise = Promise.resolve().then(cleanup).finally(() => {
@@ -1420,6 +1474,7 @@ function validateAgentProviderSession(input) {
1420
1474
  return closePromise;
1421
1475
  };
1422
1476
  const close = (closeInput) => closeOwned(closeInput, true);
1477
+ validationOwner.installClose(() => closeOwned({ reason: "error" }, false));
1423
1478
  input.sessionInteractionOwner?.installClose(() => closeOwned({ reason: "error" }, false));
1424
1479
  return Object.freeze({
1425
1480
  binding,
@@ -1504,8 +1559,14 @@ function bindingTracker(input) {
1504
1559
  "Provider branch reused its source binding."
1505
1560
  );
1506
1561
  }
1507
- input.observer(binding);
1508
1562
  observed = binding;
1563
+ try {
1564
+ input.interactions?.bind();
1565
+ input.observer(binding);
1566
+ } catch (cause) {
1567
+ input.interactions?.fail(cause);
1568
+ throw cause;
1569
+ }
1509
1570
  },
1510
1571
  requireMatch(binding) {
1511
1572
  if (!observed) {
@@ -1529,15 +1590,18 @@ async function openIdentityCreatingAgentProviderSession(input) {
1529
1590
  const tracker = bindingTracker({
1530
1591
  providerKey: input.capabilities.providerKey,
1531
1592
  observer: input.observer,
1593
+ ...input.sessionInteractionOwner === void 0 ? {} : { interactions: input.sessionInteractionOwner },
1532
1594
  ...input.sourceBinding === void 0 ? {} : { sourceBinding: input.sourceBinding }
1533
1595
  });
1534
1596
  let candidate = null;
1535
1597
  try {
1536
1598
  candidate = await input.open(tracker.observe);
1599
+ tracker.requireMatch(parseAgentSessionBinding(candidate.binding));
1537
1600
  const session = validateAgentProviderSession({
1538
1601
  capabilities: input.capabilities,
1539
1602
  sessionId: input.sessionId,
1540
1603
  candidate,
1604
+ ...input.validationOwner === void 0 ? {} : { validationOwner: input.validationOwner },
1541
1605
  ...input.sessionInteractionOwner === void 0 ? {} : { sessionInteractionOwner: input.sessionInteractionOwner },
1542
1606
  ...input.hostBindingLease === void 0 ? {} : { hostBindingLease: input.hostBindingLease },
1543
1607
  ...input.sourceBinding === void 0 ? {} : { sourceBinding: input.sourceBinding }
@@ -1560,6 +1624,7 @@ async function openIdentityCreatingAgentProviderSession(input) {
1560
1624
  }
1561
1625
  export {
1562
1626
  closeRejectedAgentProviderSession,
1627
+ createAgentSessionValidationOwner,
1563
1628
  openIdentityCreatingAgentProviderSession,
1564
1629
  validateAgentProviderSession
1565
1630
  };