@agen-ai/agent-runtime 0.4.0 → 0.4.2

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 (45) hide show
  1. package/README.md +200 -9
  2. package/dist/adapterValidation.d.ts +4 -2
  3. package/dist/adapterValidation.js +140 -51
  4. package/dist/artifacts.js +0 -1
  5. package/dist/contractErrors.d.ts +2 -2
  6. package/dist/contractErrors.js +11 -7
  7. package/dist/hostBindings/opening.d.ts +2 -0
  8. package/dist/hostBindings/opening.js +58 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +2 -0
  11. package/dist/nativeContinuations/admission.d.ts +17 -0
  12. package/dist/nativeContinuations/admission.js +250 -0
  13. package/dist/nativeContinuations/index.d.ts +2 -0
  14. package/dist/nativeContinuations/index.js +1 -0
  15. package/dist/nativeContinuations/types.d.ts +52 -0
  16. package/dist/nativeContinuations/types.js +0 -0
  17. package/dist/outputValidation.js +3 -2
  18. package/dist/providerCatalog.d.ts +1 -1
  19. package/dist/providerDriver.d.ts +4 -1
  20. package/dist/providerDriver.js +0 -1
  21. package/dist/providerInstanceRegistry.d.ts +4 -4
  22. package/dist/providerInstanceRegistry.js +2 -1
  23. package/dist/sessionAdmission.d.ts +12 -0
  24. package/dist/sessionAdmission.js +54 -0
  25. package/dist/sessionInteractions/index.d.ts +2 -0
  26. package/dist/sessionInteractions/index.js +1 -0
  27. package/dist/sessionInteractions/lifetime.d.ts +81 -0
  28. package/dist/sessionInteractions/lifetime.js +383 -0
  29. package/dist/sessionInteractions/request.d.ts +43 -0
  30. package/dist/sessionInteractions/request.js +381 -0
  31. package/dist/sessionInteractions/types.d.ts +110 -0
  32. package/dist/sessionInteractions/types.js +10 -0
  33. package/dist/sessionInteractions/validation.d.ts +25 -0
  34. package/dist/sessionInteractions/validation.js +180 -0
  35. package/dist/sessionValidation.d.ts +82 -3
  36. package/dist/sessionValidation.js +240 -181
  37. package/dist/sessions.d.ts +34 -0
  38. package/dist/testing/conformance.d.ts +3 -1
  39. package/dist/testing/conformance.js +180 -86
  40. package/dist/testing/fakeProvider.js +5 -6
  41. package/dist/turnExecution/execution.d.ts +23 -6
  42. package/dist/turnExecution/execution.js +136 -32
  43. package/dist/turnExecution/requests.d.ts +15 -3
  44. package/dist/turnExecution/requests.js +93 -28
  45. package/package.json +2 -2
@@ -2,22 +2,68 @@ import {
2
2
  parseAgentRequestResolution,
3
3
  parseAgentRequestResolutionFor
4
4
  } from "@agen-ai/agent-protocol";
5
- import { AgentProviderDelegatedOperationError, throwAgentProviderContractError } from "../contractErrors.js";
5
+ import { AgentProviderContractError, AgentProviderDelegatedOperationError, throwAgentProviderContractError } from "../contractErrors.js";
6
6
  import { throwIfAgentOperationAborted } from "../foundation.js";
7
7
  import { hasExactOwnKeys } from "../internal/objectShape.js";
8
8
  import { validateAgentRequestResolutionResult } from "./receipts.js";
9
9
  import { createAgentTurnRequestAdmission } from "./requests.js";
10
10
  function createValidatedAgentTurnExecution(input) {
11
- let state = { kind: "reserved" };
11
+ return createExecution(input, { kind: "caller", input: input.turnInput, start: input.start });
12
+ }
13
+ function adoptValidatedAgentTurnExecution(input) {
14
+ return createExecution(input, { kind: "native", turnId: input.turnId, createCandidate: input.createCandidate });
15
+ }
16
+ function createExecution(input, origin) {
17
+ const turnId = origin.kind === "caller" ? origin.input.turnId : origin.turnId;
18
+ const callerSignal = origin.kind === "caller" ? origin.input.signal : void 0;
19
+ const lifetime = new AbortController();
20
+ const validateCandidate = (candidate) => {
21
+ if (candidate === null || typeof candidate !== "object" || candidate.turnId !== turnId || !hasExactOwnKeys(candidate, ["turnId", "outputs", "resolveRequest"]) || typeof candidate.resolveRequest !== "function" || !candidate.outputs || typeof candidate.outputs[Symbol.asyncIterator] !== "function") {
22
+ throwAgentProviderContractError(
23
+ input.providerKey,
24
+ origin.kind === "native" ? "invalid_native_continuation" : "invalid_operation_result",
25
+ "Provider returned an invalid turn execution."
26
+ );
27
+ }
28
+ return candidate;
29
+ };
30
+ let state;
31
+ try {
32
+ state = origin.kind === "native" ? { kind: "parked", candidate: validateCandidate(origin.createCandidate(lifetime.signal)) } : { kind: "reserved" };
33
+ } catch (cause) {
34
+ lifetime.abort(cause);
35
+ throw cause;
36
+ }
37
+ let ready;
38
+ const readerReady = new Promise((resolve) => {
39
+ ready = resolve;
40
+ });
41
+ let allowDispatch;
42
+ const dispatch = new Promise((resolve) => {
43
+ allowDispatch = resolve;
44
+ });
45
+ let complete;
46
+ const completed = new Promise((resolve) => {
47
+ complete = resolve;
48
+ });
49
+ const currentState = () => state;
50
+ let dispatched = false;
51
+ let observedOutput = false;
52
+ let resolveDisposition;
53
+ const disposition = new Promise((resolve) => {
54
+ resolveDisposition = resolve;
55
+ });
12
56
  let source;
13
57
  const requests = createAgentTurnRequestAdmission({
58
+ providerKey: input.providerKey,
59
+ sessionAdmission: input.sessionAdmission,
14
60
  pendingRequests: input.pendingRequests,
15
- canRestore: () => state.kind === "entered" && !input.isTerminal()
61
+ canRestore: () => state.kind === "entered" && !input.isTerminal(),
62
+ canRestoreRequest: input.canRestoreRequest
16
63
  });
17
64
  let acquired = false;
18
65
  let pulling = false;
19
66
  let returnPromise;
20
- const lifetime = new AbortController();
21
67
  const nativeOperations = /* @__PURE__ */ new Set();
22
68
  const fail = (code, message) => throwAgentProviderContractError(input.providerKey, code, message);
23
69
  const abortError = () => {
@@ -28,37 +74,51 @@ function createValidatedAgentTurnExecution(input) {
28
74
  }
29
75
  return new DOMException("The turn execution was stopped.", "AbortError");
30
76
  };
31
- const detachAbort = () => input.turnInput.signal?.removeEventListener("abort", abort);
77
+ const detachAbort = () => callerSignal?.removeEventListener("abort", abort);
32
78
  const stop = (reason) => {
33
79
  if (state.kind === "settled" || state.kind === "stopped") return;
34
- const entered = state.kind === "entered" || state.kind === "entering";
80
+ const entered = dispatched || state.kind === "entered" || state.kind === "entering";
35
81
  state = { kind: "stopped", reason };
36
82
  requests.withdraw();
37
83
  lifetime.abort(reason);
38
84
  detachAbort();
39
85
  if (entered) input.retire();
40
- else input.release();
86
+ else if (origin.kind === "caller") {
87
+ input.release();
88
+ complete();
89
+ }
90
+ allowDispatch();
91
+ complete();
41
92
  };
42
93
  const abort = () => {
43
94
  let reason = new DOMException("The turn execution was aborted.", "AbortError");
44
95
  try {
45
- throwIfAgentOperationAborted(input.turnInput.signal);
96
+ throwIfAgentOperationAborted(callerSignal);
46
97
  } catch (error) {
47
98
  if (error instanceof Error) reason = error;
48
99
  }
49
100
  stop(reason);
50
101
  };
51
- input.turnInput.signal?.addEventListener("abort", abort, { once: true });
102
+ callerSignal?.addEventListener("abort", abort, { once: true });
52
103
  const track = (operation) => {
53
104
  nativeOperations.add(operation);
54
105
  void operation.then(() => nativeOperations.delete(operation), () => nativeOperations.delete(operation));
55
106
  return operation;
56
107
  };
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();
108
+ let joinPromise;
109
+ const join = () => {
110
+ if (joinPromise) return joinPromise;
111
+ const attempt = (async () => {
112
+ while (nativeOperations.size > 0 || input.inFlightOperations.size > 0) {
113
+ await Promise.allSettled([...nativeOperations, ...input.inFlightOperations]);
114
+ }
115
+ if (source?.return) await source.return();
116
+ })();
117
+ joinPromise = attempt;
118
+ void attempt.catch(() => {
119
+ if (joinPromise === attempt) joinPromise = void 0;
120
+ });
121
+ return attempt;
62
122
  };
63
123
  const requireAnswerable = () => {
64
124
  input.requireUsable();
@@ -71,7 +131,7 @@ function createValidatedAgentTurnExecution(input) {
71
131
  throwIfAgentOperationAborted(requestInput.signal);
72
132
  const resolution = parseAgentRequestResolution(requestInput.resolution);
73
133
  const pending = input.pendingRequests.get(resolution.requestId);
74
- if (!pending || pending.turnId !== input.turnInput.turnId) {
134
+ if (!pending || pending.turnId !== turnId) {
75
135
  return fail("request_resolution_mismatch", "Resolution does not identify an answerable request in this execution.");
76
136
  }
77
137
  try {
@@ -84,7 +144,7 @@ function createValidatedAgentTurnExecution(input) {
84
144
  }
85
145
  if (state.kind !== "entered") return fail("request_resolution_mismatch", "Turn execution is no longer live.");
86
146
  const candidate = state.candidate;
87
- const restore = requests.claim(resolution.requestId, pending);
147
+ const attempt = requests.claim(resolution.requestId, pending);
88
148
  let settle;
89
149
  const settlement = new Promise((resolve) => {
90
150
  settle = resolve;
@@ -97,7 +157,7 @@ function createValidatedAgentTurnExecution(input) {
97
157
  throwIfAgentOperationAborted(lifetime.signal);
98
158
  requireAnswerable();
99
159
  } catch (error) {
100
- restore();
160
+ attempt.restore();
101
161
  throw error;
102
162
  }
103
163
  let result;
@@ -105,10 +165,12 @@ function createValidatedAgentTurnExecution(input) {
105
165
  const signal = requestInput.signal === void 0 ? lifetime.signal : AbortSignal.any([lifetime.signal, requestInput.signal]);
106
166
  result = validateAgentRequestResolutionResult(await candidate.resolveRequest({ resolution, signal }), input.providerKey);
107
167
  } catch (error) {
168
+ attempt.retire();
108
169
  input.retire();
109
170
  throw new AgentProviderDelegatedOperationError(input.providerKey, "resolve_request", error);
110
171
  }
111
- if (result.status === "rejected" && result.requestState === "pending") restore();
172
+ if (result.status === "rejected" && result.requestState === "pending") attempt.restore();
173
+ else attempt.retire();
112
174
  if (result.status === "delivery_uncertain") input.retire();
113
175
  return result;
114
176
  } finally {
@@ -124,26 +186,35 @@ function createValidatedAgentTurnExecution(input) {
124
186
  let removeAbort = () => {
125
187
  };
126
188
  try {
189
+ if (state.kind === "parked") {
190
+ ready();
191
+ await dispatch;
192
+ if (currentState().kind === "settled") return { done: true, value: void 0 };
193
+ throwIfAgentOperationAborted(lifetime.signal);
194
+ if (state.kind !== "parked") throw abortError();
195
+ const candidate = state.candidate;
196
+ source = candidate.outputs[Symbol.asyncIterator]();
197
+ state = { kind: "entered", candidate, iterator: source };
198
+ }
127
199
  if (state.kind === "reserved") input.requireUsable();
128
200
  throwIfAgentOperationAborted(lifetime.signal);
129
201
  if (state.kind === "reserved") {
130
202
  try {
131
- input.turnInput.onProviderExecutionStarted?.();
203
+ if (origin.kind === "caller") origin.input.onProviderExecutionStarted?.();
132
204
  } catch (error) {
133
205
  state = { kind: "settled" };
134
206
  detachAbort();
135
207
  input.release();
208
+ complete();
136
209
  throw error;
137
210
  }
138
211
  throwIfAgentOperationAborted(lifetime.signal);
139
212
  input.requireUsable();
140
- const { onProviderExecutionStarted: _observer, ...turnInput } = input.turnInput;
213
+ if (origin.kind !== "caller") throw new Error("Only caller executions enter through startTurn.");
214
+ const { onProviderExecutionStarted: _observer, ...turnInput } = origin.input;
141
215
  state = { kind: "entering" };
142
216
  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
- }
217
+ const candidate = validateCandidate(origin.start({ ...turnInput, signal: lifetime.signal }));
147
218
  source = candidate.outputs[Symbol.asyncIterator]();
148
219
  throwIfAgentOperationAborted(lifetime.signal);
149
220
  state = { kind: "entered", candidate, iterator: source };
@@ -153,7 +224,7 @@ function createValidatedAgentTurnExecution(input) {
153
224
  }
154
225
  }
155
226
  if (state.kind !== "entered") throw abortError();
156
- const nativePull = track(Promise.resolve().then(() => state.kind === "entered" ? state.iterator.next() : Promise.reject(abortError())));
227
+ const nativePull = track(Promise.resolve().then(() => state.kind === "entered" ? state.iterator.next() : origin.kind === "native" && state.kind === "settled" ? { done: true, value: void 0 } : Promise.reject(abortError())));
157
228
  const stopped = new Promise((_, reject) => {
158
229
  const onAbort = () => reject(abortError());
159
230
  lifetime.signal.addEventListener("abort", onAbort, { once: true });
@@ -162,21 +233,29 @@ function createValidatedAgentTurnExecution(input) {
162
233
  const result = await Promise.race([nativePull, stopped]);
163
234
  throwIfAgentOperationAborted(lifetime.signal);
164
235
  if (!result.done) {
165
- const output = input.observe(result.value);
166
- if (output.kind === "event" && output.event.type === "request.opened") requests.observeRequest();
236
+ if (origin.kind === "native" && currentState().kind === "settled") throwAgentProviderContractError(input.providerKey, "invalid_native_continuation", "A no-work execution emitted native output.");
237
+ observedOutput = true;
238
+ const output = input.observe(result.value, requests);
167
239
  return { done: false, value: output };
168
240
  }
169
241
  while (input.inFlightOperations.size > 0) {
170
242
  await Promise.race([Promise.all(input.inFlightOperations), stopped]);
171
243
  }
172
244
  throwIfAgentOperationAborted(lifetime.signal);
245
+ if (origin.kind === "native" && await Promise.race([disposition, stopped]) === "not_started") return { done: true, value: void 0 };
173
246
  input.assertCompleted();
247
+ requests.withdraw();
174
248
  state = { kind: "settled" };
175
249
  detachAbort();
176
250
  input.release();
251
+ complete();
177
252
  return { done: true, value: void 0 };
178
253
  } catch (error) {
179
254
  if (state.kind === "entered" || state.kind === "entering") stop(error instanceof Error ? error : new Error("Provider output failed.", { cause: error }));
255
+ if (error instanceof AgentProviderContractError && error.code === "request_identity_limit_exceeded") {
256
+ void input.abandon().catch(() => {
257
+ });
258
+ }
180
259
  throw error;
181
260
  } finally {
182
261
  removeAbort();
@@ -188,9 +267,13 @@ function createValidatedAgentTurnExecution(input) {
188
267
  return() {
189
268
  if (returnPromise) return returnPromise;
190
269
  if (state.kind === "settled") return Promise.resolve({ done: true, value: void 0 });
191
- const entered = state.kind === "entered" || source !== void 0;
270
+ const entered = dispatched || state.kind === "entered" || source !== void 0;
192
271
  stop(new DOMException("The turn output consumer returned.", "AbortError"));
193
- returnPromise = (entered ? input.abandon() : Promise.resolve()).then(() => ({ done: true, value: void 0 }));
272
+ if (entered && origin.kind === "native") {
273
+ void input.abandon().catch(() => {
274
+ });
275
+ returnPromise = Promise.resolve({ done: true, value: void 0 });
276
+ } else returnPromise = (entered ? input.abandon() : Promise.resolve()).then(() => ({ done: true, value: void 0 }));
194
277
  return returnPromise;
195
278
  }
196
279
  };
@@ -202,13 +285,34 @@ function createValidatedAgentTurnExecution(input) {
202
285
  }
203
286
  });
204
287
  return {
205
- execution: Object.freeze({ turnId: input.turnInput.turnId, outputs, resolveRequest }),
206
- hasEntered: () => state.kind === "entering" || state.kind === "entered",
288
+ execution: Object.freeze({ turnId, outputs, resolveRequest }),
289
+ hasEntered: () => dispatched && state.kind === "parked" || state.kind === "entering" || state.kind === "entered",
207
290
  stop,
208
291
  join,
209
- suspendRequests: requests.suspend
292
+ suspendRequests: requests.suspend,
293
+ completed,
294
+ readerReady,
295
+ admitDispatch() {
296
+ if (state.kind !== "parked") throwAgentProviderContractError(input.providerKey, "invalid_native_continuation", "Prepared execution is no longer dispatchable.");
297
+ dispatched = true;
298
+ allowDispatch();
299
+ },
300
+ markStarted() {
301
+ resolveDisposition("started");
302
+ },
303
+ settleNoWork() {
304
+ if (observedOutput) {
305
+ throwAgentProviderContractError(input.providerKey, "invalid_native_continuation", "No-work outcome conflicts with entered output.");
306
+ }
307
+ state = { kind: "settled" };
308
+ resolveDisposition("not_started");
309
+ detachAbort();
310
+ allowDispatch();
311
+ complete();
312
+ }
210
313
  };
211
314
  }
212
315
  export {
316
+ adoptValidatedAgentTurnExecution,
213
317
  createValidatedAgentTurnExecution
214
318
  };
@@ -1,4 +1,5 @@
1
- import type { AgentRequest, AgentTurnId } from "@agen-ai/agent-protocol";
1
+ import type { AgentProviderKey, AgentRequest, AgentTurnId } from "@agen-ai/agent-protocol";
2
+ import type { AgentSessionAdmission } from "../sessionAdmission.js";
2
3
  export interface PendingAgentRequest {
3
4
  readonly request: AgentRequest;
4
5
  readonly turnId: AgentTurnId;
@@ -8,12 +9,23 @@ export interface SuspendedAgentTurnRequests {
8
9
  readonly withdraw: () => void;
9
10
  }
10
11
  export declare function createAgentTurnRequestAdmission(input: Readonly<{
12
+ providerKey: AgentProviderKey;
13
+ sessionAdmission: AgentSessionAdmission;
11
14
  pendingRequests: Map<string, PendingAgentRequest>;
12
15
  canRestore: () => boolean;
16
+ canRestoreRequest: (pending: PendingAgentRequest) => boolean;
13
17
  }>): {
14
- claim: (requestId: string, pending: PendingAgentRequest) => (() => void);
18
+ claim: (requestId: string, pending: PendingAgentRequest) => {
19
+ restore(): void;
20
+ retire(): void;
21
+ };
15
22
  suspend: () => SuspendedAgentTurnRequests;
16
23
  withdraw: () => void;
17
- observeRequest(): void;
24
+ open(pending: PendingAgentRequest): void;
25
+ unavailable(requestId: string): void;
26
+ unclaimedRequests(): Iterable<PendingAgentRequest>;
27
+ hasUnclaimedRequest(): boolean;
28
+ hasOpened(requestId: string): boolean;
18
29
  };
30
+ export type AgentTurnRequestAdmission = ReturnType<typeof createAgentTurnRequestAdmission>;
19
31
  //# sourceMappingURL=requests.d.ts.map
@@ -1,44 +1,79 @@
1
- function createRequestAdmissionGeneration() {
2
- return { suspensions: /* @__PURE__ */ new Set(), withheldRequests: /* @__PURE__ */ new Map() };
3
- }
1
+ import { throwAgentProviderContractError } from "../contractErrors.js";
4
2
  function createAgentTurnRequestAdmission(input) {
5
- let generation = createRequestAdmissionGeneration();
6
- const withdraw = () => {
7
- generation = createRequestAdmissionGeneration();
8
- input.pendingRequests.clear();
3
+ const requests = /* @__PURE__ */ new Map();
4
+ const invalidSequence = (message) => throwAgentProviderContractError(input.providerKey, "invalid_turn_sequence", message);
5
+ const retire = (requestId, entry) => {
6
+ entry.eligibility = { kind: "retired" };
7
+ entry.suspensions.clear();
8
+ input.pendingRequests.delete(requestId);
9
9
  };
10
- const restoreReadyRequests = (requestGeneration) => {
11
- if (requestGeneration !== generation || !input.canRestore() || requestGeneration.suspensions.size > 0 || input.pendingRequests.size > 0) return;
12
- for (const [requestId, pending] of requestGeneration.withheldRequests) {
10
+ const restoreReadyRequest = () => {
11
+ if (!input.canRestore() || input.pendingRequests.size > 0) return;
12
+ for (const [requestId, entry] of requests) {
13
+ if (entry.eligibility.kind !== "withheld" || entry.suspensions.size > 0) continue;
14
+ const pending = entry.eligibility.pending;
15
+ if (!input.canRestoreRequest(pending)) {
16
+ retire(requestId, entry);
17
+ continue;
18
+ }
19
+ entry.eligibility = { kind: "pending", pending };
13
20
  input.pendingRequests.set(requestId, pending);
21
+ return;
14
22
  }
15
- requestGeneration.withheldRequests.clear();
23
+ };
24
+ const withdraw = () => {
25
+ for (const [requestId, entry] of requests) retire(requestId, entry);
16
26
  };
17
27
  const claim = (requestId, pending) => {
18
- const claimedGeneration = generation;
28
+ const entry = requests.get(requestId);
29
+ if (!entry || entry.eligibility.kind !== "pending" || entry.eligibility.pending !== pending) {
30
+ return throwAgentProviderContractError(
31
+ input.providerKey,
32
+ "request_resolution_mismatch",
33
+ "Resolution does not identify an answerable request in this execution."
34
+ );
35
+ }
36
+ const attempt = Symbol("request response");
37
+ entry.eligibility = { kind: "claimed", pending, attempt };
19
38
  input.pendingRequests.delete(requestId);
20
- return () => {
21
- if (claimedGeneration !== generation || !input.canRestore()) return;
22
- claimedGeneration.withheldRequests.set(requestId, pending);
23
- restoreReadyRequests(claimedGeneration);
39
+ restoreReadyRequest();
40
+ return {
41
+ restore() {
42
+ if (entry.eligibility.kind !== "claimed" || entry.eligibility.attempt !== attempt || !input.canRestore()) return;
43
+ entry.eligibility = { kind: "withheld", pending };
44
+ restoreReadyRequest();
45
+ },
46
+ retire() {
47
+ if (entry.eligibility.kind !== "claimed" || entry.eligibility.attempt !== attempt) return;
48
+ retire(requestId, entry);
49
+ restoreReadyRequest();
50
+ }
24
51
  };
25
52
  };
26
53
  const suspend = () => {
27
- const suspendedGeneration = generation;
28
- const suspension = /* @__PURE__ */ Symbol("interruption");
29
- suspendedGeneration.suspensions.add(suspension);
30
- for (const [requestId, pending] of input.pendingRequests) {
31
- suspendedGeneration.withheldRequests.set(requestId, pending);
54
+ const token = Symbol("interruption");
55
+ const affected = [];
56
+ for (const [requestId, entry] of requests) {
57
+ if (entry.eligibility.kind === "retired") continue;
58
+ affected.push(entry);
59
+ entry.suspensions.add(token);
60
+ if (entry.eligibility.kind === "pending") {
61
+ entry.eligibility = { kind: "withheld", pending: entry.eligibility.pending };
62
+ input.pendingRequests.delete(requestId);
63
+ }
32
64
  }
33
- input.pendingRequests.clear();
65
+ let settled = false;
34
66
  return {
35
67
  restore() {
36
- if (!suspendedGeneration.suspensions.delete(suspension)) return;
37
- restoreReadyRequests(suspendedGeneration);
68
+ if (settled) return;
69
+ settled = true;
70
+ for (const entry of affected) entry.suspensions.delete(token);
71
+ restoreReadyRequest();
38
72
  },
39
73
  withdraw() {
40
- if (!suspendedGeneration.suspensions.delete(suspension)) return;
41
- if (suspendedGeneration === generation) withdraw();
74
+ if (settled) return;
75
+ settled = true;
76
+ withdraw();
42
77
  }
43
78
  };
44
79
  };
@@ -46,8 +81,38 @@ function createAgentTurnRequestAdmission(input) {
46
81
  claim,
47
82
  suspend,
48
83
  withdraw,
49
- observeRequest() {
50
- generation = createRequestAdmissionGeneration();
84
+ open(pending) {
85
+ if (input.pendingRequests.size > 0) invalidSequence("Provider opened more than one pending request for a session.");
86
+ const requestId = pending.request.requestId;
87
+ input.sessionAdmission.claimRequest(requestId, "invalid_turn_sequence");
88
+ requests.set(requestId, {
89
+ eligibility: { kind: "pending", pending },
90
+ canonicalRetirementObserved: false,
91
+ suspensions: /* @__PURE__ */ new Set()
92
+ });
93
+ input.pendingRequests.set(requestId, pending);
94
+ },
95
+ unavailable(requestId) {
96
+ const entry = requests.get(requestId);
97
+ if (!entry) return invalidSequence("Provider retired a request not opened in this execution.");
98
+ if (entry.canonicalRetirementObserved) return invalidSequence("Provider retired the same request more than once.");
99
+ entry.canonicalRetirementObserved = true;
100
+ retire(requestId, entry);
101
+ restoreReadyRequest();
102
+ },
103
+ *unclaimedRequests() {
104
+ for (const entry of requests.values()) {
105
+ if (entry.eligibility.kind === "pending" || entry.eligibility.kind === "withheld") yield entry.eligibility.pending;
106
+ }
107
+ },
108
+ hasUnclaimedRequest() {
109
+ for (const entry of requests.values()) {
110
+ if (entry.eligibility.kind === "pending" || entry.eligibility.kind === "withheld") return true;
111
+ }
112
+ return false;
113
+ },
114
+ hasOpened(requestId) {
115
+ return requests.has(requestId);
51
116
  }
52
117
  };
53
118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agen-ai/agent-runtime",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "private": false,
5
5
  "description": "Provider-neutral driver, instance, adapter, session, output, and conformance contracts for coding-agent runtimes.",
6
6
  "type": "module",
@@ -31,7 +31,7 @@
31
31
  }
32
32
  },
33
33
  "dependencies": {
34
- "@agen-ai/agent-protocol": "^0.4.0"
34
+ "@agen-ai/agent-protocol": "^0.4.2"
35
35
  },
36
36
  "devDependencies": {
37
37
  "tsup": "^8.5.0"