@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.
- package/README.md +288 -29
- package/dist/adapterValidation.d.ts +1 -1
- package/dist/adapterValidation.js +199 -92
- package/dist/artifacts.js +0 -1
- package/dist/contractErrors.d.ts +16 -3
- package/dist/contractErrors.js +42 -6
- package/dist/hostBindings/index.d.ts +4 -0
- package/dist/hostBindings/index.js +9 -0
- package/dist/hostBindings/lifetime.d.ts +25 -0
- package/dist/hostBindings/lifetime.js +147 -0
- package/dist/hostBindings/opening.d.ts +14 -0
- package/dist/hostBindings/opening.js +82 -0
- package/dist/hostBindings/schema.d.ts +9 -0
- package/dist/hostBindings/schema.js +169 -0
- package/dist/hostBindings/types.d.ts +71 -0
- package/dist/hostBindings/types.js +0 -0
- package/dist/hostBindings/validation.d.ts +14 -0
- package/dist/hostBindings/validation.js +107 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -0
- package/dist/internal/objectShape.d.ts +2 -0
- package/dist/internal/objectShape.js +7 -0
- package/dist/outputValidation.js +3 -2
- package/dist/providerDriver.js +0 -1
- package/dist/providerInstanceRegistry.js +2 -1
- package/dist/sessionCleanup.d.ts +9 -0
- package/dist/sessionCleanup.js +17 -0
- package/dist/sessionInteractions/index.d.ts +2 -0
- package/dist/sessionInteractions/index.js +1 -0
- package/dist/sessionInteractions/lifetime.d.ts +52 -0
- package/dist/sessionInteractions/lifetime.js +251 -0
- package/dist/sessionInteractions/request.d.ts +42 -0
- package/dist/sessionInteractions/request.js +308 -0
- package/dist/sessionInteractions/types.d.ts +54 -0
- package/dist/sessionInteractions/types.js +10 -0
- package/dist/sessionInteractions/validation.d.ts +21 -0
- package/dist/sessionInteractions/validation.js +119 -0
- package/dist/sessionValidation.d.ts +15 -2
- package/dist/sessionValidation.js +155 -318
- package/dist/sessions.d.ts +31 -4
- package/dist/testing/conformance.d.ts +3 -3
- package/dist/testing/conformance.js +24 -31
- package/dist/testing/fakeProvider.js +59 -49
- package/dist/turnExecution/execution.d.ts +26 -0
- package/dist/turnExecution/execution.js +214 -0
- package/dist/turnExecution/receipts.d.ts +6 -0
- package/dist/turnExecution/receipts.js +39 -0
- package/dist/turnExecution/requests.d.ts +19 -0
- package/dist/turnExecution/requests.js +56 -0
- package/package.json +2 -2
package/dist/sessions.d.ts
CHANGED
|
@@ -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
|
|
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<
|
|
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
|
|
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 {
|
|
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:
|
|
20
|
-
readonly interruptionTurn:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|