@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
@@ -1,13 +1,17 @@
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";
6
+ import type { AgentSessionInteractionContext, AgentSessionInteractionSupport } from "./sessionInteractions/types.js";
5
7
  import type { AgentEnvironmentObservationPort } from "./environment/types.js";
6
8
  export interface AgentProviderSessionContext {
7
9
  readonly sessionId: AgentSessionId;
8
10
  readonly workingDirectory: string;
9
11
  readonly configuration: AgentSessionConfiguration;
10
12
  readonly signal?: AbortSignal;
13
+ readonly hostBinding?: AgentSessionHostBinding;
14
+ readonly interactions?: AgentSessionInteractionContext;
11
15
  }
12
16
  export type AgentSessionBindingCreatedObserver = (binding: AgentSessionBinding) => void;
13
17
  export interface AgentProviderCreateSessionInput extends AgentProviderSessionContext {
@@ -22,7 +26,7 @@ export interface AgentProviderBranchSessionInput extends AgentProviderSessionCon
22
26
  }
23
27
  export type AgentProviderExecutionStartedObserver = () => void;
24
28
  export type AgentProviderOutputObserver = (output: AgentProviderOutput) => MaybePromise<void>;
25
- export interface AgentProviderRunTurnInput {
29
+ export interface AgentProviderStartTurnInput {
26
30
  readonly turnId: AgentTurnId;
27
31
  readonly interactionMode: AgentTurnInteractionMode;
28
32
  readonly parts: readonly AgentTurnInputPart[];
@@ -34,6 +38,23 @@ export interface AgentProviderRunTurnInput {
34
38
  export interface AgentProviderResolveRequestInput {
35
39
  readonly resolution: AgentRequestResolution;
36
40
  readonly signal?: AbortSignal;
41
+ readonly onProviderExecutionStarted?: AgentProviderExecutionStartedObserver;
42
+ }
43
+ /** Acceptance of the canonical answer or step, not model consumption or turn completion. */
44
+ export type AgentRequestResolutionResult = Readonly<{
45
+ status: "delivered";
46
+ }> | Readonly<{
47
+ status: "rejected";
48
+ requestState: "pending" | "unavailable";
49
+ error: AgentError;
50
+ }> | Readonly<{
51
+ status: "delivery_uncertain";
52
+ error: AgentError;
53
+ }>;
54
+ export interface AgentProviderTurnExecution {
55
+ readonly turnId: AgentTurnId;
56
+ readonly outputs: AsyncIterable<AgentProviderOutput>;
57
+ readonly resolveRequest: (input: AgentProviderResolveRequestInput) => Promise<AgentRequestResolutionResult>;
37
58
  }
38
59
  export interface AgentProviderInterruptTurnInput {
39
60
  readonly turnId: AgentTurnId;
@@ -89,6 +110,11 @@ export interface AgentProviderOperationResult {
89
110
  readonly outputs?: readonly AgentProviderOutput[];
90
111
  readonly error?: AgentError;
91
112
  }
113
+ export interface AgentProviderTurnInterruptionResult {
114
+ readonly status: AgentProviderOperationStatus;
115
+ readonly error?: AgentError;
116
+ readonly outputs?: never;
117
+ }
92
118
  export type AgentTurnSteeringResult = Readonly<{
93
119
  status: "delivered";
94
120
  }> | Readonly<{
@@ -102,7 +128,7 @@ export type AgentTurnInterruption = Readonly<{
102
128
  kind: "unsupported";
103
129
  }> | Readonly<{
104
130
  kind: "supported";
105
- interruptTurn: (input: AgentProviderInterruptTurnInput) => MaybePromise<AgentProviderOperationResult>;
131
+ interruptTurn: (input: AgentProviderInterruptTurnInput) => MaybePromise<AgentProviderTurnInterruptionResult>;
106
132
  }>;
107
133
  export type AgentTurnSteering = Readonly<{
108
134
  kind: "unsupported";
@@ -145,8 +171,7 @@ export type AgentGeneratedResourceAccess = Readonly<{
145
171
  }>;
146
172
  export interface AgentProviderSession {
147
173
  readonly binding: AgentSessionBinding;
148
- readonly runTurn: (input: AgentProviderRunTurnInput) => AsyncIterable<AgentProviderOutput>;
149
- readonly resolveRequest: (input: AgentProviderResolveRequestInput) => AsyncIterable<AgentProviderOutput>;
174
+ readonly startTurn: (input: AgentProviderStartTurnInput) => AgentProviderTurnExecution;
150
175
  readonly interruption: AgentTurnInterruption;
151
176
  readonly steering: AgentTurnSteering;
152
177
  readonly configuration: AgentSessionConfigurationControl;
@@ -189,6 +214,8 @@ export type AgentProviderAuthentication = Readonly<{
189
214
  cancel: (input: AgentProviderAuthenticationCancelInput) => MaybePromise<AgentProviderOperationResult>;
190
215
  }>;
191
216
  export interface AgentProviderAdapter {
217
+ readonly sessionInteractions?: AgentSessionInteractionSupport;
218
+ readonly hostBinding?: AgentSessionHostBindingSupport;
192
219
  readonly createSession: (input: AgentProviderCreateSessionInput) => MaybePromise<AgentProviderSession>;
193
220
  readonly resumption: AgentSessionResumption;
194
221
  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;
@@ -15,7 +15,6 @@ class AgentProviderConformanceError extends Error {
15
15
  this.check = check;
16
16
  this.name = "AgentProviderConformanceError";
17
17
  }
18
- check;
19
18
  }
20
19
  function requireCheck(condition, check, message) {
21
20
  if (!condition) throw new AgentProviderConformanceError(check, message);
@@ -38,7 +37,8 @@ async function checkEnvironmentWatch(input) {
38
37
  }
39
38
  }
40
39
  async function advanceToStartedTurn(session, input) {
41
- const iterator = session.runTurn(input)[Symbol.asyncIterator]();
40
+ const execution = session.startTurn(input);
41
+ const iterator = execution.outputs[Symbol.asyncIterator]();
42
42
  const outputs = [];
43
43
  try {
44
44
  for (; ; ) {
@@ -50,7 +50,7 @@ async function advanceToStartedTurn(session, input) {
50
50
  );
51
51
  outputs.push(next.value);
52
52
  if (next.value.kind === "event" && next.value.event.type === "turn.started") {
53
- return { iterator, outputs };
53
+ return { execution, iterator, outputs };
54
54
  }
55
55
  }
56
56
  } catch (error) {
@@ -58,18 +58,18 @@ async function advanceToStartedTurn(session, input) {
58
58
  throw error;
59
59
  }
60
60
  }
61
- async function collectRemainingOutputs(started) {
61
+ async function collectRemainingOutputs(started, resolutionFor) {
62
62
  for (; ; ) {
63
63
  const next = await started.iterator.next();
64
64
  if (next.done) return started.outputs;
65
65
  started.outputs.push(next.value);
66
+ if (next.value.kind === "event" && next.value.event.type === "request.opened" && resolutionFor) {
67
+ const request = next.value.event.payload.request;
68
+ const receipt = await started.execution.resolveRequest({ resolution: parseAgentRequestResolutionFor(request, resolutionFor(request)) });
69
+ requireCheck(receipt.status === "delivered", "request_resolution", "Matching request answer was not accepted by the provider handler.");
70
+ }
66
71
  }
67
72
  }
68
- async function collectOutputs(outputs) {
69
- const collected = [];
70
- for await (const output of outputs) collected.push(output);
71
- return collected;
72
- }
73
73
  function requireOperationStatus(result, statuses, check, message) {
74
74
  requireCheck(statuses.includes(result.status), check, message);
75
75
  }
@@ -78,6 +78,12 @@ function semanticEvents(outputs) {
78
78
  (output) => output.kind === "event" ? [output.event] : []
79
79
  );
80
80
  }
81
+ function hasStableTurnBoundary(events) {
82
+ const boundary = events.findLast(
83
+ (event) => event.type === "turn.completed" || event.type === "turn.state_changed"
84
+ );
85
+ return boundary?.type === "turn.completed" || boundary?.type === "turn.state_changed" && boundary.payload.state === "waiting_for_request";
86
+ }
81
87
  async function runAgentProviderConformance(scenario) {
82
88
  const checks = [];
83
89
  try {
@@ -299,7 +305,7 @@ async function runAgentProviderConformance(scenario) {
299
305
  "Unsupported steering exposed a handler."
300
306
  );
301
307
  }
302
- turnOutputs = await collectRemainingOutputs(startedTurn);
308
+ turnOutputs = await collectRemainingOutputs(startedTurn, scenario.resolutionFor);
303
309
  } catch (error) {
304
310
  await startedTurn.iterator.return?.();
305
311
  await created.close({ reason: "contract_rejected" });
@@ -311,32 +317,13 @@ async function runAgentProviderConformance(scenario) {
311
317
  "turn_order",
312
318
  "Turn did not start first."
313
319
  );
314
- const lastEvent = events.at(-1);
315
320
  requireCheck(
316
- lastEvent?.type === "turn.completed" || lastEvent?.type === "turn.state_changed" && lastEvent.payload.state === "waiting_for_request",
321
+ hasStableTurnBoundary(events),
317
322
  "turn_order",
318
323
  "Turn did not terminate or enter a waiting state."
319
324
  );
320
325
  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
- }
326
+ if (events.some((event) => event.type === "request.opened")) checks.push("request_resolution");
340
327
  checks.push("steering");
341
328
  if (instance.capabilities.turns.interrupt) {
342
329
  let interruptedBindingCount = 0;
@@ -375,6 +362,12 @@ async function runAgentProviderConformance(scenario) {
375
362
  "interruption",
376
363
  "Live turn interruption did not succeed."
377
364
  );
365
+ const outputs = await collectRemainingOutputs(liveTurn);
366
+ requireCheck(
367
+ semanticEvents(outputs).some((event) => event.type === "turn.completed"),
368
+ "interruption",
369
+ "Interrupted turn did not produce native terminal output."
370
+ );
378
371
  } finally {
379
372
  await liveTurn?.iterator.return?.();
380
373
  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: 11,
48
48
  providerKey,
49
49
  sessions: { create: true, resume: true, branch: { kind: "through_turn" } },
50
50
  turns: {
@@ -55,6 +55,7 @@ function defaultCapabilities(providerKey) {
55
55
  requests: {
56
56
  approval: {
57
57
  kind: "supported",
58
+ providerManagedRemembrance: false,
58
59
  modes: [{ persistence: "once", scopeKinds: ["exact_action"] }]
59
60
  },
60
61
  elicitation: {
@@ -165,7 +166,7 @@ function sessionBinding(state, sessionId) {
165
166
  };
166
167
  }
167
168
  function eventBase(sessionId, turnId, occurredAt) {
168
- return { protocolVersion: 9, sessionId, turnId, occurredAt };
169
+ return { protocolVersion: 11, sessionId, turnId, occurredAt };
169
170
  }
170
171
  function fakeEnvironmentSnapshot(environmentId) {
171
172
  return parseAgentEnvironmentSnapshot({
@@ -251,15 +252,13 @@ function requestForTurn(turnId) {
251
252
  optionId: parseAgentApprovalOptionId("approval:allow-once"),
252
253
  label: "Allow once",
253
254
  decision: "approved",
254
- persistence: "once",
255
- scope: { kind: "exact_action" }
255
+ consequence: { kind: "scoped", persistence: "once", scope: { kind: "exact_action" } }
256
256
  },
257
257
  {
258
258
  optionId: parseAgentApprovalOptionId("approval:deny-once"),
259
259
  label: "Deny",
260
260
  decision: "denied",
261
- persistence: "once",
262
- scope: { kind: "exact_action" }
261
+ consequence: { kind: "scoped", persistence: "once", scope: { kind: "exact_action" } }
263
262
  }
264
263
  ]
265
264
  };
@@ -280,7 +279,8 @@ function fakeSession(input) {
280
279
  );
281
280
  };
282
281
  let closed = false;
283
- const runTurn = async function* (turnInput) {
282
+ const interruptedTurns = /* @__PURE__ */ new Set();
283
+ const outputTurn = async function* (turnInput) {
284
284
  throwIfAgentOperationAborted(turnInput.signal);
285
285
  input.state.turnIds.push(turnInput.turnId);
286
286
  const occurredAt = input.now();
@@ -289,6 +289,14 @@ function fakeSession(input) {
289
289
  type: "turn.started",
290
290
  payload: { message: "Fake provider turn started." }
291
291
  });
292
+ if (interruptedTurns.has(turnInput.turnId)) {
293
+ yield createAgentEventOutput({
294
+ ...eventBase(input.context.sessionId, turnInput.turnId, input.now()),
295
+ type: "turn.completed",
296
+ payload: { outcome: "canceled", reason: "Fake provider interrupted." }
297
+ });
298
+ return;
299
+ }
292
300
  yield createAgentEventOutput({
293
301
  ...eventBase(input.context.sessionId, turnInput.turnId, occurredAt),
294
302
  type: "item.started",
@@ -311,10 +319,13 @@ function fakeSession(input) {
311
319
  }
312
320
  if (input.capabilities.requests.approval.kind === "supported") {
313
321
  const request = requestForTurn(turnInput.turnId);
314
- pendingRequests.set(request.requestId, {
315
- request,
316
- turnId: turnInput.turnId
322
+ let release;
323
+ const answered = new Promise((resolve) => {
324
+ release = resolve;
317
325
  });
326
+ pendingRequests.set(request.requestId, { request, turnId: turnInput.turnId, release });
327
+ const aborted = () => release();
328
+ turnInput.signal?.addEventListener("abort", aborted, { once: true });
318
329
  yield createAgentEventOutput({
319
330
  ...eventBase(input.context.sessionId, turnInput.turnId, occurredAt),
320
331
  type: "request.opened",
@@ -325,17 +336,30 @@ function fakeSession(input) {
325
336
  type: "turn.state_changed",
326
337
  payload: { state: "waiting_for_request", requestId: request.requestId }
327
338
  });
328
- return;
339
+ try {
340
+ if (!turnInput.signal?.aborted) await answered;
341
+ throwIfAgentOperationAborted(turnInput.signal);
342
+ if (closed) return;
343
+ } finally {
344
+ turnInput.signal?.removeEventListener("abort", aborted);
345
+ pendingRequests.delete(request.requestId);
346
+ }
347
+ yield createAgentEventOutput({
348
+ ...eventBase(input.context.sessionId, turnInput.turnId, input.now()),
349
+ type: "turn.state_changed",
350
+ payload: { state: "running" }
351
+ });
329
352
  }
330
353
  yield createAgentEventOutput({
331
354
  ...eventBase(input.context.sessionId, turnInput.turnId, occurredAt),
332
355
  type: "turn.completed",
333
- payload: { outcome: "completed", reason: "Fake provider completed." }
356
+ payload: { outcome: interruptedTurns.has(turnInput.turnId) ? "canceled" : "completed", reason: "Fake provider completed." }
334
357
  });
335
358
  };
336
359
  const close = async () => {
337
360
  if (closed) return;
338
361
  closed = true;
362
+ for (const pending of pendingRequests.values()) pending.release();
339
363
  input.state.closeCounts.set(
340
364
  input.context.sessionId,
341
365
  (input.state.closeCounts.get(input.context.sessionId) ?? 0) + 1
@@ -343,49 +367,35 @@ function fakeSession(input) {
343
367
  };
344
368
  return {
345
369
  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
- });
370
+ startTurn(turnInput) {
371
+ return {
372
+ turnId: turnInput.turnId,
373
+ outputs: outputTurn(turnInput),
374
+ async resolveRequest(requestInput) {
375
+ throwIfAgentOperationAborted(requestInput.signal);
376
+ const pending = pendingRequests.get(requestInput.resolution.requestId);
377
+ if (!pending || pending.turnId !== turnInput.turnId) throw new TypeError("Fake provider has no matching pending request.");
378
+ const resolution = parseAgentRequestResolutionFor(pending.request, requestInput.resolution);
379
+ input.state.resolutions.push(resolution);
380
+ pendingRequests.delete(resolution.requestId);
381
+ pending.release();
382
+ return { status: "delivered" };
383
+ }
384
+ };
369
385
  },
370
386
  interruption: input.capabilities.turns.interrupt ? {
371
387
  kind: "supported",
372
388
  interruptTurn: async (interruptionInput) => {
373
389
  throwIfAgentOperationAborted(interruptionInput.signal);
374
390
  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
- ]);
391
+ interruptedTurns.add(interruptionInput.turnId);
392
+ for (const [requestId, pending] of pendingRequests) {
393
+ if (pending.turnId === interruptionInput.turnId) {
394
+ pendingRequests.delete(requestId);
395
+ pending.release();
396
+ }
397
+ }
398
+ return { status: "accepted" };
389
399
  }
390
400
  } : { kind: "unsupported" },
391
401
  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