@agen-ai/agent-runtime 0.4.1 → 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.
- package/README.md +154 -71
- package/dist/adapterValidation.d.ts +4 -2
- package/dist/adapterValidation.js +108 -59
- package/dist/contractErrors.d.ts +1 -1
- package/dist/contractErrors.js +7 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/nativeContinuations/admission.d.ts +17 -0
- package/dist/nativeContinuations/admission.js +250 -0
- package/dist/nativeContinuations/index.d.ts +2 -0
- package/dist/nativeContinuations/index.js +1 -0
- package/dist/nativeContinuations/types.d.ts +52 -0
- package/dist/nativeContinuations/types.js +0 -0
- package/dist/outputValidation.js +1 -0
- package/dist/providerCatalog.d.ts +1 -1
- package/dist/providerDriver.d.ts +4 -1
- package/dist/providerInstanceRegistry.d.ts +4 -4
- package/dist/sessionAdmission.d.ts +12 -0
- package/dist/sessionAdmission.js +54 -0
- package/dist/sessionInteractions/lifetime.d.ts +32 -3
- package/dist/sessionInteractions/lifetime.js +145 -13
- package/dist/sessionInteractions/request.d.ts +3 -2
- package/dist/sessionInteractions/request.js +98 -25
- package/dist/sessionInteractions/types.d.ts +68 -12
- package/dist/sessionInteractions/validation.d.ts +13 -9
- package/dist/sessionInteractions/validation.js +121 -60
- package/dist/sessionValidation.d.ts +78 -2
- package/dist/sessionValidation.js +213 -162
- package/dist/sessions.d.ts +33 -2
- package/dist/testing/conformance.d.ts +3 -1
- package/dist/testing/conformance.js +180 -85
- package/dist/testing/fakeProvider.js +2 -2
- package/dist/turnExecution/execution.d.ts +23 -6
- package/dist/turnExecution/execution.js +136 -32
- package/dist/turnExecution/requests.d.ts +15 -3
- package/dist/turnExecution/requests.js +93 -28
- 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
|
-
|
|
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 = () =>
|
|
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
|
|
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(
|
|
96
|
+
throwIfAgentOperationAborted(callerSignal);
|
|
46
97
|
} catch (error) {
|
|
47
98
|
if (error instanceof Error) reason = error;
|
|
48
99
|
}
|
|
49
100
|
stop(reason);
|
|
50
101
|
};
|
|
51
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 !==
|
|
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
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
18
|
+
claim: (requestId: string, pending: PendingAgentRequest) => {
|
|
19
|
+
restore(): void;
|
|
20
|
+
retire(): void;
|
|
21
|
+
};
|
|
15
22
|
suspend: () => SuspendedAgentTurnRequests;
|
|
16
23
|
withdraw: () => void;
|
|
17
|
-
|
|
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
|
-
|
|
2
|
-
return { suspensions: /* @__PURE__ */ new Set(), withheldRequests: /* @__PURE__ */ new Map() };
|
|
3
|
-
}
|
|
1
|
+
import { throwAgentProviderContractError } from "../contractErrors.js";
|
|
4
2
|
function createAgentTurnRequestAdmission(input) {
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
11
|
-
if (
|
|
12
|
-
for (const [requestId,
|
|
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
|
-
|
|
23
|
+
};
|
|
24
|
+
const withdraw = () => {
|
|
25
|
+
for (const [requestId, entry] of requests) retire(requestId, entry);
|
|
16
26
|
};
|
|
17
27
|
const claim = (requestId, pending) => {
|
|
18
|
-
const
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
65
|
+
let settled = false;
|
|
34
66
|
return {
|
|
35
67
|
restore() {
|
|
36
|
-
if (
|
|
37
|
-
|
|
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 (
|
|
41
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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.
|
|
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.
|
|
34
|
+
"@agen-ai/agent-protocol": "^0.4.2"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"tsup": "^8.5.0"
|