@agen-ai/agent-runtime 0.3.1 → 0.4.1

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 (50) hide show
  1. package/README.md +288 -29
  2. package/dist/adapterValidation.d.ts +1 -1
  3. package/dist/adapterValidation.js +199 -92
  4. package/dist/artifacts.js +0 -1
  5. package/dist/contractErrors.d.ts +16 -3
  6. package/dist/contractErrors.js +42 -6
  7. package/dist/hostBindings/index.d.ts +4 -0
  8. package/dist/hostBindings/index.js +9 -0
  9. package/dist/hostBindings/lifetime.d.ts +25 -0
  10. package/dist/hostBindings/lifetime.js +147 -0
  11. package/dist/hostBindings/opening.d.ts +14 -0
  12. package/dist/hostBindings/opening.js +82 -0
  13. package/dist/hostBindings/schema.d.ts +9 -0
  14. package/dist/hostBindings/schema.js +169 -0
  15. package/dist/hostBindings/types.d.ts +71 -0
  16. package/dist/hostBindings/types.js +0 -0
  17. package/dist/hostBindings/validation.d.ts +14 -0
  18. package/dist/hostBindings/validation.js +107 -0
  19. package/dist/index.d.ts +3 -0
  20. package/dist/index.js +7 -0
  21. package/dist/internal/objectShape.d.ts +2 -0
  22. package/dist/internal/objectShape.js +7 -0
  23. package/dist/outputValidation.js +3 -2
  24. package/dist/providerDriver.js +0 -1
  25. package/dist/providerInstanceRegistry.js +2 -1
  26. package/dist/sessionCleanup.d.ts +9 -0
  27. package/dist/sessionCleanup.js +17 -0
  28. package/dist/sessionInteractions/index.d.ts +2 -0
  29. package/dist/sessionInteractions/index.js +1 -0
  30. package/dist/sessionInteractions/lifetime.d.ts +52 -0
  31. package/dist/sessionInteractions/lifetime.js +251 -0
  32. package/dist/sessionInteractions/request.d.ts +42 -0
  33. package/dist/sessionInteractions/request.js +308 -0
  34. package/dist/sessionInteractions/types.d.ts +54 -0
  35. package/dist/sessionInteractions/types.js +10 -0
  36. package/dist/sessionInteractions/validation.d.ts +21 -0
  37. package/dist/sessionInteractions/validation.js +119 -0
  38. package/dist/sessionValidation.d.ts +15 -2
  39. package/dist/sessionValidation.js +155 -318
  40. package/dist/sessions.d.ts +31 -4
  41. package/dist/testing/conformance.d.ts +3 -3
  42. package/dist/testing/conformance.js +24 -31
  43. package/dist/testing/fakeProvider.js +59 -49
  44. package/dist/turnExecution/execution.d.ts +26 -0
  45. package/dist/turnExecution/execution.js +214 -0
  46. package/dist/turnExecution/receipts.d.ts +6 -0
  47. package/dist/turnExecution/receipts.js +39 -0
  48. package/dist/turnExecution/requests.d.ts +19 -0
  49. package/dist/turnExecution/requests.js +56 -0
  50. package/package.json +2 -2
@@ -9,8 +9,6 @@ import {
9
9
  parseAgentGeneratedResourceId,
10
10
  parseAgentOperationInvocation,
11
11
  parseAgentOperationInvocationFor,
12
- parseAgentRequestResolution,
13
- parseAgentRequestResolutionFor,
14
12
  parseAgentSessionBinding,
15
13
  parseAgentTurnId,
16
14
  parseAgentTurnInputContent,
@@ -32,8 +30,12 @@ import {
32
30
  } from "./interactionValidation.js";
33
31
  import {
34
32
  AgentProviderDelegatedOperationError,
33
+ AgentProviderSessionCleanupError,
35
34
  throwAgentProviderContractError
36
35
  } from "./contractErrors.js";
36
+ import { createAgentHostBindingCleanupError } from "./hostBindings/lifetime.js";
37
+ import { associateAgentSessionCleanupError } from "./sessionCleanup.js";
38
+ import { hasExactOwnKeys } from "./internal/objectShape.js";
37
39
  import {
38
40
  throwIfAgentOperationAborted
39
41
  } from "./foundation.js";
@@ -42,18 +44,17 @@ import {
42
44
  validateAgentProviderOutputForContext
43
45
  } from "./outputValidation.js";
44
46
  import { validateAgentTurnSteeringResult } from "./steeringValidation.js";
45
- function hasExactOwnKeys(candidate, expectedKeys) {
46
- const ownKeys = Reflect.ownKeys(candidate);
47
- if (ownKeys.some((key) => typeof key !== "string")) return false;
48
- const actualKeys = ownKeys.sort();
49
- const canonicalExpectedKeys = [...expectedKeys].sort();
50
- return actualKeys.length === canonicalExpectedKeys.length && actualKeys.every((key, index) => key === canonicalExpectedKeys[index]);
47
+ import { createValidatedAgentTurnExecution } from "./turnExecution/execution.js";
48
+ import { validateAgentProviderTurnInterruptionResult } from "./turnExecution/receipts.js";
49
+ function validateCloseReason(reason) {
50
+ if (reason !== void 0 && !["idle", "shutdown", "replaced", "contract_rejected", "error", "other"].includes(reason)) {
51
+ throw new TypeError("Provider session close reason is unsupported.");
52
+ }
51
53
  }
52
54
  function validateSessionPorts(capabilities, session) {
53
55
  if (!hasExactOwnKeys(session, [
54
56
  "binding",
55
- "runTurn",
56
- "resolveRequest",
57
+ "startTurn",
57
58
  "interruption",
58
59
  "steering",
59
60
  "configuration",
@@ -63,7 +64,7 @@ function validateSessionPorts(capabilities, session) {
63
64
  "collaboration",
64
65
  "generatedResources",
65
66
  "close"
66
- ]) || typeof session.runTurn !== "function" || typeof session.resolveRequest !== "function" || typeof session.close !== "function" || session.interruption === null || typeof session.interruption !== "object" || !["supported", "unsupported"].includes(session.interruption.kind) || session.steering === null || typeof session.steering !== "object" || !["supported", "unsupported"].includes(session.steering.kind) || session.configuration === null || typeof session.configuration !== "object" || !["managed", "selectable"].includes(session.configuration.kind) || session.operations === null || typeof session.operations !== "object" || !["supported", "unsupported"].includes(session.operations.kind) || session.managedContent === null || typeof session.managedContent !== "object" || !["supported", "unsupported"].includes(session.managedContent.kind) || session.environment === null || typeof session.environment !== "object" || !["read", "read_and_watch", "unsupported"].includes(session.environment.kind) || session.collaboration === null || typeof session.collaboration !== "object" || !["supported", "unsupported"].includes(session.collaboration.kind) || session.generatedResources === null || typeof session.generatedResources !== "object" || !["supported", "unsupported"].includes(session.generatedResources.kind)) {
67
+ ]) || typeof session.startTurn !== "function" || typeof session.close !== "function" || session.interruption === null || typeof session.interruption !== "object" || !["supported", "unsupported"].includes(session.interruption.kind) || session.steering === null || typeof session.steering !== "object" || !["supported", "unsupported"].includes(session.steering.kind) || session.configuration === null || typeof session.configuration !== "object" || !["managed", "selectable"].includes(session.configuration.kind) || session.operations === null || typeof session.operations !== "object" || !["supported", "unsupported"].includes(session.operations.kind) || session.managedContent === null || typeof session.managedContent !== "object" || !["supported", "unsupported"].includes(session.managedContent.kind) || session.environment === null || typeof session.environment !== "object" || !["read", "read_and_watch", "unsupported"].includes(session.environment.kind) || session.collaboration === null || typeof session.collaboration !== "object" || !["supported", "unsupported"].includes(session.collaboration.kind) || session.generatedResources === null || typeof session.generatedResources !== "object" || !["supported", "unsupported"].includes(session.generatedResources.kind)) {
67
68
  throwAgentProviderContractError(
68
69
  capabilities.providerKey,
69
70
  "invalid_session",
@@ -169,7 +170,6 @@ function completeTurnSequence(input) {
169
170
  );
170
171
  }
171
172
  input.state.terminal = true;
172
- input.state.waiting = false;
173
173
  }
174
174
  const FINAL_DIFF_TRAILING_EVENT_TYPES = [
175
175
  "artifact.referenced",
@@ -305,6 +305,7 @@ function observeTurnEvent(input) {
305
305
  contextUsageState,
306
306
  event,
307
307
  openedRequestIds,
308
+ turnRequestIds,
308
309
  pendingRequests,
309
310
  providerKey,
310
311
  state
@@ -399,14 +400,15 @@ function observeTurnEvent(input) {
399
400
  state
400
401
  });
401
402
  openedRequestIds.add(requestId);
403
+ turnRequestIds.add(requestId);
402
404
  pendingRequests.set(requestId, {
403
405
  request: event.payload.request,
404
406
  turnId: event.turnId
405
407
  });
406
408
  }
407
409
  if (event.type === "turn.state_changed") {
408
- state.waiting = event.payload.state === "waiting_for_request";
409
- if (state.waiting && !pendingRequests.has(event.payload.requestId)) {
410
+ const waiting = event.payload.state === "waiting_for_request";
411
+ if (waiting && !turnRequestIds.has(event.payload.requestId)) {
410
412
  invalidTurnSequence(
411
413
  providerKey,
412
414
  "Provider entered a waiting state for a request it did not open."
@@ -414,6 +416,7 @@ function observeTurnEvent(input) {
414
416
  }
415
417
  }
416
418
  if (event.type === "turn.completed") {
419
+ if (event.payload.outcome !== "completed") pendingRequests.clear();
417
420
  completeTurnSequence({ providerKey, state, pendingRequests });
418
421
  }
419
422
  }
@@ -506,6 +509,7 @@ function observeTurnOutput(input) {
506
509
  state: input.state,
507
510
  pendingRequests: input.pendingRequests,
508
511
  openedRequestIds: input.openedRequestIds,
512
+ turnRequestIds: input.turnRequestIds,
509
513
  contextUsageState: input.contextUsageState
510
514
  });
511
515
  }
@@ -544,23 +548,11 @@ function observeSessionOperationOutput(input) {
544
548
  state: input.state,
545
549
  pendingRequests: /* @__PURE__ */ new Map(),
546
550
  openedRequestIds: /* @__PURE__ */ new Set(),
551
+ turnRequestIds: /* @__PURE__ */ new Set(),
547
552
  contextUsageState: input.contextUsageState,
548
553
  interactionState: input.interactionState
549
554
  });
550
555
  }
551
- function assertStableTurnBoundary(input) {
552
- const terminal = input.state.terminal && !input.state.waiting;
553
- const waiting = !input.state.terminal && input.state.waiting && input.pendingRequests.size === 1;
554
- if (terminal && input.pendingRequests.size === 0 || waiting) return;
555
- invalidTurnSequence(
556
- input.providerKey,
557
- "Provider turn must finish with completion or exactly one pending request."
558
- );
559
- }
560
- function replacePendingRequests(target, source) {
561
- target.clear();
562
- for (const [requestId, pending] of source) target.set(requestId, pending);
563
- }
564
556
  function trackActiveTurnOperation(turn) {
565
557
  if (!turn) return null;
566
558
  let settle;
@@ -573,45 +565,6 @@ function trackActiveTurnOperation(turn) {
573
565
  settle();
574
566
  };
575
567
  }
576
- function interruptionTerminalizesTurn(input) {
577
- if (["completed", "canceled"].includes(input.result.status)) {
578
- return true;
579
- }
580
- return (input.result.outputs ?? []).some(
581
- (output) => output.kind === "event" && output.event.turnId === input.turnId && output.event.type === "turn.completed"
582
- );
583
- }
584
- function observeTurnOperationOutputs(input) {
585
- for (const output of input.outputs ?? []) {
586
- observeTurnOutput({
587
- providerKey: input.providerKey,
588
- output,
589
- state: input.state,
590
- pendingRequests: input.pendingRequests,
591
- openedRequestIds: input.openedRequestIds,
592
- contextUsageState: input.contextUsageState,
593
- capabilities: input.capabilities,
594
- interactionState: input.interactionState
595
- });
596
- }
597
- }
598
- function reconstructWaitingTurn(input) {
599
- const targetedPendingRequests = new Map(
600
- [...input.pendingRequests].filter(
601
- ([, pending]) => pending.turnId === input.turnId
602
- )
603
- );
604
- if (targetedPendingRequests.size === 0 || input.state === void 0 || input.state.terminal || !input.state.waiting) return null;
605
- return {
606
- state: input.state,
607
- pendingRequests: targetedPendingRequests
608
- };
609
- }
610
- function clearPendingRequestsForTurn(pendingRequests, turnId) {
611
- for (const [requestId, pending] of pendingRequests) {
612
- if (pending.turnId === turnId) pendingRequests.delete(requestId);
613
- }
614
- }
615
568
  function assertAgentTurnInputCapability(input) {
616
569
  const imageParts = input.parts.filter((part) => part.type === "image");
617
570
  if (imageParts.length > 0) {
@@ -726,8 +679,6 @@ function validateAgentProviderSession(input) {
726
679
  port: input.candidate.environment,
727
680
  signal: sessionLifetime.signal
728
681
  });
729
- const pendingRequests = /* @__PURE__ */ new Map();
730
- const waitingTurnStates = /* @__PURE__ */ new Map();
731
682
  const openedRequestIds = /* @__PURE__ */ new Set();
732
683
  const contextUsageState = {
733
684
  latestByScope: /* @__PURE__ */ new Map(),
@@ -746,6 +697,7 @@ function validateAgentProviderSession(input) {
746
697
  let activeTurn = null;
747
698
  let closePromise = null;
748
699
  let closed = false;
700
+ let providerClosed = false;
749
701
  let unusable = false;
750
702
  const requireOpen = () => {
751
703
  if (closed)
@@ -756,6 +708,7 @@ function validateAgentProviderSession(input) {
756
708
  );
757
709
  };
758
710
  const requireUsable = () => {
711
+ input.sessionInteractionOwner?.requireUsable();
759
712
  requireOpen();
760
713
  if (unusable) {
761
714
  throwAgentProviderContractError(
@@ -771,17 +724,6 @@ function validateAgentProviderSession(input) {
771
724
  sessionId,
772
725
  ...turnId === void 0 ? {} : { turnId }
773
726
  });
774
- const delegateOperation = async (operation, turnId) => {
775
- try {
776
- return validateAgentProviderOperationResult(
777
- await operation(),
778
- outputContext(turnId)
779
- );
780
- } catch (error) {
781
- unusable = true;
782
- throw error;
783
- }
784
- };
785
727
  const delegateProviderMutation = async (input2) => {
786
728
  let executionStarted = false;
787
729
  const onProviderExecutionStarted = () => {
@@ -806,6 +748,7 @@ function validateAgentProviderSession(input) {
806
748
  await input2.observeOutput?.(candidate);
807
749
  };
808
750
  try {
751
+ requireUsable();
809
752
  const candidate = await input2.invoke(onProviderExecutionStarted, onOutput);
810
753
  if (!executionStarted) {
811
754
  throwAgentProviderContractError(
@@ -825,7 +768,7 @@ function validateAgentProviderSession(input) {
825
768
  );
826
769
  }
827
770
  };
828
- const runTurn = async function* (turnInput) {
771
+ const startTurn = (turnInput) => {
829
772
  requireUsable();
830
773
  throwIfAgentOperationAborted(turnInput.signal);
831
774
  const parsedInput = parseAgentTurnRunInput({
@@ -835,11 +778,7 @@ function validateAgentProviderSession(input) {
835
778
  ...turnInput.summary === void 0 ? {} : { summary: turnInput.summary },
836
779
  ...turnInput.deadlineAt === void 0 ? {} : { deadlineAt: turnInput.deadlineAt }
837
780
  });
838
- assertAgentTurnInputCapability({
839
- capability: capabilities.input,
840
- parts: parsedInput.parts,
841
- providerKey
842
- });
781
+ assertAgentTurnInputCapability({ capability: capabilities.input, parts: parsedInput.parts, providerKey });
843
782
  if (!capabilities.turns.interactionModes.includes(parsedInput.interactionMode)) {
844
783
  throwAgentProviderContractError(
845
784
  providerKey,
@@ -847,179 +786,63 @@ function validateAgentProviderSession(input) {
847
786
  `Provider ${providerKey} does not support ${parsedInput.interactionMode} turn interaction mode.`
848
787
  );
849
788
  }
850
- if (pendingRequests.size > 0) {
851
- throwAgentProviderContractError(
852
- providerKey,
853
- "request_pending",
854
- "Provider session cannot start another turn while a request is pending."
855
- );
856
- }
857
789
  if (activeTurn !== null) {
858
- throwAgentProviderContractError(
859
- providerKey,
860
- "concurrent_turn",
861
- "Provider session already has an active turn."
862
- );
790
+ throwAgentProviderContractError(providerKey, "concurrent_turn", "Provider session already has an active turn.");
863
791
  }
864
- let reachedStableBoundary = false;
865
- const currentTurn = {
866
- turnId: parsedInput.turnId,
867
- pendingRequests: /* @__PURE__ */ new Map(),
868
- state: {
869
- fileChangeMode: capabilities.output.fileChanges,
870
- observedItems: /* @__PURE__ */ new Map(),
871
- proposedPlans: /* @__PURE__ */ new Map(),
872
- generatedResourceIds: /* @__PURE__ */ new Set(),
873
- started: false,
874
- terminal: false,
875
- waiting: false,
876
- finalDiffObserved: false
877
- },
878
- inFlightOperations: /* @__PURE__ */ new Set()
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
879
802
  };
880
- activeTurn = currentTurn;
881
- try {
882
- turnInput.onProviderExecutionStarted?.();
883
- for await (const candidate of input.candidate.runTurn({
803
+ const inFlightOperations = /* @__PURE__ */ new Set();
804
+ const owner = createValidatedAgentTurnExecution({
805
+ providerKey,
806
+ turnInput: {
884
807
  ...parsedInput,
885
- ...turnInput.signal === void 0 ? {} : { signal: turnInput.signal }
886
- })) {
887
- const output = validateAgentProviderOutputForContext(
888
- candidate,
889
- outputContext(parsedInput.turnId)
890
- );
891
- observeTurnOutput({
892
- capabilities,
893
- providerKey,
894
- output,
895
- state: currentTurn.state,
896
- pendingRequests: currentTurn.pendingRequests,
897
- openedRequestIds,
898
- contextUsageState,
899
- interactionState
900
- });
901
- yield output;
902
- }
903
- while (currentTurn.inFlightOperations.size > 0) {
904
- await Promise.all(currentTurn.inFlightOperations);
905
- }
906
- if (!currentTurn.state.started) {
907
- invalidTurnSequence(
908
- providerKey,
909
- "Provider turn must emit turn.started before reaching a stable boundary."
910
- );
911
- }
912
- assertStableTurnBoundary({
913
- providerKey,
914
- state: currentTurn.state,
915
- pendingRequests: currentTurn.pendingRequests
916
- });
917
- if (currentTurn.state.waiting) {
918
- replacePendingRequests(pendingRequests, currentTurn.pendingRequests);
919
- waitingTurnStates.set(currentTurn.turnId, currentTurn.state);
920
- } else {
921
- waitingTurnStates.delete(currentTurn.turnId);
922
- }
923
- reachedStableBoundary = true;
924
- } finally {
925
- if (!reachedStableBoundary) unusable = true;
926
- if (activeTurn === currentTurn) activeTurn = null;
927
- }
928
- };
929
- const resolveRequest = async function* (requestInput) {
930
- requireUsable();
931
- throwIfAgentOperationAborted(requestInput.signal);
932
- const resolution = parseAgentRequestResolution(requestInput.resolution);
933
- const pending = pendingRequests.get(resolution.requestId);
934
- if (!pending) {
935
- throwAgentProviderContractError(
936
- providerKey,
937
- "request_resolution_mismatch",
938
- "Provider request resolution does not identify a pending request."
939
- );
940
- }
941
- try {
942
- parseAgentRequestResolutionFor(pending.request, resolution);
943
- } catch {
944
- throwAgentProviderContractError(
945
- providerKey,
946
- "request_resolution_mismatch",
947
- "Provider request resolution does not match the opened request."
948
- );
949
- }
950
- if (pending.request.expiresAt !== void 0 && Date.parse(pending.request.expiresAt) <= Date.now() && resolution.disposition !== "canceled") {
951
- throwAgentProviderContractError(
952
- providerKey,
953
- "request_resolution_mismatch",
954
- "Provider request resolution identifies an expired request."
955
- );
956
- }
957
- const nextPendingRequests = new Map(pendingRequests);
958
- nextPendingRequests.delete(resolution.requestId);
959
- if (activeTurn !== null) {
960
- throwAgentProviderContractError(
961
- providerKey,
962
- "concurrent_turn",
963
- "Provider session already has an active turn."
964
- );
965
- }
966
- const waitingTurnState = waitingTurnStates.get(pending.turnId);
967
- if (waitingTurnState === void 0 || waitingTurnState.terminal || !waitingTurnState.waiting) {
968
- throwAgentProviderContractError(
969
- providerKey,
970
- "request_resolution_mismatch",
971
- "Provider request resolution does not identify a waiting turn."
972
- );
973
- }
974
- waitingTurnState.waiting = false;
975
- const currentTurn = {
976
- turnId: pending.turnId,
977
- pendingRequests: nextPendingRequests,
978
- state: waitingTurnState,
979
- inFlightOperations: /* @__PURE__ */ new Set()
980
- };
981
- let reachedStableBoundary = false;
982
- activeTurn = currentTurn;
983
- try {
984
- for await (const candidate of input.candidate.resolveRequest({
985
- resolution,
986
- ...requestInput.signal === void 0 ? {} : { signal: requestInput.signal }
987
- })) {
988
- const output = validateAgentProviderOutputForContext(
989
- candidate,
990
- outputContext(pending.turnId)
991
- );
808
+ ...turnInput.signal === void 0 ? {} : { signal: turnInput.signal },
809
+ ...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));
992
818
  observeTurnOutput({
993
819
  capabilities,
994
820
  providerKey,
995
821
  output,
996
- state: currentTurn.state,
997
- pendingRequests: currentTurn.pendingRequests,
822
+ state,
823
+ pendingRequests,
998
824
  openedRequestIds,
825
+ turnRequestIds,
999
826
  contextUsageState,
1000
827
  interactionState
1001
828
  });
1002
- yield output;
1003
- }
1004
- while (currentTurn.inFlightOperations.size > 0) {
1005
- await Promise.all(currentTurn.inFlightOperations);
1006
- }
1007
- assertStableTurnBoundary({
1008
- providerKey,
1009
- state: currentTurn.state,
1010
- pendingRequests: currentTurn.pendingRequests
1011
- });
1012
- replacePendingRequests(pendingRequests, currentTurn.pendingRequests);
1013
- if (currentTurn.state.waiting) {
1014
- waitingTurnStates.set(currentTurn.turnId, currentTurn.state);
1015
- } else {
1016
- waitingTurnStates.delete(currentTurn.turnId);
1017
- }
1018
- reachedStableBoundary = true;
1019
- } finally {
1020
- if (!reachedStableBoundary) unusable = true;
1021
- if (activeTurn === currentTurn) activeTurn = null;
1022
- }
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;
1023
846
  };
1024
847
  const declaredInterruption = input.candidate.interruption;
1025
848
  const interruption = declaredInterruption.kind === "unsupported" ? Object.freeze({ kind: "unsupported" }) : Object.freeze({
@@ -1033,68 +856,36 @@ function validateAgentProviderSession(input) {
1033
856
  ...interruptionInput.requestedAt === void 0 ? {} : { requestedAt: interruptionInput.requestedAt }
1034
857
  });
1035
858
  const targetedActiveTurn = activeTurn?.turnId === parsed.turnId ? activeTurn : null;
1036
- const targetedWaitingTurn = targetedActiveTurn === null ? reconstructWaitingTurn({
1037
- pendingRequests,
1038
- turnId: parsed.turnId,
1039
- state: waitingTurnStates.get(parsed.turnId)
1040
- }) : null;
1041
- const targetedTurn = targetedActiveTurn ?? targetedWaitingTurn;
1042
- if (targetedTurn === null) {
859
+ const targetedTurn = targetedActiveTurn;
860
+ if (targetedTurn === null || targetedTurn.state.terminal || !targetedTurn.owner.hasEntered()) {
1043
861
  throwAgentProviderContractError(
1044
862
  providerKey,
1045
863
  "active_turn_mismatch",
1046
- "Provider interruption does not identify an active or waiting turn."
864
+ "Provider interruption does not identify an active turn."
1047
865
  );
1048
866
  }
1049
867
  const settleActiveInterruption = trackActiveTurnOperation(
1050
868
  targetedActiveTurn
1051
869
  );
1052
870
  try {
1053
- const result = await delegateOperation(
1054
- () => declaredInterruption.interruptTurn({
1055
- ...parsed,
1056
- ...interruptionInput.signal === void 0 ? {} : { signal: interruptionInput.signal }
1057
- }),
1058
- parsed.turnId
1059
- );
1060
- const terminalized = interruptionTerminalizesTurn({
1061
- turnId: parsed.turnId,
1062
- result
1063
- });
1064
- if (terminalized || result.status === "accepted") {
1065
- clearPendingRequestsForTurn(pendingRequests, parsed.turnId);
1066
- clearPendingRequestsForTurn(
1067
- targetedTurn.pendingRequests,
1068
- parsed.turnId
1069
- );
1070
- targetedTurn.state.waiting = false;
1071
- waitingTurnStates.delete(parsed.turnId);
1072
- }
871
+ const suspendedRequests = targetedTurn.owner.suspendRequests();
872
+ let result;
1073
873
  try {
1074
- observeTurnOperationOutputs({
1075
- capabilities,
1076
- providerKey,
1077
- outputs: result.outputs,
1078
- state: targetedTurn.state,
1079
- pendingRequests: targetedTurn.pendingRequests,
1080
- openedRequestIds,
1081
- contextUsageState,
1082
- interactionState
1083
- });
1084
- if (terminalized && !targetedTurn.state.terminal) {
1085
- completeTurnSequence({
1086
- providerKey,
1087
- state: targetedTurn.state,
1088
- pendingRequests: targetedTurn.pendingRequests
1089
- });
1090
- }
874
+ result = validateAgentProviderTurnInterruptionResult(
875
+ await declaredInterruption.interruptTurn({
876
+ ...parsed,
877
+ ...interruptionInput.signal === void 0 ? {} : { signal: interruptionInput.signal }
878
+ }),
879
+ outputContext(parsed.turnId)
880
+ );
1091
881
  } catch (error) {
882
+ suspendedRequests.withdraw();
1092
883
  unusable = true;
1093
884
  throw error;
1094
885
  }
1095
- if (result.status === "accepted" && targetedWaitingTurn !== null && !terminalized) {
1096
- unusable = true;
1097
- }
886
+ if (result.status === "failed") {
887
+ suspendedRequests.restore();
888
+ } else suspendedRequests.withdraw();
1098
889
  return result;
1099
890
  } finally {
1100
891
  settleActiveInterruption?.();
@@ -1108,7 +899,7 @@ function validateAgentProviderSession(input) {
1108
899
  requireUsable();
1109
900
  throwIfAgentOperationAborted(steeringInput.signal);
1110
901
  const turnId = parseAgentTurnId(steeringInput.turnId);
1111
- if (activeTurn?.turnId !== turnId || activeTurn.state.terminal || activeTurn.state.waiting) {
902
+ if (activeTurn?.turnId !== turnId || !activeTurn.owner.hasEntered() || activeTurn.state.terminal || activeTurn.pendingRequests.size > 0) {
1112
903
  throwAgentProviderContractError(
1113
904
  providerKey,
1114
905
  "active_turn_mismatch",
@@ -1128,7 +919,7 @@ function validateAgentProviderSession(input) {
1128
919
  const settleSteering = trackActiveTurnOperation(targetedActiveTurn);
1129
920
  try {
1130
921
  try {
1131
- return validateAgentTurnSteeringResult(
922
+ const result = validateAgentTurnSteeringResult(
1132
923
  await declaredSteering.steerTurn({
1133
924
  turnId,
1134
925
  ...content,
@@ -1136,6 +927,8 @@ function validateAgentProviderSession(input) {
1136
927
  }),
1137
928
  providerKey
1138
929
  );
930
+ if (result.status === "delivery_uncertain") unusable = true;
931
+ return result;
1139
932
  } catch (error) {
1140
933
  unusable = true;
1141
934
  throw new AgentProviderDelegatedOperationError(
@@ -1287,7 +1080,6 @@ function validateAgentProviderSession(input) {
1287
1080
  generatedResourceIds: /* @__PURE__ */ new Set(),
1288
1081
  started: true,
1289
1082
  terminal: false,
1290
- waiting: false,
1291
1083
  finalDiffObserved: false
1292
1084
  };
1293
1085
  interactionState.operationInvocations.set(
@@ -1601,33 +1393,37 @@ function validateAgentProviderSession(input) {
1601
1393
  return inspection;
1602
1394
  }
1603
1395
  });
1604
- const close = (closeInput) => {
1605
- if (closed) return Promise.resolve();
1606
- if (closePromise) return closePromise;
1396
+ const closeOwned = (closeInput, explicit) => {
1397
+ if (closed && !input.sessionInteractionOwner) return Promise.resolve();
1398
+ if (!input.sessionInteractionOwner && closePromise) return closePromise;
1607
1399
  throwIfAgentOperationAborted(closeInput.signal);
1608
- if (closeInput.reason !== void 0 && ![
1609
- "idle",
1610
- "shutdown",
1611
- "replaced",
1612
- "contract_rejected",
1613
- "error",
1614
- "other"
1615
- ].includes(closeInput.reason)) {
1616
- throw new TypeError("Provider session close reason is unsupported.");
1617
- }
1400
+ validateCloseReason(closeInput.reason);
1618
1401
  unusable = true;
1619
1402
  sessionLifetime.abort(new DOMException("The provider session was closed.", "AbortError"));
1620
- closePromise = Promise.resolve().then(() => input.candidate.close(closeInput)).then(() => {
1403
+ input.hostBindingLease?.stop();
1404
+ const closingTurn = activeTurn;
1405
+ closingTurn?.owner.stop(new DOMException("The provider session was closed.", "AbortError"));
1406
+ const cleanup = async () => {
1407
+ if (!providerClosed) {
1408
+ await input.candidate.close(closeInput);
1409
+ providerClosed = true;
1410
+ }
1411
+ await input.sessionInteractionOwner?.join();
1412
+ await closingTurn?.owner.join();
1413
+ await input.hostBindingLease?.release();
1621
1414
  closed = true;
1622
- }).finally(() => {
1415
+ };
1416
+ if (input.sessionInteractionOwner) return input.sessionInteractionOwner.close(cleanup, explicit);
1417
+ closePromise = Promise.resolve().then(cleanup).finally(() => {
1623
1418
  closePromise = null;
1624
1419
  });
1625
1420
  return closePromise;
1626
1421
  };
1422
+ const close = (closeInput) => closeOwned(closeInput, true);
1423
+ input.sessionInteractionOwner?.installClose(() => closeOwned({ reason: "error" }, false));
1627
1424
  return Object.freeze({
1628
1425
  binding,
1629
- runTurn,
1630
- resolveRequest,
1426
+ startTurn,
1631
1427
  interruption,
1632
1428
  steering,
1633
1429
  configuration,
@@ -1639,11 +1435,40 @@ function validateAgentProviderSession(input) {
1639
1435
  close
1640
1436
  });
1641
1437
  }
1642
- async function closeRejectedAgentProviderSession(session, error) {
1643
- if (!session || typeof session.close !== "function") throw error;
1438
+ async function closeRejectedAgentProviderSession(session, error, hostBinding, opening, interactions) {
1439
+ if (!session) throw error;
1440
+ hostBinding?.lease.stop();
1441
+ interactions?.stop();
1442
+ let providerClosed = false;
1443
+ const cleanup = async () => {
1444
+ if (!providerClosed) {
1445
+ if (typeof session.close !== "function") {
1446
+ throw new TypeError("Rejected provider session has no callable close port.");
1447
+ }
1448
+ await session.close({ reason: "contract_rejected" });
1449
+ providerClosed = true;
1450
+ }
1451
+ await interactions?.join();
1452
+ await hostBinding?.lease.release();
1453
+ };
1644
1454
  try {
1645
- await session.close({ reason: "contract_rejected" });
1455
+ if (interactions) await interactions.close(cleanup, false);
1456
+ else await cleanup();
1646
1457
  } catch (cleanupError) {
1458
+ if (hostBinding) {
1459
+ const retained = createAgentHostBindingCleanupError({
1460
+ providerKey: hostBinding.providerKey,
1461
+ lease: hostBinding.lease,
1462
+ errors: [error, cleanupError],
1463
+ retryCleanup: () => interactions ? interactions.close(cleanup, false) : cleanup()
1464
+ });
1465
+ throw opening ? associateAgentSessionCleanupError(retained, opening.input) : retained;
1466
+ }
1467
+ if (opening) throw associateAgentSessionCleanupError(new AgentProviderSessionCleanupError({
1468
+ providerKey: opening.providerKey,
1469
+ errors: [error, cleanupError],
1470
+ retryCleanup: () => interactions ? interactions.close(cleanup, false) : cleanup()
1471
+ }), opening.input);
1647
1472
  throw new AggregateError(
1648
1473
  [error, cleanupError],
1649
1474
  "Provider returned an invalid session and cleanup failed."
@@ -1713,12 +1538,24 @@ async function openIdentityCreatingAgentProviderSession(input) {
1713
1538
  capabilities: input.capabilities,
1714
1539
  sessionId: input.sessionId,
1715
1540
  candidate,
1541
+ ...input.sessionInteractionOwner === void 0 ? {} : { sessionInteractionOwner: input.sessionInteractionOwner },
1542
+ ...input.hostBindingLease === void 0 ? {} : { hostBindingLease: input.hostBindingLease },
1716
1543
  ...input.sourceBinding === void 0 ? {} : { sourceBinding: input.sourceBinding }
1717
1544
  });
1718
1545
  tracker.requireMatch(session.binding);
1546
+ input.sessionInteractionOwner?.adopt();
1719
1547
  return session;
1720
1548
  } catch (error) {
1721
- return closeRejectedAgentProviderSession(candidate, error);
1549
+ return closeRejectedAgentProviderSession(
1550
+ candidate,
1551
+ error,
1552
+ input.hostBindingLease === void 0 ? void 0 : {
1553
+ providerKey: input.capabilities.providerKey,
1554
+ lease: input.hostBindingLease
1555
+ },
1556
+ input.openingInput === void 0 ? void 0 : { providerKey: input.capabilities.providerKey, input: input.openingInput },
1557
+ input.sessionInteractionOwner
1558
+ );
1722
1559
  }
1723
1560
  }
1724
1561
  export {