@agen-ai/agent-runtime 0.1.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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +139 -0
  3. package/dist/adapterValidation.d.ts +4 -0
  4. package/dist/adapterValidation.js +242 -0
  5. package/dist/artifacts.d.ts +28 -0
  6. package/dist/artifacts.js +87 -0
  7. package/dist/configurationValidation.d.ts +3 -0
  8. package/dist/configurationValidation.js +35 -0
  9. package/dist/contractErrors.d.ts +17 -0
  10. package/dist/contractErrors.js +59 -0
  11. package/dist/evidence.d.ts +50 -0
  12. package/dist/evidence.js +368 -0
  13. package/dist/foundation.d.ts +8 -0
  14. package/dist/foundation.js +37 -0
  15. package/dist/index.d.ts +13 -0
  16. package/dist/index.js +12 -0
  17. package/dist/internal/controlCharacters.d.ts +2 -0
  18. package/dist/internal/controlCharacters.js +7 -0
  19. package/dist/internal/serializedJsonBytes.d.ts +3 -0
  20. package/dist/internal/serializedJsonBytes.js +57 -0
  21. package/dist/outputValidation.d.ts +13 -0
  22. package/dist/outputValidation.js +174 -0
  23. package/dist/outputs.d.ts +81 -0
  24. package/dist/outputs.js +217 -0
  25. package/dist/providerCatalog.d.ts +13 -0
  26. package/dist/providerCatalog.js +33 -0
  27. package/dist/providerDriver.d.ts +41 -0
  28. package/dist/providerDriver.js +52 -0
  29. package/dist/providerInstanceRegistry.d.ts +40 -0
  30. package/dist/providerInstanceRegistry.js +322 -0
  31. package/dist/readiness.d.ts +22 -0
  32. package/dist/readiness.js +58 -0
  33. package/dist/sessionValidation.d.ts +19 -0
  34. package/dist/sessionValidation.js +767 -0
  35. package/dist/sessions.d.ts +133 -0
  36. package/dist/sessions.js +0 -0
  37. package/dist/steeringValidation.d.ts +4 -0
  38. package/dist/steeringValidation.js +36 -0
  39. package/dist/testing/conformance.d.ts +30 -0
  40. package/dist/testing/conformance.js +379 -0
  41. package/dist/testing/fakeProvider.d.ts +35 -0
  42. package/dist/testing/fakeProvider.js +367 -0
  43. package/dist/testing/index.d.ts +3 -0
  44. package/dist/testing/index.js +2 -0
  45. package/dist/text.d.ts +2 -0
  46. package/dist/text.js +16 -0
  47. package/package.json +62 -0
@@ -0,0 +1,767 @@
1
+ import {
2
+ matchesAgentSessionBinding,
3
+ parseAgentRequestResolution,
4
+ parseAgentRequestResolutionFor,
5
+ parseAgentSessionBinding,
6
+ parseAgentSessionConfiguration,
7
+ parseAgentTurnId,
8
+ parseAgentTurnInputContent,
9
+ parseAgentTurnInterruptionInput,
10
+ parseAgentTurnRunInput
11
+ } from "@agen-ai/agent-protocol";
12
+ import {
13
+ assertAgentSessionConfigurationSupported
14
+ } from "./configurationValidation.js";
15
+ import {
16
+ AgentProviderDelegatedOperationError,
17
+ throwAgentProviderContractError
18
+ } from "./contractErrors.js";
19
+ import {
20
+ throwIfAgentOperationAborted
21
+ } from "./foundation.js";
22
+ import {
23
+ validateAgentProviderOperationResult,
24
+ validateAgentProviderOutputForContext
25
+ } from "./outputValidation.js";
26
+ import { validateAgentTurnSteeringResult } from "./steeringValidation.js";
27
+ function validateSessionPorts(capabilities, session) {
28
+ if (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)) {
29
+ throwAgentProviderContractError(
30
+ capabilities.providerKey,
31
+ "invalid_session",
32
+ `Provider ${capabilities.providerKey} returned an incomplete session.`
33
+ );
34
+ }
35
+ if (session.interruption.kind === "supported" !== capabilities.turns.interrupt || session.steering.kind === "supported" !== (capabilities.turns.steer.kind === "supported") || session.configuration.kind !== capabilities.configuration.kind || session.interruption.kind === "supported" && typeof session.interruption.interruptTurn !== "function" || session.steering.kind === "supported" && typeof session.steering.steerTurn !== "function" || session.configuration.kind === "selectable" && typeof session.configuration.applyConfiguration !== "function") {
36
+ throwAgentProviderContractError(
37
+ capabilities.providerKey,
38
+ "capability_port_mismatch",
39
+ `Provider ${capabilities.providerKey} session ports do not match its capabilities.`
40
+ );
41
+ }
42
+ }
43
+ function invalidTurnSequence(providerKey, message) {
44
+ return throwAgentProviderContractError(
45
+ providerKey,
46
+ "invalid_turn_sequence",
47
+ message
48
+ );
49
+ }
50
+ function completeTurnSequence(input) {
51
+ if (input.pendingRequests.size > 0) {
52
+ invalidTurnSequence(
53
+ input.providerKey,
54
+ "Provider completed a turn while an interaction request remained pending."
55
+ );
56
+ }
57
+ input.state.terminal = true;
58
+ input.state.waiting = false;
59
+ }
60
+ const FINAL_DIFF_TRAILING_EVENT_TYPES = [
61
+ "artifact.referenced",
62
+ "provider.diagnostic",
63
+ "runtime.error",
64
+ "runtime.warning",
65
+ "turn.completed"
66
+ ];
67
+ function observeTurnEvent(input) {
68
+ const { event, openedRequestIds, pendingRequests, providerKey, state } = input;
69
+ if (state.terminal) {
70
+ invalidTurnSequence(
71
+ providerKey,
72
+ "Provider emitted an event after turn completion."
73
+ );
74
+ }
75
+ if (event.type === "turn.started") {
76
+ if (state.started) {
77
+ invalidTurnSequence(
78
+ providerKey,
79
+ "Provider emitted turn.started more than once."
80
+ );
81
+ }
82
+ state.started = true;
83
+ } else if (!state.started) {
84
+ invalidTurnSequence(
85
+ providerKey,
86
+ "Provider emitted turn data before turn.started."
87
+ );
88
+ }
89
+ if (state.fileChangeMode === "final_diff") {
90
+ if (event.type === "turn.diff.updated") {
91
+ if (pendingRequests.size > 0) {
92
+ invalidTurnSequence(
93
+ providerKey,
94
+ "Provider emitted a terminal diff while an interaction request remained pending."
95
+ );
96
+ }
97
+ if (state.finalDiffObserved) {
98
+ invalidTurnSequence(
99
+ providerKey,
100
+ "Provider emitted more than one terminal diff for a turn."
101
+ );
102
+ }
103
+ state.finalDiffObserved = true;
104
+ } else if (state.finalDiffObserved && !FINAL_DIFF_TRAILING_EVENT_TYPES.includes(event.type)) {
105
+ invalidTurnSequence(
106
+ providerKey,
107
+ "Provider emitted turn materialization after its terminal diff."
108
+ );
109
+ }
110
+ }
111
+ if (event.type === "request.opened") {
112
+ if (pendingRequests.size > 0) {
113
+ invalidTurnSequence(
114
+ providerKey,
115
+ "Provider opened more than one pending request for a session."
116
+ );
117
+ }
118
+ const requestId = event.payload.request.requestId;
119
+ if (openedRequestIds.has(requestId)) {
120
+ invalidTurnSequence(
121
+ providerKey,
122
+ "Provider reused a request ID within a session."
123
+ );
124
+ }
125
+ openedRequestIds.add(requestId);
126
+ pendingRequests.set(requestId, {
127
+ request: event.payload.request,
128
+ turnId: event.turnId
129
+ });
130
+ }
131
+ if (event.type === "turn.state_changed") {
132
+ state.waiting = event.payload.state === "waiting_for_request";
133
+ if (state.waiting && !pendingRequests.has(event.payload.requestId)) {
134
+ invalidTurnSequence(
135
+ providerKey,
136
+ "Provider entered a waiting state for a request it did not open."
137
+ );
138
+ }
139
+ }
140
+ if (event.type === "turn.completed") {
141
+ completeTurnSequence({ providerKey, state, pendingRequests });
142
+ }
143
+ }
144
+ function assertStableTurnBoundary(input) {
145
+ const terminal = input.state.terminal && !input.state.waiting;
146
+ const waiting = !input.state.terminal && input.state.waiting && input.pendingRequests.size === 1;
147
+ if (terminal && input.pendingRequests.size === 0 || waiting) return;
148
+ invalidTurnSequence(
149
+ input.providerKey,
150
+ "Provider turn must finish with completion or exactly one pending request."
151
+ );
152
+ }
153
+ function replacePendingRequests(target, source) {
154
+ target.clear();
155
+ for (const [requestId, pending] of source) target.set(requestId, pending);
156
+ }
157
+ function trackActiveTurnOperation(turn) {
158
+ if (!turn) return null;
159
+ let settle;
160
+ const settlement = new Promise((resolve) => {
161
+ settle = resolve;
162
+ });
163
+ turn.inFlightOperations.add(settlement);
164
+ return () => {
165
+ turn.inFlightOperations.delete(settlement);
166
+ settle();
167
+ };
168
+ }
169
+ function interruptionTerminalizesTurn(input) {
170
+ if (["completed", "canceled"].includes(input.result.status)) {
171
+ return true;
172
+ }
173
+ return (input.result.outputs ?? []).some(
174
+ (output) => output.kind === "event" && output.event.turnId === input.turnId && output.event.type === "turn.completed"
175
+ );
176
+ }
177
+ function observeTurnOperationOutputs(input) {
178
+ for (const output of input.outputs ?? []) {
179
+ if (output.kind !== "event") continue;
180
+ observeTurnEvent({
181
+ providerKey: input.providerKey,
182
+ event: output.event,
183
+ state: input.state,
184
+ pendingRequests: input.pendingRequests,
185
+ openedRequestIds: input.openedRequestIds
186
+ });
187
+ }
188
+ }
189
+ function reconstructWaitingTurn(input) {
190
+ const targetedPendingRequests = new Map(
191
+ [...input.pendingRequests].filter(
192
+ ([, pending]) => pending.turnId === input.turnId
193
+ )
194
+ );
195
+ if (targetedPendingRequests.size === 0) return null;
196
+ return {
197
+ state: {
198
+ fileChangeMode: input.fileChangeMode,
199
+ started: true,
200
+ terminal: false,
201
+ waiting: true,
202
+ finalDiffObserved: false
203
+ },
204
+ pendingRequests: targetedPendingRequests
205
+ };
206
+ }
207
+ function clearPendingRequestsForTurn(pendingRequests, turnId) {
208
+ for (const [requestId, pending] of pendingRequests) {
209
+ if (pending.turnId === turnId) pendingRequests.delete(requestId);
210
+ }
211
+ }
212
+ function assertAgentTurnInputCapability(input) {
213
+ const imageParts = input.parts.filter((part) => part.type === "image");
214
+ if (imageParts.length === 0) return;
215
+ const imageCapability = input.capability.images;
216
+ if (imageCapability.kind === "unsupported") {
217
+ throwAgentProviderContractError(
218
+ input.providerKey,
219
+ "input_capability_mismatch",
220
+ `Provider ${input.providerKey} does not accept image input.`
221
+ );
222
+ }
223
+ const reject = (reason) => throwAgentProviderContractError(
224
+ input.providerKey,
225
+ "input_capability_mismatch",
226
+ `Provider ${input.providerKey} cannot accept this image input: ${reason}.`
227
+ );
228
+ if (imageParts.length > imageCapability.maxImages) {
229
+ reject("image count exceeds the declared limit");
230
+ }
231
+ if (!imageCapability.supportsImageOnly && !input.parts.some((part) => part.type === "text" && part.text.trim().length > 0)) {
232
+ reject("image-only input is unsupported");
233
+ }
234
+ let totalBytes = 0;
235
+ for (const part of imageParts) {
236
+ const { source } = part;
237
+ if (!imageCapability.sourceKinds.includes(source.type)) {
238
+ reject(`source kind ${source.type} is unsupported`);
239
+ }
240
+ if (!imageCapability.mediaTypes.includes(source.mediaType)) {
241
+ reject(`media type ${source.mediaType} is unsupported`);
242
+ }
243
+ if (source.byteSize > imageCapability.maxBytesPerImage) {
244
+ reject("an image exceeds the declared byte limit");
245
+ }
246
+ if (source.widthPixels > imageCapability.maxWidthPixels || source.heightPixels > imageCapability.maxHeightPixels || source.widthPixels * source.heightPixels > imageCapability.maxPixelsPerImage) {
247
+ reject("an image exceeds the declared dimension or pixel limit");
248
+ }
249
+ totalBytes += source.byteSize;
250
+ }
251
+ if (totalBytes > imageCapability.maxTotalBytes) {
252
+ reject("aggregate image bytes exceed the declared limit");
253
+ }
254
+ }
255
+ function validateAgentProviderSession(input) {
256
+ const { capabilities, sessionId } = input;
257
+ const providerKey = capabilities.providerKey;
258
+ if (input.candidate === null || typeof input.candidate !== "object") {
259
+ throwAgentProviderContractError(
260
+ providerKey,
261
+ "invalid_session",
262
+ "Provider returned an invalid session."
263
+ );
264
+ }
265
+ let binding;
266
+ try {
267
+ binding = parseAgentSessionBinding(input.candidate.binding);
268
+ } catch {
269
+ throwAgentProviderContractError(
270
+ providerKey,
271
+ "invalid_binding",
272
+ "Provider returned an invalid binding."
273
+ );
274
+ }
275
+ if (input.expectedBinding && !matchesAgentSessionBinding(binding, input.expectedBinding)) {
276
+ throwAgentProviderContractError(
277
+ providerKey,
278
+ "resume_binding_mismatch",
279
+ "Provider resumed another binding."
280
+ );
281
+ }
282
+ if (input.sourceBinding && matchesAgentSessionBinding(binding, input.sourceBinding)) {
283
+ throwAgentProviderContractError(
284
+ providerKey,
285
+ "branch_binding_reused",
286
+ "Provider branch reused its source binding."
287
+ );
288
+ }
289
+ validateSessionPorts(capabilities, input.candidate);
290
+ const pendingRequests = /* @__PURE__ */ new Map();
291
+ const openedRequestIds = /* @__PURE__ */ new Set();
292
+ let activeTurn = null;
293
+ let closePromise = null;
294
+ let closed = false;
295
+ let unusable = false;
296
+ const requireOpen = () => {
297
+ if (closed)
298
+ throwAgentProviderContractError(
299
+ providerKey,
300
+ "session_closed",
301
+ "Provider session is closed."
302
+ );
303
+ };
304
+ const requireUsable = () => {
305
+ requireOpen();
306
+ if (unusable) {
307
+ throwAgentProviderContractError(
308
+ providerKey,
309
+ "session_unusable",
310
+ "Provider session cannot be reused after an incomplete operation."
311
+ );
312
+ }
313
+ };
314
+ const outputContext = (turnId) => ({
315
+ capabilities,
316
+ providerKey,
317
+ sessionId,
318
+ ...turnId === void 0 ? {} : { turnId }
319
+ });
320
+ const delegateOperation = async (operation, turnId) => {
321
+ try {
322
+ return validateAgentProviderOperationResult(
323
+ await operation(),
324
+ outputContext(turnId)
325
+ );
326
+ } catch (error) {
327
+ unusable = true;
328
+ throw error;
329
+ }
330
+ };
331
+ const runTurn = async function* (turnInput) {
332
+ requireUsable();
333
+ throwIfAgentOperationAborted(turnInput.signal);
334
+ const parsedInput = parseAgentTurnRunInput({
335
+ turnId: turnInput.turnId,
336
+ interactionMode: turnInput.interactionMode,
337
+ parts: turnInput.parts,
338
+ ...turnInput.summary === void 0 ? {} : { summary: turnInput.summary },
339
+ ...turnInput.deadlineAt === void 0 ? {} : { deadlineAt: turnInput.deadlineAt }
340
+ });
341
+ assertAgentTurnInputCapability({
342
+ capability: capabilities.input,
343
+ parts: parsedInput.parts,
344
+ providerKey
345
+ });
346
+ if (!capabilities.turns.interactionModes.includes(parsedInput.interactionMode)) {
347
+ throwAgentProviderContractError(
348
+ providerKey,
349
+ "input_capability_mismatch",
350
+ `Provider ${providerKey} does not support ${parsedInput.interactionMode} turn interaction mode.`
351
+ );
352
+ }
353
+ if (pendingRequests.size > 0) {
354
+ throwAgentProviderContractError(
355
+ providerKey,
356
+ "request_pending",
357
+ "Provider session cannot start another turn while a request is pending."
358
+ );
359
+ }
360
+ if (activeTurn !== null) {
361
+ throwAgentProviderContractError(
362
+ providerKey,
363
+ "concurrent_turn",
364
+ "Provider session already has an active turn."
365
+ );
366
+ }
367
+ let reachedStableBoundary = false;
368
+ const currentTurn = {
369
+ turnId: parsedInput.turnId,
370
+ pendingRequests: /* @__PURE__ */ new Map(),
371
+ state: {
372
+ fileChangeMode: capabilities.output.fileChanges,
373
+ started: false,
374
+ terminal: false,
375
+ waiting: false,
376
+ finalDiffObserved: false
377
+ },
378
+ inFlightOperations: /* @__PURE__ */ new Set()
379
+ };
380
+ activeTurn = currentTurn;
381
+ try {
382
+ turnInput.onProviderExecutionStarted?.();
383
+ for await (const candidate of input.candidate.runTurn({
384
+ ...parsedInput,
385
+ ...turnInput.signal === void 0 ? {} : { signal: turnInput.signal }
386
+ })) {
387
+ const output = validateAgentProviderOutputForContext(
388
+ candidate,
389
+ outputContext(parsedInput.turnId)
390
+ );
391
+ if (output.kind === "event") {
392
+ observeTurnEvent({
393
+ providerKey,
394
+ event: output.event,
395
+ state: currentTurn.state,
396
+ pendingRequests: currentTurn.pendingRequests,
397
+ openedRequestIds
398
+ });
399
+ }
400
+ yield output;
401
+ }
402
+ while (currentTurn.inFlightOperations.size > 0) {
403
+ await Promise.all(currentTurn.inFlightOperations);
404
+ }
405
+ if (!currentTurn.state.started) {
406
+ invalidTurnSequence(
407
+ providerKey,
408
+ "Provider turn must emit turn.started before reaching a stable boundary."
409
+ );
410
+ }
411
+ assertStableTurnBoundary({
412
+ providerKey,
413
+ state: currentTurn.state,
414
+ pendingRequests: currentTurn.pendingRequests
415
+ });
416
+ if (currentTurn.state.waiting) {
417
+ replacePendingRequests(pendingRequests, currentTurn.pendingRequests);
418
+ }
419
+ reachedStableBoundary = true;
420
+ } finally {
421
+ if (!reachedStableBoundary) unusable = true;
422
+ if (activeTurn === currentTurn) activeTurn = null;
423
+ }
424
+ };
425
+ const resolveRequest = async function* (requestInput) {
426
+ requireUsable();
427
+ throwIfAgentOperationAborted(requestInput.signal);
428
+ const resolution = parseAgentRequestResolution(requestInput.resolution);
429
+ const pending = pendingRequests.get(resolution.requestId);
430
+ if (!pending) {
431
+ throwAgentProviderContractError(
432
+ providerKey,
433
+ "request_resolution_mismatch",
434
+ "Provider request resolution does not identify a pending request."
435
+ );
436
+ }
437
+ try {
438
+ parseAgentRequestResolutionFor(pending.request, resolution);
439
+ } catch {
440
+ throwAgentProviderContractError(
441
+ providerKey,
442
+ "request_resolution_mismatch",
443
+ "Provider request resolution does not match the opened request."
444
+ );
445
+ }
446
+ const nextPendingRequests = new Map(pendingRequests);
447
+ nextPendingRequests.delete(resolution.requestId);
448
+ if (activeTurn !== null) {
449
+ throwAgentProviderContractError(
450
+ providerKey,
451
+ "concurrent_turn",
452
+ "Provider session already has an active turn."
453
+ );
454
+ }
455
+ const currentTurn = {
456
+ turnId: pending.turnId,
457
+ pendingRequests: nextPendingRequests,
458
+ state: {
459
+ fileChangeMode: capabilities.output.fileChanges,
460
+ started: true,
461
+ terminal: false,
462
+ waiting: false,
463
+ finalDiffObserved: false
464
+ },
465
+ inFlightOperations: /* @__PURE__ */ new Set()
466
+ };
467
+ let reachedStableBoundary = false;
468
+ activeTurn = currentTurn;
469
+ try {
470
+ for await (const candidate of input.candidate.resolveRequest({
471
+ resolution,
472
+ ...requestInput.signal === void 0 ? {} : { signal: requestInput.signal }
473
+ })) {
474
+ const output = validateAgentProviderOutputForContext(
475
+ candidate,
476
+ outputContext(pending.turnId)
477
+ );
478
+ if (output.kind === "event") {
479
+ observeTurnEvent({
480
+ providerKey,
481
+ event: output.event,
482
+ state: currentTurn.state,
483
+ pendingRequests: currentTurn.pendingRequests,
484
+ openedRequestIds
485
+ });
486
+ }
487
+ yield output;
488
+ }
489
+ while (currentTurn.inFlightOperations.size > 0) {
490
+ await Promise.all(currentTurn.inFlightOperations);
491
+ }
492
+ assertStableTurnBoundary({
493
+ providerKey,
494
+ state: currentTurn.state,
495
+ pendingRequests: currentTurn.pendingRequests
496
+ });
497
+ replacePendingRequests(pendingRequests, currentTurn.pendingRequests);
498
+ reachedStableBoundary = true;
499
+ } finally {
500
+ if (!reachedStableBoundary) unusable = true;
501
+ if (activeTurn === currentTurn) activeTurn = null;
502
+ }
503
+ };
504
+ const declaredInterruption = input.candidate.interruption;
505
+ const interruption = declaredInterruption.kind === "unsupported" ? Object.freeze({ kind: "unsupported" }) : Object.freeze({
506
+ kind: "supported",
507
+ interruptTurn: async (interruptionInput) => {
508
+ requireUsable();
509
+ throwIfAgentOperationAborted(interruptionInput.signal);
510
+ const parsed = parseAgentTurnInterruptionInput({
511
+ turnId: interruptionInput.turnId,
512
+ reason: interruptionInput.reason,
513
+ ...interruptionInput.requestedAt === void 0 ? {} : { requestedAt: interruptionInput.requestedAt }
514
+ });
515
+ const targetedActiveTurn = activeTurn?.turnId === parsed.turnId ? activeTurn : null;
516
+ const targetedWaitingTurn = targetedActiveTurn === null ? reconstructWaitingTurn({
517
+ pendingRequests,
518
+ turnId: parsed.turnId,
519
+ fileChangeMode: capabilities.output.fileChanges
520
+ }) : null;
521
+ const targetedTurn = targetedActiveTurn ?? targetedWaitingTurn;
522
+ if (targetedTurn === null) {
523
+ throwAgentProviderContractError(
524
+ providerKey,
525
+ "active_turn_mismatch",
526
+ "Provider interruption does not identify an active or waiting turn."
527
+ );
528
+ }
529
+ const settleActiveInterruption = trackActiveTurnOperation(
530
+ targetedActiveTurn
531
+ );
532
+ try {
533
+ const result = await delegateOperation(
534
+ () => declaredInterruption.interruptTurn({
535
+ ...parsed,
536
+ ...interruptionInput.signal === void 0 ? {} : { signal: interruptionInput.signal }
537
+ }),
538
+ parsed.turnId
539
+ );
540
+ const terminalized = interruptionTerminalizesTurn({
541
+ turnId: parsed.turnId,
542
+ result
543
+ });
544
+ if (terminalized || result.status === "accepted") {
545
+ clearPendingRequestsForTurn(pendingRequests, parsed.turnId);
546
+ clearPendingRequestsForTurn(
547
+ targetedTurn.pendingRequests,
548
+ parsed.turnId
549
+ );
550
+ targetedTurn.state.waiting = false;
551
+ }
552
+ try {
553
+ observeTurnOperationOutputs({
554
+ providerKey,
555
+ outputs: result.outputs,
556
+ state: targetedTurn.state,
557
+ pendingRequests: targetedTurn.pendingRequests,
558
+ openedRequestIds
559
+ });
560
+ if (terminalized && !targetedTurn.state.terminal) {
561
+ completeTurnSequence({
562
+ providerKey,
563
+ state: targetedTurn.state,
564
+ pendingRequests: targetedTurn.pendingRequests
565
+ });
566
+ }
567
+ } catch (error) {
568
+ unusable = true;
569
+ throw error;
570
+ }
571
+ if (result.status === "accepted" && targetedWaitingTurn !== null && !terminalized) {
572
+ unusable = true;
573
+ }
574
+ return result;
575
+ } finally {
576
+ settleActiveInterruption?.();
577
+ }
578
+ }
579
+ });
580
+ const declaredSteering = input.candidate.steering;
581
+ const steering = declaredSteering.kind === "unsupported" ? Object.freeze({ kind: "unsupported" }) : Object.freeze({
582
+ kind: "supported",
583
+ steerTurn: async (steeringInput) => {
584
+ requireUsable();
585
+ throwIfAgentOperationAborted(steeringInput.signal);
586
+ const turnId = parseAgentTurnId(steeringInput.turnId);
587
+ if (activeTurn?.turnId !== turnId || activeTurn.state.terminal || activeTurn.state.waiting) {
588
+ throwAgentProviderContractError(
589
+ providerKey,
590
+ "active_turn_mismatch",
591
+ "Provider steering does not identify a running turn."
592
+ );
593
+ }
594
+ const content = parseAgentTurnInputContent({
595
+ parts: steeringInput.parts,
596
+ ...steeringInput.summary === void 0 ? {} : { summary: steeringInput.summary }
597
+ });
598
+ assertAgentTurnInputCapability({
599
+ capability: capabilities.turns.steer.kind === "supported" ? capabilities.turns.steer.input : capabilities.input,
600
+ parts: content.parts,
601
+ providerKey
602
+ });
603
+ const targetedActiveTurn = activeTurn;
604
+ const settleSteering = trackActiveTurnOperation(targetedActiveTurn);
605
+ try {
606
+ try {
607
+ return validateAgentTurnSteeringResult(
608
+ await declaredSteering.steerTurn({
609
+ turnId,
610
+ ...content,
611
+ ...steeringInput.signal === void 0 ? {} : { signal: steeringInput.signal }
612
+ }),
613
+ providerKey
614
+ );
615
+ } catch (error) {
616
+ unusable = true;
617
+ throw new AgentProviderDelegatedOperationError(
618
+ providerKey,
619
+ "steer_turn",
620
+ error
621
+ );
622
+ }
623
+ } finally {
624
+ settleSteering?.();
625
+ }
626
+ }
627
+ });
628
+ const declaredConfiguration = input.candidate.configuration;
629
+ const configuration = declaredConfiguration.kind === "managed" ? Object.freeze({ kind: "managed" }) : Object.freeze({
630
+ kind: "selectable",
631
+ applyConfiguration: async (configurationInput) => {
632
+ requireUsable();
633
+ throwIfAgentOperationAborted(configurationInput.signal);
634
+ const configuration2 = parseAgentSessionConfiguration(
635
+ configurationInput.configuration
636
+ );
637
+ assertAgentSessionConfigurationSupported(
638
+ capabilities,
639
+ configuration2
640
+ );
641
+ return delegateOperation(
642
+ () => declaredConfiguration.applyConfiguration({
643
+ configuration: configuration2,
644
+ ...configurationInput.signal === void 0 ? {} : { signal: configurationInput.signal }
645
+ })
646
+ );
647
+ }
648
+ });
649
+ const close = (closeInput) => {
650
+ if (closed) return Promise.resolve();
651
+ if (closePromise) return closePromise;
652
+ throwIfAgentOperationAborted(closeInput.signal);
653
+ if (closeInput.reason !== void 0 && ![
654
+ "idle",
655
+ "shutdown",
656
+ "replaced",
657
+ "contract_rejected",
658
+ "error",
659
+ "other"
660
+ ].includes(closeInput.reason)) {
661
+ throw new TypeError("Provider session close reason is unsupported.");
662
+ }
663
+ unusable = true;
664
+ closePromise = Promise.resolve().then(() => input.candidate.close(closeInput)).then(() => {
665
+ closed = true;
666
+ }).finally(() => {
667
+ closePromise = null;
668
+ });
669
+ return closePromise;
670
+ };
671
+ return Object.freeze({
672
+ binding,
673
+ runTurn,
674
+ resolveRequest,
675
+ interruption,
676
+ steering,
677
+ configuration,
678
+ close
679
+ });
680
+ }
681
+ async function closeRejectedAgentProviderSession(session, error) {
682
+ if (!session || typeof session.close !== "function") throw error;
683
+ try {
684
+ await session.close({ reason: "contract_rejected" });
685
+ } catch (cleanupError) {
686
+ throw new AggregateError(
687
+ [error, cleanupError],
688
+ "Provider returned an invalid session and cleanup failed."
689
+ );
690
+ }
691
+ throw error;
692
+ }
693
+ function bindingTracker(input) {
694
+ let observed = null;
695
+ return {
696
+ observe(candidate) {
697
+ if (observed) {
698
+ throwAgentProviderContractError(
699
+ input.providerKey,
700
+ "binding_callback_repeated",
701
+ "Provider reported session binding creation more than once."
702
+ );
703
+ }
704
+ let binding;
705
+ try {
706
+ binding = parseAgentSessionBinding(candidate);
707
+ } catch {
708
+ throwAgentProviderContractError(
709
+ input.providerKey,
710
+ "invalid_binding",
711
+ "Provider reported an invalid binding."
712
+ );
713
+ }
714
+ if (input.sourceBinding && matchesAgentSessionBinding(binding, input.sourceBinding)) {
715
+ throwAgentProviderContractError(
716
+ input.providerKey,
717
+ "branch_binding_reused",
718
+ "Provider branch reused its source binding."
719
+ );
720
+ }
721
+ input.observer(binding);
722
+ observed = binding;
723
+ },
724
+ requireMatch(binding) {
725
+ if (!observed) {
726
+ throwAgentProviderContractError(
727
+ input.providerKey,
728
+ "binding_callback_missing",
729
+ "Provider did not report binding creation before returning a session."
730
+ );
731
+ }
732
+ if (!matchesAgentSessionBinding(observed, binding)) {
733
+ throwAgentProviderContractError(
734
+ input.providerKey,
735
+ "binding_callback_mismatch",
736
+ "Provider returned a different binding than it reported."
737
+ );
738
+ }
739
+ }
740
+ };
741
+ }
742
+ async function openIdentityCreatingAgentProviderSession(input) {
743
+ const tracker = bindingTracker({
744
+ providerKey: input.capabilities.providerKey,
745
+ observer: input.observer,
746
+ ...input.sourceBinding === void 0 ? {} : { sourceBinding: input.sourceBinding }
747
+ });
748
+ let candidate = null;
749
+ try {
750
+ candidate = await input.open(tracker.observe);
751
+ const session = validateAgentProviderSession({
752
+ capabilities: input.capabilities,
753
+ sessionId: input.sessionId,
754
+ candidate,
755
+ ...input.sourceBinding === void 0 ? {} : { sourceBinding: input.sourceBinding }
756
+ });
757
+ tracker.requireMatch(session.binding);
758
+ return session;
759
+ } catch (error) {
760
+ return closeRejectedAgentProviderSession(candidate, error);
761
+ }
762
+ }
763
+ export {
764
+ closeRejectedAgentProviderSession,
765
+ openIdentityCreatingAgentProviderSession,
766
+ validateAgentProviderSession
767
+ };