@agen-ai/agent-runtime 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +182 -24
  2. package/dist/adapterValidation.d.ts +1 -1
  3. package/dist/adapterValidation.js +162 -95
  4. package/dist/contractErrors.d.ts +15 -2
  5. package/dist/contractErrors.js +39 -1
  6. package/dist/hostBindings/index.d.ts +4 -0
  7. package/dist/hostBindings/index.js +9 -0
  8. package/dist/hostBindings/lifetime.d.ts +25 -0
  9. package/dist/hostBindings/lifetime.js +147 -0
  10. package/dist/hostBindings/opening.d.ts +12 -0
  11. package/dist/hostBindings/opening.js +24 -0
  12. package/dist/hostBindings/schema.d.ts +9 -0
  13. package/dist/hostBindings/schema.js +169 -0
  14. package/dist/hostBindings/types.d.ts +71 -0
  15. package/dist/hostBindings/types.js +0 -0
  16. package/dist/hostBindings/validation.d.ts +14 -0
  17. package/dist/hostBindings/validation.js +107 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +6 -0
  20. package/dist/internal/objectShape.d.ts +2 -0
  21. package/dist/internal/objectShape.js +7 -0
  22. package/dist/outputValidation.js +1 -0
  23. package/dist/sessionCleanup.d.ts +9 -0
  24. package/dist/sessionCleanup.js +17 -0
  25. package/dist/sessionValidation.d.ts +12 -2
  26. package/dist/sessionValidation.js +134 -305
  27. package/dist/sessions.d.ts +28 -4
  28. package/dist/testing/conformance.d.ts +3 -3
  29. package/dist/testing/conformance.js +24 -30
  30. package/dist/testing/fakeProvider.js +56 -45
  31. package/dist/turnExecution/execution.d.ts +26 -0
  32. package/dist/turnExecution/execution.js +214 -0
  33. package/dist/turnExecution/receipts.d.ts +6 -0
  34. package/dist/turnExecution/receipts.js +39 -0
  35. package/dist/turnExecution/requests.d.ts +19 -0
  36. package/dist/turnExecution/requests.js +56 -0
  37. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
1
  import type { AgentAuthenticationFlow, AgentCollaborationControlInput, AgentCollaborationNode, AgentCollaborationSpawnInput, AgentConfigurationCatalog, AgentConfigurationSelectionInput, AgentError, AgentGeneratedResourceDescriptor, AgentGeneratedResourceId, AgentManagedContentCatalog, AgentOperationCatalog, AgentOperationInvocation, AgentOperationResult, AgentRequestResolution, AgentSessionBinding, AgentSessionBranchSource, AgentSessionConfiguration, AgentSessionId, AgentTurnId, AgentTurnInputContent, AgentTurnInteractionMode, AgentTurnInputPart, AgentTurnInterruptionReason } from "@agen-ai/agent-protocol";
2
2
  import type { MaybePromise } from "./foundation.js";
3
+ import type { AgentSessionHostBinding, AgentSessionHostBindingSupport } from "./hostBindings/types.js";
3
4
  import type { AgentProviderOutput } from "./outputs.js";
4
5
  import type { AgentArtifactCandidate } from "./artifacts.js";
5
6
  import type { AgentEnvironmentObservationPort } from "./environment/types.js";
@@ -8,6 +9,7 @@ export interface AgentProviderSessionContext {
8
9
  readonly workingDirectory: string;
9
10
  readonly configuration: AgentSessionConfiguration;
10
11
  readonly signal?: AbortSignal;
12
+ readonly hostBinding?: AgentSessionHostBinding;
11
13
  }
12
14
  export type AgentSessionBindingCreatedObserver = (binding: AgentSessionBinding) => void;
13
15
  export interface AgentProviderCreateSessionInput extends AgentProviderSessionContext {
@@ -22,7 +24,7 @@ export interface AgentProviderBranchSessionInput extends AgentProviderSessionCon
22
24
  }
23
25
  export type AgentProviderExecutionStartedObserver = () => void;
24
26
  export type AgentProviderOutputObserver = (output: AgentProviderOutput) => MaybePromise<void>;
25
- export interface AgentProviderRunTurnInput {
27
+ export interface AgentProviderStartTurnInput {
26
28
  readonly turnId: AgentTurnId;
27
29
  readonly interactionMode: AgentTurnInteractionMode;
28
30
  readonly parts: readonly AgentTurnInputPart[];
@@ -34,6 +36,23 @@ export interface AgentProviderRunTurnInput {
34
36
  export interface AgentProviderResolveRequestInput {
35
37
  readonly resolution: AgentRequestResolution;
36
38
  readonly signal?: AbortSignal;
39
+ readonly onProviderExecutionStarted?: AgentProviderExecutionStartedObserver;
40
+ }
41
+ /** Acceptance of the canonical answer or step, not model consumption or turn completion. */
42
+ export type AgentRequestResolutionResult = Readonly<{
43
+ status: "delivered";
44
+ }> | Readonly<{
45
+ status: "rejected";
46
+ requestState: "pending" | "unavailable";
47
+ error: AgentError;
48
+ }> | Readonly<{
49
+ status: "delivery_uncertain";
50
+ error: AgentError;
51
+ }>;
52
+ export interface AgentProviderTurnExecution {
53
+ readonly turnId: AgentTurnId;
54
+ readonly outputs: AsyncIterable<AgentProviderOutput>;
55
+ readonly resolveRequest: (input: AgentProviderResolveRequestInput) => Promise<AgentRequestResolutionResult>;
37
56
  }
38
57
  export interface AgentProviderInterruptTurnInput {
39
58
  readonly turnId: AgentTurnId;
@@ -89,6 +108,11 @@ export interface AgentProviderOperationResult {
89
108
  readonly outputs?: readonly AgentProviderOutput[];
90
109
  readonly error?: AgentError;
91
110
  }
111
+ export interface AgentProviderTurnInterruptionResult {
112
+ readonly status: AgentProviderOperationStatus;
113
+ readonly error?: AgentError;
114
+ readonly outputs?: never;
115
+ }
92
116
  export type AgentTurnSteeringResult = Readonly<{
93
117
  status: "delivered";
94
118
  }> | Readonly<{
@@ -102,7 +126,7 @@ export type AgentTurnInterruption = Readonly<{
102
126
  kind: "unsupported";
103
127
  }> | Readonly<{
104
128
  kind: "supported";
105
- interruptTurn: (input: AgentProviderInterruptTurnInput) => MaybePromise<AgentProviderOperationResult>;
129
+ interruptTurn: (input: AgentProviderInterruptTurnInput) => MaybePromise<AgentProviderTurnInterruptionResult>;
106
130
  }>;
107
131
  export type AgentTurnSteering = Readonly<{
108
132
  kind: "unsupported";
@@ -145,8 +169,7 @@ export type AgentGeneratedResourceAccess = Readonly<{
145
169
  }>;
146
170
  export interface AgentProviderSession {
147
171
  readonly binding: AgentSessionBinding;
148
- readonly runTurn: (input: AgentProviderRunTurnInput) => AsyncIterable<AgentProviderOutput>;
149
- readonly resolveRequest: (input: AgentProviderResolveRequestInput) => AsyncIterable<AgentProviderOutput>;
172
+ readonly startTurn: (input: AgentProviderStartTurnInput) => AgentProviderTurnExecution;
150
173
  readonly interruption: AgentTurnInterruption;
151
174
  readonly steering: AgentTurnSteering;
152
175
  readonly configuration: AgentSessionConfigurationControl;
@@ -189,6 +212,7 @@ export type AgentProviderAuthentication = Readonly<{
189
212
  cancel: (input: AgentProviderAuthenticationCancelInput) => MaybePromise<AgentProviderOperationResult>;
190
213
  }>;
191
214
  export interface AgentProviderAdapter {
215
+ readonly hostBinding?: AgentSessionHostBindingSupport;
192
216
  readonly createSession: (input: AgentProviderCreateSessionInput) => MaybePromise<AgentProviderSession>;
193
217
  readonly resumption: AgentSessionResumption;
194
218
  readonly branching: AgentSessionBranching;
@@ -1,6 +1,6 @@
1
1
  import { type AgentCollaborationSpawnInput, type AgentConfigurationSelectionInput, type AgentEnvironmentId, type AgentGeneratedResourceId, type AgentOperationInvocation, type AgentRequest, type AgentRequestResolution, type AgentSessionBinding, type AgentSessionBranchSource, type AgentSessionConfiguration, type AgentSessionId, type AgentTurnId, type AgentTurnInputContent } from "@agen-ai/agent-protocol";
2
2
  import type { AgentProviderDriver, AgentProviderInstanceDefinition } from "../providerDriver.js";
3
- import type { AgentProviderRunTurnInput } from "../sessions.js";
3
+ import type { AgentProviderStartTurnInput } from "../sessions.js";
4
4
  export interface AgentProviderConformanceScenario {
5
5
  readonly driver: AgentProviderDriver;
6
6
  readonly definition: AgentProviderInstanceDefinition;
@@ -16,8 +16,8 @@ export interface AgentProviderConformanceScenario {
16
16
  readonly branchSessionId?: AgentSessionId;
17
17
  readonly abortedSessionId: AgentSessionId;
18
18
  readonly interruptionSessionId: AgentSessionId;
19
- readonly turn: AgentProviderRunTurnInput;
20
- readonly interruptionTurn: AgentProviderRunTurnInput;
19
+ readonly turn: AgentProviderStartTurnInput;
20
+ readonly interruptionTurn: AgentProviderStartTurnInput;
21
21
  readonly resolutionFor: (request: AgentRequest) => AgentRequestResolution;
22
22
  readonly branchSource?: (binding: AgentSessionBinding, turnId: AgentTurnId) => AgentSessionBranchSource;
23
23
  readonly steeringInput?: AgentTurnInputContent;
@@ -38,7 +38,8 @@ async function checkEnvironmentWatch(input) {
38
38
  }
39
39
  }
40
40
  async function advanceToStartedTurn(session, input) {
41
- const iterator = session.runTurn(input)[Symbol.asyncIterator]();
41
+ const execution = session.startTurn(input);
42
+ const iterator = execution.outputs[Symbol.asyncIterator]();
42
43
  const outputs = [];
43
44
  try {
44
45
  for (; ; ) {
@@ -50,7 +51,7 @@ async function advanceToStartedTurn(session, input) {
50
51
  );
51
52
  outputs.push(next.value);
52
53
  if (next.value.kind === "event" && next.value.event.type === "turn.started") {
53
- return { iterator, outputs };
54
+ return { execution, iterator, outputs };
54
55
  }
55
56
  }
56
57
  } catch (error) {
@@ -58,18 +59,18 @@ async function advanceToStartedTurn(session, input) {
58
59
  throw error;
59
60
  }
60
61
  }
61
- async function collectRemainingOutputs(started) {
62
+ async function collectRemainingOutputs(started, resolutionFor) {
62
63
  for (; ; ) {
63
64
  const next = await started.iterator.next();
64
65
  if (next.done) return started.outputs;
65
66
  started.outputs.push(next.value);
67
+ if (next.value.kind === "event" && next.value.event.type === "request.opened" && resolutionFor) {
68
+ const request = next.value.event.payload.request;
69
+ const receipt = await started.execution.resolveRequest({ resolution: parseAgentRequestResolutionFor(request, resolutionFor(request)) });
70
+ requireCheck(receipt.status === "delivered", "request_resolution", "Matching request answer was not accepted by the provider handler.");
71
+ }
66
72
  }
67
73
  }
68
- async function collectOutputs(outputs) {
69
- const collected = [];
70
- for await (const output of outputs) collected.push(output);
71
- return collected;
72
- }
73
74
  function requireOperationStatus(result, statuses, check, message) {
74
75
  requireCheck(statuses.includes(result.status), check, message);
75
76
  }
@@ -78,6 +79,12 @@ function semanticEvents(outputs) {
78
79
  (output) => output.kind === "event" ? [output.event] : []
79
80
  );
80
81
  }
82
+ function hasStableTurnBoundary(events) {
83
+ const boundary = events.findLast(
84
+ (event) => event.type === "turn.completed" || event.type === "turn.state_changed"
85
+ );
86
+ return boundary?.type === "turn.completed" || boundary?.type === "turn.state_changed" && boundary.payload.state === "waiting_for_request";
87
+ }
81
88
  async function runAgentProviderConformance(scenario) {
82
89
  const checks = [];
83
90
  try {
@@ -299,7 +306,7 @@ async function runAgentProviderConformance(scenario) {
299
306
  "Unsupported steering exposed a handler."
300
307
  );
301
308
  }
302
- turnOutputs = await collectRemainingOutputs(startedTurn);
309
+ turnOutputs = await collectRemainingOutputs(startedTurn, scenario.resolutionFor);
303
310
  } catch (error) {
304
311
  await startedTurn.iterator.return?.();
305
312
  await created.close({ reason: "contract_rejected" });
@@ -311,32 +318,13 @@ async function runAgentProviderConformance(scenario) {
311
318
  "turn_order",
312
319
  "Turn did not start first."
313
320
  );
314
- const lastEvent = events.at(-1);
315
321
  requireCheck(
316
- lastEvent?.type === "turn.completed" || lastEvent?.type === "turn.state_changed" && lastEvent.payload.state === "waiting_for_request",
322
+ hasStableTurnBoundary(events),
317
323
  "turn_order",
318
324
  "Turn did not terminate or enter a waiting state."
319
325
  );
320
326
  checks.push("turn_order");
321
- const openedRequest = events.find(
322
- (event) => event.type === "request.opened"
323
- );
324
- if (openedRequest?.type === "request.opened") {
325
- const resolution = parseAgentRequestResolutionFor(
326
- openedRequest.payload.request,
327
- scenario.resolutionFor(openedRequest.payload.request)
328
- );
329
- const resolutionOutputs = await collectOutputs(
330
- created.resolveRequest({ resolution })
331
- );
332
- const resolutionBoundary = semanticEvents(resolutionOutputs).at(-1);
333
- requireCheck(
334
- resolutionBoundary?.type === "turn.completed" || resolutionBoundary?.type === "turn.state_changed" && resolutionBoundary.payload.state === "waiting_for_request",
335
- "request_resolution",
336
- "Matching request resolution did not reach a stable turn boundary."
337
- );
338
- checks.push("request_resolution");
339
- }
327
+ if (events.some((event) => event.type === "request.opened")) checks.push("request_resolution");
340
328
  checks.push("steering");
341
329
  if (instance.capabilities.turns.interrupt) {
342
330
  let interruptedBindingCount = 0;
@@ -375,6 +363,12 @@ async function runAgentProviderConformance(scenario) {
375
363
  "interruption",
376
364
  "Live turn interruption did not succeed."
377
365
  );
366
+ const outputs = await collectRemainingOutputs(liveTurn);
367
+ requireCheck(
368
+ semanticEvents(outputs).some((event) => event.type === "turn.completed"),
369
+ "interruption",
370
+ "Interrupted turn did not produce native terminal output."
371
+ );
378
372
  } finally {
379
373
  await liveTurn?.iterator.return?.();
380
374
  await interrupted.close({ reason: "other" });
@@ -44,7 +44,7 @@ function defaultCapabilities(providerKey) {
44
44
  }
45
45
  };
46
46
  return parseAgentCapabilities({
47
- protocolVersion: 9,
47
+ protocolVersion: 10,
48
48
  providerKey,
49
49
  sessions: { create: true, resume: true, branch: { kind: "through_turn" } },
50
50
  turns: {
@@ -165,7 +165,7 @@ function sessionBinding(state, sessionId) {
165
165
  };
166
166
  }
167
167
  function eventBase(sessionId, turnId, occurredAt) {
168
- return { protocolVersion: 9, sessionId, turnId, occurredAt };
168
+ return { protocolVersion: 10, sessionId, turnId, occurredAt };
169
169
  }
170
170
  function fakeEnvironmentSnapshot(environmentId) {
171
171
  return parseAgentEnvironmentSnapshot({
@@ -280,7 +280,8 @@ function fakeSession(input) {
280
280
  );
281
281
  };
282
282
  let closed = false;
283
- const runTurn = async function* (turnInput) {
283
+ const interruptedTurns = /* @__PURE__ */ new Set();
284
+ const outputTurn = async function* (turnInput) {
284
285
  throwIfAgentOperationAborted(turnInput.signal);
285
286
  input.state.turnIds.push(turnInput.turnId);
286
287
  const occurredAt = input.now();
@@ -289,6 +290,14 @@ function fakeSession(input) {
289
290
  type: "turn.started",
290
291
  payload: { message: "Fake provider turn started." }
291
292
  });
293
+ if (interruptedTurns.has(turnInput.turnId)) {
294
+ yield createAgentEventOutput({
295
+ ...eventBase(input.context.sessionId, turnInput.turnId, input.now()),
296
+ type: "turn.completed",
297
+ payload: { outcome: "canceled", reason: "Fake provider interrupted." }
298
+ });
299
+ return;
300
+ }
292
301
  yield createAgentEventOutput({
293
302
  ...eventBase(input.context.sessionId, turnInput.turnId, occurredAt),
294
303
  type: "item.started",
@@ -311,10 +320,13 @@ function fakeSession(input) {
311
320
  }
312
321
  if (input.capabilities.requests.approval.kind === "supported") {
313
322
  const request = requestForTurn(turnInput.turnId);
314
- pendingRequests.set(request.requestId, {
315
- request,
316
- turnId: turnInput.turnId
323
+ let release;
324
+ const answered = new Promise((resolve) => {
325
+ release = resolve;
317
326
  });
327
+ pendingRequests.set(request.requestId, { request, turnId: turnInput.turnId, release });
328
+ const aborted = () => release();
329
+ turnInput.signal?.addEventListener("abort", aborted, { once: true });
318
330
  yield createAgentEventOutput({
319
331
  ...eventBase(input.context.sessionId, turnInput.turnId, occurredAt),
320
332
  type: "request.opened",
@@ -325,17 +337,30 @@ function fakeSession(input) {
325
337
  type: "turn.state_changed",
326
338
  payload: { state: "waiting_for_request", requestId: request.requestId }
327
339
  });
328
- return;
340
+ try {
341
+ if (!turnInput.signal?.aborted) await answered;
342
+ throwIfAgentOperationAborted(turnInput.signal);
343
+ if (closed) return;
344
+ } finally {
345
+ turnInput.signal?.removeEventListener("abort", aborted);
346
+ pendingRequests.delete(request.requestId);
347
+ }
348
+ yield createAgentEventOutput({
349
+ ...eventBase(input.context.sessionId, turnInput.turnId, input.now()),
350
+ type: "turn.state_changed",
351
+ payload: { state: "running" }
352
+ });
329
353
  }
330
354
  yield createAgentEventOutput({
331
355
  ...eventBase(input.context.sessionId, turnInput.turnId, occurredAt),
332
356
  type: "turn.completed",
333
- payload: { outcome: "completed", reason: "Fake provider completed." }
357
+ payload: { outcome: interruptedTurns.has(turnInput.turnId) ? "canceled" : "completed", reason: "Fake provider completed." }
334
358
  });
335
359
  };
336
360
  const close = async () => {
337
361
  if (closed) return;
338
362
  closed = true;
363
+ for (const pending of pendingRequests.values()) pending.release();
339
364
  input.state.closeCounts.set(
340
365
  input.context.sessionId,
341
366
  (input.state.closeCounts.get(input.context.sessionId) ?? 0) + 1
@@ -343,49 +368,35 @@ function fakeSession(input) {
343
368
  };
344
369
  return {
345
370
  binding: input.binding,
346
- runTurn,
347
- resolveRequest: async function* (requestInput) {
348
- throwIfAgentOperationAborted(requestInput.signal);
349
- const pending = pendingRequests.get(requestInput.resolution.requestId);
350
- if (!pending)
351
- throw new TypeError("Fake provider has no matching pending request.");
352
- const resolution = parseAgentRequestResolutionFor(
353
- pending.request,
354
- requestInput.resolution
355
- );
356
- input.state.resolutions.push(resolution);
357
- pendingRequests.delete(resolution.requestId);
358
- const occurredAt = input.now();
359
- yield createAgentEventOutput({
360
- ...eventBase(input.context.sessionId, pending.turnId, occurredAt),
361
- type: "turn.state_changed",
362
- payload: { state: "running" }
363
- });
364
- yield createAgentEventOutput({
365
- ...eventBase(input.context.sessionId, pending.turnId, occurredAt),
366
- type: "turn.completed",
367
- payload: { outcome: "completed", reason: "Fake request resolved." }
368
- });
371
+ startTurn(turnInput) {
372
+ return {
373
+ turnId: turnInput.turnId,
374
+ outputs: outputTurn(turnInput),
375
+ async resolveRequest(requestInput) {
376
+ throwIfAgentOperationAborted(requestInput.signal);
377
+ const pending = pendingRequests.get(requestInput.resolution.requestId);
378
+ if (!pending || pending.turnId !== turnInput.turnId) throw new TypeError("Fake provider has no matching pending request.");
379
+ const resolution = parseAgentRequestResolutionFor(pending.request, requestInput.resolution);
380
+ input.state.resolutions.push(resolution);
381
+ pendingRequests.delete(resolution.requestId);
382
+ pending.release();
383
+ return { status: "delivered" };
384
+ }
385
+ };
369
386
  },
370
387
  interruption: input.capabilities.turns.interrupt ? {
371
388
  kind: "supported",
372
389
  interruptTurn: async (interruptionInput) => {
373
390
  throwIfAgentOperationAborted(interruptionInput.signal);
374
391
  input.state.interruptedTurnIds.push(interruptionInput.turnId);
375
- return completedResult([
376
- createAgentEventOutput({
377
- ...eventBase(
378
- input.context.sessionId,
379
- interruptionInput.turnId,
380
- input.now()
381
- ),
382
- type: "turn.completed",
383
- payload: {
384
- outcome: "canceled",
385
- reason: interruptionInput.reason
386
- }
387
- })
388
- ]);
392
+ interruptedTurns.add(interruptionInput.turnId);
393
+ for (const [requestId, pending] of pendingRequests) {
394
+ if (pending.turnId === interruptionInput.turnId) {
395
+ pendingRequests.delete(requestId);
396
+ pending.release();
397
+ }
398
+ }
399
+ return { status: "accepted" };
389
400
  }
390
401
  } : { kind: "unsupported" },
391
402
  steering: input.capabilities.turns.steer.kind === "supported" ? {
@@ -0,0 +1,26 @@
1
+ import { type AgentProviderKey } from "@agen-ai/agent-protocol";
2
+ import type { AgentProviderOutput } from "../outputs.js";
3
+ import type { AgentProviderStartTurnInput, AgentProviderTurnExecution } from "../sessions.js";
4
+ import { type PendingAgentRequest, type SuspendedAgentTurnRequests } from "./requests.js";
5
+ export interface ValidatedAgentTurnExecution {
6
+ readonly execution: AgentProviderTurnExecution;
7
+ readonly hasEntered: () => boolean;
8
+ readonly stop: (reason: Error) => void;
9
+ readonly join: () => Promise<void>;
10
+ readonly suspendRequests: () => SuspendedAgentTurnRequests;
11
+ }
12
+ export declare function createValidatedAgentTurnExecution(input: Readonly<{
13
+ providerKey: AgentProviderKey;
14
+ turnInput: AgentProviderStartTurnInput;
15
+ pendingRequests: Map<string, PendingAgentRequest>;
16
+ inFlightOperations: Set<Promise<void>>;
17
+ start: (input: AgentProviderStartTurnInput) => AgentProviderTurnExecution;
18
+ requireUsable: () => void;
19
+ isTerminal: () => boolean;
20
+ observe: (output: AgentProviderOutput) => AgentProviderOutput;
21
+ assertCompleted: () => void;
22
+ release: () => void;
23
+ retire: () => void;
24
+ abandon: () => Promise<void>;
25
+ }>): ValidatedAgentTurnExecution;
26
+ //# sourceMappingURL=execution.d.ts.map
@@ -0,0 +1,214 @@
1
+ import {
2
+ parseAgentRequestResolution,
3
+ parseAgentRequestResolutionFor
4
+ } from "@agen-ai/agent-protocol";
5
+ import { AgentProviderDelegatedOperationError, throwAgentProviderContractError } from "../contractErrors.js";
6
+ import { throwIfAgentOperationAborted } from "../foundation.js";
7
+ import { hasExactOwnKeys } from "../internal/objectShape.js";
8
+ import { validateAgentRequestResolutionResult } from "./receipts.js";
9
+ import { createAgentTurnRequestAdmission } from "./requests.js";
10
+ function createValidatedAgentTurnExecution(input) {
11
+ let state = { kind: "reserved" };
12
+ let source;
13
+ const requests = createAgentTurnRequestAdmission({
14
+ pendingRequests: input.pendingRequests,
15
+ canRestore: () => state.kind === "entered" && !input.isTerminal()
16
+ });
17
+ let acquired = false;
18
+ let pulling = false;
19
+ let returnPromise;
20
+ const lifetime = new AbortController();
21
+ const nativeOperations = /* @__PURE__ */ new Set();
22
+ const fail = (code, message) => throwAgentProviderContractError(input.providerKey, code, message);
23
+ const abortError = () => {
24
+ try {
25
+ throwIfAgentOperationAborted(lifetime.signal);
26
+ } catch (error) {
27
+ if (error instanceof Error) return error;
28
+ }
29
+ return new DOMException("The turn execution was stopped.", "AbortError");
30
+ };
31
+ const detachAbort = () => input.turnInput.signal?.removeEventListener("abort", abort);
32
+ const stop = (reason) => {
33
+ if (state.kind === "settled" || state.kind === "stopped") return;
34
+ const entered = state.kind === "entered" || state.kind === "entering";
35
+ state = { kind: "stopped", reason };
36
+ requests.withdraw();
37
+ lifetime.abort(reason);
38
+ detachAbort();
39
+ if (entered) input.retire();
40
+ else input.release();
41
+ };
42
+ const abort = () => {
43
+ let reason = new DOMException("The turn execution was aborted.", "AbortError");
44
+ try {
45
+ throwIfAgentOperationAborted(input.turnInput.signal);
46
+ } catch (error) {
47
+ if (error instanceof Error) reason = error;
48
+ }
49
+ stop(reason);
50
+ };
51
+ input.turnInput.signal?.addEventListener("abort", abort, { once: true });
52
+ const track = (operation) => {
53
+ nativeOperations.add(operation);
54
+ void operation.then(() => nativeOperations.delete(operation), () => nativeOperations.delete(operation));
55
+ return operation;
56
+ };
57
+ const join = async () => {
58
+ while (nativeOperations.size > 0 || input.inFlightOperations.size > 0) {
59
+ await Promise.allSettled([...nativeOperations, ...input.inFlightOperations]);
60
+ }
61
+ if (source?.return) await source.return();
62
+ };
63
+ const requireAnswerable = () => {
64
+ input.requireUsable();
65
+ if (state.kind !== "entered" || input.isTerminal()) {
66
+ fail("request_resolution_mismatch", "Request no longer belongs to an answerable turn execution.");
67
+ }
68
+ };
69
+ const resolveRequest = async (requestInput) => {
70
+ requireAnswerable();
71
+ throwIfAgentOperationAborted(requestInput.signal);
72
+ const resolution = parseAgentRequestResolution(requestInput.resolution);
73
+ const pending = input.pendingRequests.get(resolution.requestId);
74
+ if (!pending || pending.turnId !== input.turnInput.turnId) {
75
+ return fail("request_resolution_mismatch", "Resolution does not identify an answerable request in this execution.");
76
+ }
77
+ try {
78
+ parseAgentRequestResolutionFor(pending.request, resolution);
79
+ } catch {
80
+ return fail("request_resolution_mismatch", "Resolution does not match the opened request.");
81
+ }
82
+ if (pending.request.expiresAt !== void 0 && Date.parse(pending.request.expiresAt) <= Date.now() && resolution.disposition !== "canceled") {
83
+ return fail("request_resolution_mismatch", "Resolution identifies an expired request.");
84
+ }
85
+ if (state.kind !== "entered") return fail("request_resolution_mismatch", "Turn execution is no longer live.");
86
+ const candidate = state.candidate;
87
+ const restore = requests.claim(resolution.requestId, pending);
88
+ let settle;
89
+ const settlement = new Promise((resolve) => {
90
+ settle = resolve;
91
+ });
92
+ input.inFlightOperations.add(settlement);
93
+ try {
94
+ try {
95
+ requestInput.onProviderExecutionStarted?.();
96
+ throwIfAgentOperationAborted(requestInput.signal);
97
+ throwIfAgentOperationAborted(lifetime.signal);
98
+ requireAnswerable();
99
+ } catch (error) {
100
+ restore();
101
+ throw error;
102
+ }
103
+ let result;
104
+ try {
105
+ const signal = requestInput.signal === void 0 ? lifetime.signal : AbortSignal.any([lifetime.signal, requestInput.signal]);
106
+ result = validateAgentRequestResolutionResult(await candidate.resolveRequest({ resolution, signal }), input.providerKey);
107
+ } catch (error) {
108
+ input.retire();
109
+ throw new AgentProviderDelegatedOperationError(input.providerKey, "resolve_request", error);
110
+ }
111
+ if (result.status === "rejected" && result.requestState === "pending") restore();
112
+ if (result.status === "delivery_uncertain") input.retire();
113
+ return result;
114
+ } finally {
115
+ input.inFlightOperations.delete(settlement);
116
+ settle();
117
+ }
118
+ };
119
+ const next = async () => {
120
+ if (state.kind === "stopped") throw state.reason;
121
+ if (state.kind === "settled") return { done: true, value: void 0 };
122
+ if (pulling) return fail("concurrent_turn", "Turn output permits only one outstanding pull.");
123
+ pulling = true;
124
+ let removeAbort = () => {
125
+ };
126
+ try {
127
+ if (state.kind === "reserved") input.requireUsable();
128
+ throwIfAgentOperationAborted(lifetime.signal);
129
+ if (state.kind === "reserved") {
130
+ try {
131
+ input.turnInput.onProviderExecutionStarted?.();
132
+ } catch (error) {
133
+ state = { kind: "settled" };
134
+ detachAbort();
135
+ input.release();
136
+ throw error;
137
+ }
138
+ throwIfAgentOperationAborted(lifetime.signal);
139
+ input.requireUsable();
140
+ const { onProviderExecutionStarted: _observer, ...turnInput } = input.turnInput;
141
+ state = { kind: "entering" };
142
+ try {
143
+ const candidate = input.start({ ...turnInput, signal: lifetime.signal });
144
+ if (candidate === null || typeof candidate !== "object" || candidate.turnId !== turnInput.turnId || !hasExactOwnKeys(candidate, ["turnId", "outputs", "resolveRequest"]) || typeof candidate.resolveRequest !== "function" || !candidate.outputs || typeof candidate.outputs[Symbol.asyncIterator] !== "function") {
145
+ return fail("invalid_operation_result", "Provider returned an invalid turn execution.");
146
+ }
147
+ source = candidate.outputs[Symbol.asyncIterator]();
148
+ throwIfAgentOperationAborted(lifetime.signal);
149
+ state = { kind: "entered", candidate, iterator: source };
150
+ } catch (error) {
151
+ stop(error instanceof Error ? error : new Error("Provider turn entry failed.", { cause: error }));
152
+ throw error;
153
+ }
154
+ }
155
+ if (state.kind !== "entered") throw abortError();
156
+ const nativePull = track(Promise.resolve().then(() => state.kind === "entered" ? state.iterator.next() : Promise.reject(abortError())));
157
+ const stopped = new Promise((_, reject) => {
158
+ const onAbort = () => reject(abortError());
159
+ lifetime.signal.addEventListener("abort", onAbort, { once: true });
160
+ removeAbort = () => lifetime.signal.removeEventListener("abort", onAbort);
161
+ });
162
+ const result = await Promise.race([nativePull, stopped]);
163
+ throwIfAgentOperationAborted(lifetime.signal);
164
+ if (!result.done) {
165
+ const output = input.observe(result.value);
166
+ if (output.kind === "event" && output.event.type === "request.opened") requests.observeRequest();
167
+ return { done: false, value: output };
168
+ }
169
+ while (input.inFlightOperations.size > 0) {
170
+ await Promise.race([Promise.all(input.inFlightOperations), stopped]);
171
+ }
172
+ throwIfAgentOperationAborted(lifetime.signal);
173
+ input.assertCompleted();
174
+ state = { kind: "settled" };
175
+ detachAbort();
176
+ input.release();
177
+ return { done: true, value: void 0 };
178
+ } catch (error) {
179
+ if (state.kind === "entered" || state.kind === "entering") stop(error instanceof Error ? error : new Error("Provider output failed.", { cause: error }));
180
+ throw error;
181
+ } finally {
182
+ removeAbort();
183
+ pulling = false;
184
+ }
185
+ };
186
+ const iterator = {
187
+ next,
188
+ return() {
189
+ if (returnPromise) return returnPromise;
190
+ if (state.kind === "settled") return Promise.resolve({ done: true, value: void 0 });
191
+ const entered = state.kind === "entered" || source !== void 0;
192
+ stop(new DOMException("The turn output consumer returned.", "AbortError"));
193
+ returnPromise = (entered ? input.abandon() : Promise.resolve()).then(() => ({ done: true, value: void 0 }));
194
+ return returnPromise;
195
+ }
196
+ };
197
+ const outputs = Object.freeze({
198
+ [Symbol.asyncIterator]() {
199
+ if (acquired) return fail("concurrent_turn", "Turn output has exactly one consumer.");
200
+ acquired = true;
201
+ return iterator;
202
+ }
203
+ });
204
+ return {
205
+ execution: Object.freeze({ turnId: input.turnInput.turnId, outputs, resolveRequest }),
206
+ hasEntered: () => state.kind === "entering" || state.kind === "entered",
207
+ stop,
208
+ join,
209
+ suspendRequests: requests.suspend
210
+ };
211
+ }
212
+ export {
213
+ createValidatedAgentTurnExecution
214
+ };
@@ -0,0 +1,6 @@
1
+ import { type AgentProviderKey } from "@agen-ai/agent-protocol";
2
+ import { type AgentProviderOutputValidationContext } from "../outputValidation.js";
3
+ import type { AgentProviderTurnInterruptionResult, AgentRequestResolutionResult } from "../sessions.js";
4
+ export declare function validateAgentProviderTurnInterruptionResult(candidate: AgentProviderTurnInterruptionResult, context: AgentProviderOutputValidationContext): AgentProviderTurnInterruptionResult;
5
+ export declare function validateAgentRequestResolutionResult(candidate: unknown, providerKey: AgentProviderKey): AgentRequestResolutionResult;
6
+ //# sourceMappingURL=receipts.d.ts.map
@@ -0,0 +1,39 @@
1
+ import { parseAgentError } from "@agen-ai/agent-protocol";
2
+ import { throwAgentProviderContractError } from "../contractErrors.js";
3
+ import { hasExactOwnKeys } from "../internal/objectShape.js";
4
+ import { validateAgentProviderOperationResult } from "../outputValidation.js";
5
+ function validateAgentProviderTurnInterruptionResult(candidate, context) {
6
+ if (candidate === null || typeof candidate !== "object" || !hasExactOwnKeys(candidate, candidate.status === "failed" ? ["status", "error"] : ["status"])) {
7
+ return throwAgentProviderContractError(
8
+ context.providerKey,
9
+ "invalid_operation_result",
10
+ "Provider interruption must return an output-free operation receipt."
11
+ );
12
+ }
13
+ const result = validateAgentProviderOperationResult(candidate, context);
14
+ return Object.freeze({ status: result.status, ...result.error === void 0 ? {} : { error: result.error } });
15
+ }
16
+ function validateAgentRequestResolutionResult(candidate, providerKey) {
17
+ const invalid = () => throwAgentProviderContractError(
18
+ providerKey,
19
+ "invalid_operation_result",
20
+ "Provider returned an invalid request delivery receipt."
21
+ );
22
+ if (candidate === null || typeof candidate !== "object" || !("status" in candidate)) return invalid();
23
+ if (candidate.status === "delivered" && hasExactOwnKeys(candidate, ["status"])) {
24
+ return Object.freeze({ status: "delivered" });
25
+ }
26
+ if (!("error" in candidate)) return invalid();
27
+ const error = parseAgentError(candidate.error);
28
+ if (candidate.status === "delivery_uncertain" && hasExactOwnKeys(candidate, ["status", "error"])) {
29
+ return Object.freeze({ status: "delivery_uncertain", error });
30
+ }
31
+ if (candidate.status === "rejected" && hasExactOwnKeys(candidate, ["status", "requestState", "error"]) && "requestState" in candidate && (candidate.requestState === "pending" || candidate.requestState === "unavailable")) {
32
+ return Object.freeze({ status: "rejected", requestState: candidate.requestState, error });
33
+ }
34
+ return invalid();
35
+ }
36
+ export {
37
+ validateAgentProviderTurnInterruptionResult,
38
+ validateAgentRequestResolutionResult
39
+ };