@agen-ai/agent-runtime 0.2.1 → 0.2.3
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 +36 -9
- package/dist/configurationValidation.js +12 -17
- package/dist/contractErrors.d.ts +2 -2
- package/dist/contractErrors.js +6 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/interactionValidation.d.ts +23 -0
- package/dist/interactionValidation.js +238 -0
- package/dist/outputValidation.d.ts +4 -1
- package/dist/outputValidation.js +42 -4
- package/dist/sessionValidation.js +810 -64
- package/dist/sessions.d.ts +70 -4
- package/dist/testing/conformance.d.ts +5 -2
- package/dist/testing/conformance.js +161 -4
- package/dist/testing/fakeProvider.js +220 -17
- package/package.json +2 -2
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AGENT_COLLABORATION_GRAPH_LIMITS,
|
|
2
3
|
matchesAgentSessionBinding,
|
|
4
|
+
parseAgentCollaborationControlInput,
|
|
5
|
+
parseAgentCollaborationSpawnInput,
|
|
6
|
+
parseAgentConfigurationSelectionFor,
|
|
7
|
+
parseAgentGeneratedResourceId,
|
|
8
|
+
parseAgentOperationInvocation,
|
|
9
|
+
parseAgentOperationInvocationFor,
|
|
3
10
|
parseAgentRequestResolution,
|
|
4
11
|
parseAgentRequestResolutionFor,
|
|
5
12
|
parseAgentSessionBinding,
|
|
6
|
-
parseAgentSessionConfiguration,
|
|
7
13
|
parseAgentTurnId,
|
|
8
14
|
parseAgentTurnInputContent,
|
|
9
15
|
parseAgentTurnInterruptionInput,
|
|
10
16
|
parseAgentTurnRunInput
|
|
11
17
|
} from "@agen-ai/agent-protocol";
|
|
12
18
|
import {
|
|
13
|
-
|
|
14
|
-
} from "./
|
|
19
|
+
createAgentArtifactCandidate
|
|
20
|
+
} from "./artifacts.js";
|
|
21
|
+
import {
|
|
22
|
+
validateAgentCollaborationNodeForCapabilities,
|
|
23
|
+
validateAgentConfigurationCatalogForCapabilities,
|
|
24
|
+
validateAgentGeneratedResourceForCapabilities,
|
|
25
|
+
validateAgentIntegrationCatalogForCapabilities,
|
|
26
|
+
validateAgentManagedContentCatalogForCapabilities,
|
|
27
|
+
validateAgentOperationCatalogForCapabilities,
|
|
28
|
+
validateAgentOperationResultForInvocation,
|
|
29
|
+
validateAgentOperationResultTransition
|
|
30
|
+
} from "./interactionValidation.js";
|
|
15
31
|
import {
|
|
16
32
|
AgentProviderDelegatedOperationError,
|
|
17
33
|
throwAgentProviderContractError
|
|
@@ -24,15 +40,59 @@ import {
|
|
|
24
40
|
validateAgentProviderOutputForContext
|
|
25
41
|
} from "./outputValidation.js";
|
|
26
42
|
import { validateAgentTurnSteeringResult } from "./steeringValidation.js";
|
|
43
|
+
function hasExactOwnKeys(candidate, expectedKeys) {
|
|
44
|
+
const ownKeys = Reflect.ownKeys(candidate);
|
|
45
|
+
if (ownKeys.some((key) => typeof key !== "string")) return false;
|
|
46
|
+
const actualKeys = ownKeys.sort();
|
|
47
|
+
const canonicalExpectedKeys = [...expectedKeys].sort();
|
|
48
|
+
return actualKeys.length === canonicalExpectedKeys.length && actualKeys.every((key, index) => key === canonicalExpectedKeys[index]);
|
|
49
|
+
}
|
|
27
50
|
function validateSessionPorts(capabilities, session) {
|
|
28
|
-
if (
|
|
51
|
+
if (!hasExactOwnKeys(session, [
|
|
52
|
+
"binding",
|
|
53
|
+
"runTurn",
|
|
54
|
+
"resolveRequest",
|
|
55
|
+
"interruption",
|
|
56
|
+
"steering",
|
|
57
|
+
"configuration",
|
|
58
|
+
"operations",
|
|
59
|
+
"managedContent",
|
|
60
|
+
"integrations",
|
|
61
|
+
"collaboration",
|
|
62
|
+
"generatedResources",
|
|
63
|
+
"close"
|
|
64
|
+
]) || 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) || session.operations === null || typeof session.operations !== "object" || !["supported", "unsupported"].includes(session.operations.kind) || session.managedContent === null || typeof session.managedContent !== "object" || !["supported", "unsupported"].includes(session.managedContent.kind) || session.integrations === null || typeof session.integrations !== "object" || !["supported", "unsupported"].includes(session.integrations.kind) || session.collaboration === null || typeof session.collaboration !== "object" || !["supported", "unsupported"].includes(session.collaboration.kind) || session.generatedResources === null || typeof session.generatedResources !== "object" || !["supported", "unsupported"].includes(session.generatedResources.kind)) {
|
|
29
65
|
throwAgentProviderContractError(
|
|
30
66
|
capabilities.providerKey,
|
|
31
67
|
"invalid_session",
|
|
32
68
|
`Provider ${capabilities.providerKey} returned an incomplete session.`
|
|
33
69
|
);
|
|
34
70
|
}
|
|
35
|
-
if (
|
|
71
|
+
if (!hasExactOwnKeys(
|
|
72
|
+
session.interruption,
|
|
73
|
+
session.interruption.kind === "supported" ? ["kind", "interruptTurn"] : ["kind"]
|
|
74
|
+
) || !hasExactOwnKeys(
|
|
75
|
+
session.steering,
|
|
76
|
+
session.steering.kind === "supported" ? ["kind", "steerTurn"] : ["kind"]
|
|
77
|
+
) || !hasExactOwnKeys(
|
|
78
|
+
session.configuration,
|
|
79
|
+
session.configuration.kind === "selectable" ? ["kind", "listConfiguration", "applyConfigurationSelection"] : ["kind"]
|
|
80
|
+
) || !hasExactOwnKeys(
|
|
81
|
+
session.operations,
|
|
82
|
+
session.operations.kind === "supported" ? ["kind", "listOperations", "invokeOperation"] : ["kind"]
|
|
83
|
+
) || !hasExactOwnKeys(
|
|
84
|
+
session.managedContent,
|
|
85
|
+
session.managedContent.kind === "supported" ? ["kind", "listManagedContent"] : ["kind"]
|
|
86
|
+
) || !hasExactOwnKeys(
|
|
87
|
+
session.integrations,
|
|
88
|
+
session.integrations.kind === "supported" ? ["kind", "observeIntegrations"] : ["kind"]
|
|
89
|
+
) || !hasExactOwnKeys(
|
|
90
|
+
session.collaboration,
|
|
91
|
+
session.collaboration.kind === "supported" ? ["kind", "spawnCollaboration", "controlCollaboration"] : ["kind"]
|
|
92
|
+
) || !hasExactOwnKeys(
|
|
93
|
+
session.generatedResources,
|
|
94
|
+
session.generatedResources.kind === "supported" ? ["kind", "getGeneratedResource"] : ["kind"]
|
|
95
|
+
) || 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.listConfiguration !== "function" || typeof session.configuration.applyConfigurationSelection !== "function") || session.operations.kind === "supported" !== (capabilities.operations.kind === "supported") || session.operations.kind === "supported" && (typeof session.operations.listOperations !== "function" || typeof session.operations.invokeOperation !== "function") || session.managedContent.kind === "supported" !== (capabilities.managedContent.kind === "supported") || session.managedContent.kind === "supported" && typeof session.managedContent.listManagedContent !== "function" || session.integrations.kind === "supported" !== (capabilities.integrations.kind === "supported") || session.integrations.kind === "supported" && typeof session.integrations.observeIntegrations !== "function" || session.collaboration.kind === "supported" !== (capabilities.collaboration.kind === "supported") || session.collaboration.kind === "supported" && (typeof session.collaboration.spawnCollaboration !== "function" || typeof session.collaboration.controlCollaboration !== "function") || session.generatedResources.kind === "supported" !== (capabilities.generatedResources.kind === "supported") || session.generatedResources.kind === "supported" && typeof session.generatedResources.getGeneratedResource !== "function") {
|
|
36
96
|
throwAgentProviderContractError(
|
|
37
97
|
capabilities.providerKey,
|
|
38
98
|
"capability_port_mismatch",
|
|
@@ -40,6 +100,58 @@ function validateSessionPorts(capabilities, session) {
|
|
|
40
100
|
);
|
|
41
101
|
}
|
|
42
102
|
}
|
|
103
|
+
const TERMINAL_COLLABORATION_STATUSES = /* @__PURE__ */ new Set([
|
|
104
|
+
"completed",
|
|
105
|
+
"failed",
|
|
106
|
+
"canceled"
|
|
107
|
+
]);
|
|
108
|
+
function evaluateCollaborationGraphAdmission(input) {
|
|
109
|
+
const existing = input.nodes.get(input.candidate.collaborationId);
|
|
110
|
+
if (existing !== void 0) {
|
|
111
|
+
const parent2 = existing.parentCollaborationId === void 0 ? void 0 : input.nodes.get(existing.parentCollaborationId);
|
|
112
|
+
return parent2 === void 0 ? Object.freeze({ kind: "admitted" }) : Object.freeze({ kind: "admitted", parent: parent2 });
|
|
113
|
+
}
|
|
114
|
+
const reservation = input.reservations.get(input.candidate.collaborationId);
|
|
115
|
+
if (reservation !== void 0 && reservation.parentCollaborationId !== input.candidate.parentCollaborationId) {
|
|
116
|
+
return Object.freeze({ kind: "conflict" });
|
|
117
|
+
}
|
|
118
|
+
const parent = input.candidate.parentCollaborationId === void 0 ? void 0 : input.nodes.get(input.candidate.parentCollaborationId);
|
|
119
|
+
if (input.candidate.parentCollaborationId === void 0 && input.candidate.rootCollaborationId !== input.candidate.collaborationId || input.candidate.parentCollaborationId !== void 0 && (parent === void 0 || input.candidate.rootCollaborationId !== parent.rootCollaborationId || TERMINAL_COLLABORATION_STATUSES.has(parent.status) || parent.closedAt !== void 0)) {
|
|
120
|
+
return Object.freeze({ kind: "conflict" });
|
|
121
|
+
}
|
|
122
|
+
let depth = 1;
|
|
123
|
+
const visitedIds = /* @__PURE__ */ new Set([input.candidate.collaborationId]);
|
|
124
|
+
for (let ancestor = parent; ancestor !== void 0; ) {
|
|
125
|
+
if (visitedIds.has(ancestor.collaborationId)) {
|
|
126
|
+
return Object.freeze({ kind: "conflict" });
|
|
127
|
+
}
|
|
128
|
+
visitedIds.add(ancestor.collaborationId);
|
|
129
|
+
depth += 1;
|
|
130
|
+
if (ancestor.parentCollaborationId === void 0) break;
|
|
131
|
+
ancestor = input.nodes.get(ancestor.parentCollaborationId);
|
|
132
|
+
if (ancestor === void 0) {
|
|
133
|
+
return Object.freeze({ kind: "conflict" });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (reservation !== void 0) {
|
|
137
|
+
return parent === void 0 ? Object.freeze({ kind: "admitted" }) : Object.freeze({ kind: "admitted", parent });
|
|
138
|
+
}
|
|
139
|
+
const unmaterializedReservations = [...input.reservations].filter(
|
|
140
|
+
([collaborationId]) => !input.nodes.has(collaborationId)
|
|
141
|
+
);
|
|
142
|
+
const childCount = parent === void 0 ? 0 : [...input.nodes.values()].filter(
|
|
143
|
+
(node) => node.parentCollaborationId === parent.collaborationId
|
|
144
|
+
).length + unmaterializedReservations.filter(
|
|
145
|
+
([, retainedReservation]) => retainedReservation.parentCollaborationId === parent.collaborationId
|
|
146
|
+
).length;
|
|
147
|
+
const activeCount = [...input.nodes.values()].filter(
|
|
148
|
+
(node) => !TERMINAL_COLLABORATION_STATUSES.has(node.status)
|
|
149
|
+
).length + unmaterializedReservations.length;
|
|
150
|
+
if (input.nodes.size + unmaterializedReservations.length >= AGENT_COLLABORATION_GRAPH_LIMITS.maxNodes || childCount >= input.capability.maxChildrenPerNode || depth > input.capability.maxDepth || input.candidate.active && activeCount >= input.capability.maxActiveNodes) {
|
|
151
|
+
return Object.freeze({ kind: "capacity_exceeded" });
|
|
152
|
+
}
|
|
153
|
+
return parent === void 0 ? Object.freeze({ kind: "admitted" }) : Object.freeze({ kind: "admitted", parent });
|
|
154
|
+
}
|
|
43
155
|
function invalidTurnSequence(providerKey, message) {
|
|
44
156
|
return throwAgentProviderContractError(
|
|
45
157
|
providerKey,
|
|
@@ -59,13 +171,61 @@ function completeTurnSequence(input) {
|
|
|
59
171
|
}
|
|
60
172
|
const FINAL_DIFF_TRAILING_EVENT_TYPES = [
|
|
61
173
|
"artifact.referenced",
|
|
174
|
+
"collaboration.updated",
|
|
175
|
+
"operation.updated",
|
|
62
176
|
"provider.diagnostic",
|
|
177
|
+
"resource.updated",
|
|
63
178
|
"runtime.error",
|
|
64
179
|
"runtime.warning",
|
|
65
180
|
"turn.completed"
|
|
66
181
|
];
|
|
182
|
+
const TERMINAL_ITEM_STATUSES = /* @__PURE__ */ new Set([
|
|
183
|
+
"completed",
|
|
184
|
+
"failed",
|
|
185
|
+
"canceled"
|
|
186
|
+
]);
|
|
187
|
+
function observeItemLifecycle(input) {
|
|
188
|
+
const { event, providerKey, state } = input;
|
|
189
|
+
const previous = state.observedItems.get(event.payload.itemId);
|
|
190
|
+
if (event.type === "item.completed" && !TERMINAL_ITEM_STATUSES.has(event.payload.status)) {
|
|
191
|
+
invalidTurnSequence(
|
|
192
|
+
providerKey,
|
|
193
|
+
"Provider completed an item without a terminal item status."
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
if (previous?.terminal) {
|
|
197
|
+
invalidTurnSequence(
|
|
198
|
+
providerKey,
|
|
199
|
+
"Provider emitted an item lifecycle event after the item became terminal."
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (previous?.itemKind !== void 0 && previous.itemKind !== event.payload.itemKind) {
|
|
203
|
+
invalidTurnSequence(
|
|
204
|
+
providerKey,
|
|
205
|
+
"Provider changed the kind of an existing item identity."
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (previous !== void 0 && event.type === "item.started") {
|
|
209
|
+
invalidTurnSequence(
|
|
210
|
+
providerKey,
|
|
211
|
+
"Provider started an item identity more than once."
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
if (previous?.inProgressObserved && event.payload.status === "pending") {
|
|
215
|
+
invalidTurnSequence(
|
|
216
|
+
providerKey,
|
|
217
|
+
"Provider regressed an in-progress item to pending."
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
state.observedItems.set(event.payload.itemId, {
|
|
221
|
+
itemKind: event.payload.itemKind,
|
|
222
|
+
status: event.payload.status,
|
|
223
|
+
inProgressObserved: previous?.inProgressObserved === true || event.payload.status === "in_progress",
|
|
224
|
+
terminal: event.type === "item.completed" || TERMINAL_ITEM_STATUSES.has(event.payload.status)
|
|
225
|
+
});
|
|
226
|
+
}
|
|
67
227
|
function assertApprovalSubjectCorrelation(input) {
|
|
68
|
-
const request = input
|
|
228
|
+
const { request } = input;
|
|
69
229
|
if (request.requestKind !== "approval") return;
|
|
70
230
|
if (request.subject.kind === "plan") {
|
|
71
231
|
if (input.state.proposedPlans.get(request.subject.artifactId) !== request.requestId) {
|
|
@@ -76,14 +236,24 @@ function assertApprovalSubjectCorrelation(input) {
|
|
|
76
236
|
}
|
|
77
237
|
return;
|
|
78
238
|
}
|
|
79
|
-
const
|
|
80
|
-
|
|
239
|
+
const item = input.state.observedItems.get(request.subject.itemId);
|
|
240
|
+
const itemIsLive = item !== void 0 && !item.terminal && (item.status === "pending" || item.status === "in_progress" || item.status === "unknown" && item.inProgressObserved);
|
|
241
|
+
if (!itemIsLive) {
|
|
81
242
|
invalidTurnSequence(
|
|
82
243
|
input.providerKey,
|
|
83
244
|
"Provider approval request does not identify a live item from this turn."
|
|
84
245
|
);
|
|
85
246
|
}
|
|
86
247
|
}
|
|
248
|
+
function assertPendingApprovalSubjectCorrelations(input) {
|
|
249
|
+
for (const pending of input.pendingRequests.values()) {
|
|
250
|
+
assertApprovalSubjectCorrelation({
|
|
251
|
+
providerKey: input.providerKey,
|
|
252
|
+
request: pending.request,
|
|
253
|
+
state: input.state
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
87
257
|
function observeContextUsage(input) {
|
|
88
258
|
const previous = input.state.latestByScope.get(input.usage.measurementScope);
|
|
89
259
|
if (previous !== void 0) {
|
|
@@ -123,6 +293,11 @@ function observeContextUsage(input) {
|
|
|
123
293
|
input.state.latestByScope.set(input.usage.measurementScope, input.usage);
|
|
124
294
|
input.state.compactionReadyScopes.delete(input.usage.measurementScope);
|
|
125
295
|
}
|
|
296
|
+
function observeContextMaterializationBoundary(state) {
|
|
297
|
+
state.latestByScope.delete("materialization");
|
|
298
|
+
state.cumulativeByScope.delete("materialization");
|
|
299
|
+
state.compactionReadyScopes.delete("materialization");
|
|
300
|
+
}
|
|
126
301
|
function observeTurnEvent(input) {
|
|
127
302
|
const {
|
|
128
303
|
contextUsageState,
|
|
@@ -175,7 +350,12 @@ function observeTurnEvent(input) {
|
|
|
175
350
|
}
|
|
176
351
|
}
|
|
177
352
|
if (event.type === "item.started" || event.type === "item.updated" || event.type === "item.completed") {
|
|
178
|
-
|
|
353
|
+
observeItemLifecycle({ providerKey, event, state });
|
|
354
|
+
assertPendingApprovalSubjectCorrelations({
|
|
355
|
+
providerKey,
|
|
356
|
+
pendingRequests,
|
|
357
|
+
state
|
|
358
|
+
});
|
|
179
359
|
if (event.payload.itemKind === "context_compaction" && event.type === "item.completed" && event.payload.status === "completed") {
|
|
180
360
|
for (const scope of ["session", "materialization"]) {
|
|
181
361
|
contextUsageState.compactionReadyScopes.add(scope);
|
|
@@ -184,6 +364,11 @@ function observeTurnEvent(input) {
|
|
|
184
364
|
}
|
|
185
365
|
if (event.type === "turn.plan.proposed") {
|
|
186
366
|
state.proposedPlans.set(event.payload.artifactId, event.payload.requestId);
|
|
367
|
+
assertPendingApprovalSubjectCorrelations({
|
|
368
|
+
providerKey,
|
|
369
|
+
pendingRequests,
|
|
370
|
+
state
|
|
371
|
+
});
|
|
187
372
|
}
|
|
188
373
|
if (event.type === "context.usage.updated") {
|
|
189
374
|
observeContextUsage({
|
|
@@ -206,7 +391,11 @@ function observeTurnEvent(input) {
|
|
|
206
391
|
"Provider reused a request ID within a session."
|
|
207
392
|
);
|
|
208
393
|
}
|
|
209
|
-
assertApprovalSubjectCorrelation({
|
|
394
|
+
assertApprovalSubjectCorrelation({
|
|
395
|
+
providerKey,
|
|
396
|
+
request: event.payload.request,
|
|
397
|
+
state
|
|
398
|
+
});
|
|
210
399
|
openedRequestIds.add(requestId);
|
|
211
400
|
pendingRequests.set(requestId, {
|
|
212
401
|
request: event.payload.request,
|
|
@@ -226,6 +415,98 @@ function observeTurnEvent(input) {
|
|
|
226
415
|
completeTurnSequence({ providerKey, state, pendingRequests });
|
|
227
416
|
}
|
|
228
417
|
}
|
|
418
|
+
function observeTurnOutput(input) {
|
|
419
|
+
if (input.output.kind === "lifecycle" && input.output.lifecycle.type === "process.started") {
|
|
420
|
+
observeContextMaterializationBoundary(input.contextUsageState);
|
|
421
|
+
}
|
|
422
|
+
if (input.output.kind !== "event") return;
|
|
423
|
+
if (input.output.event.type === "operation.updated") {
|
|
424
|
+
const result = validateAgentOperationResultTransition({
|
|
425
|
+
providerKey: input.providerKey,
|
|
426
|
+
candidate: input.output.event.payload.result,
|
|
427
|
+
previous: input.interactionState.operationResults.get(
|
|
428
|
+
input.output.event.payload.result.invocationId
|
|
429
|
+
)
|
|
430
|
+
});
|
|
431
|
+
input.interactionState.operationResults.set(result.invocationId, result);
|
|
432
|
+
const invocation = input.interactionState.operationInvocations.get(
|
|
433
|
+
result.invocationId
|
|
434
|
+
);
|
|
435
|
+
if (invocation !== void 0) invocation.result = result;
|
|
436
|
+
}
|
|
437
|
+
if (input.output.event.type === "collaboration.updated") {
|
|
438
|
+
const node = validateAgentCollaborationNodeForCapabilities({
|
|
439
|
+
capabilities: input.capabilities,
|
|
440
|
+
candidate: input.output.event.payload.node,
|
|
441
|
+
previous: input.interactionState.collaborationNodes.get(
|
|
442
|
+
input.output.event.payload.node.collaborationId
|
|
443
|
+
)
|
|
444
|
+
});
|
|
445
|
+
const capability = input.capabilities.collaboration;
|
|
446
|
+
if (capability.kind !== "supported") {
|
|
447
|
+
throwAgentProviderContractError(
|
|
448
|
+
input.providerKey,
|
|
449
|
+
"output_capability_mismatch",
|
|
450
|
+
"Provider emitted collaboration state without declaring support."
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
const admission = evaluateCollaborationGraphAdmission({
|
|
454
|
+
capability,
|
|
455
|
+
candidate: {
|
|
456
|
+
collaborationId: node.collaborationId,
|
|
457
|
+
rootCollaborationId: node.rootCollaborationId,
|
|
458
|
+
...node.parentCollaborationId === void 0 ? {} : { parentCollaborationId: node.parentCollaborationId },
|
|
459
|
+
active: !TERMINAL_COLLABORATION_STATUSES.has(node.status)
|
|
460
|
+
},
|
|
461
|
+
nodes: input.interactionState.collaborationNodes,
|
|
462
|
+
reservations: input.interactionState.collaborationSpawnReservations
|
|
463
|
+
});
|
|
464
|
+
if (admission.kind === "conflict") {
|
|
465
|
+
throwAgentProviderContractError(
|
|
466
|
+
input.providerKey,
|
|
467
|
+
"output_collaboration_mismatch",
|
|
468
|
+
"Provider emitted a collaboration node outside the canonical graph."
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
if (admission.kind === "capacity_exceeded") {
|
|
472
|
+
throwAgentProviderContractError(
|
|
473
|
+
input.providerKey,
|
|
474
|
+
"output_capability_mismatch",
|
|
475
|
+
"Provider emitted a collaboration graph outside its declared limits."
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
input.interactionState.collaborationNodes.set(node.collaborationId, node);
|
|
479
|
+
}
|
|
480
|
+
if (input.output.event.type === "resource.updated") {
|
|
481
|
+
const capability = input.capabilities.generatedResources;
|
|
482
|
+
const resourceId = input.output.event.payload.resource.resourceId;
|
|
483
|
+
if (capability.kind !== "supported" || !input.state.generatedResourceIds.has(resourceId) && input.state.generatedResourceIds.size >= capability.maxResourcesPerTurn) {
|
|
484
|
+
throwAgentProviderContractError(
|
|
485
|
+
input.providerKey,
|
|
486
|
+
"output_capability_mismatch",
|
|
487
|
+
"Provider exceeded its declared generated-resource count for the turn."
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
const resource = validateAgentGeneratedResourceForCapabilities({
|
|
491
|
+
capabilities: input.capabilities,
|
|
492
|
+
candidate: input.output.event.payload.resource,
|
|
493
|
+
expectedResourceId: input.output.event.payload.resource.resourceId,
|
|
494
|
+
previous: input.interactionState.generatedResources.get(
|
|
495
|
+
input.output.event.payload.resource.resourceId
|
|
496
|
+
)
|
|
497
|
+
});
|
|
498
|
+
input.state.generatedResourceIds.add(resource.resourceId);
|
|
499
|
+
input.interactionState.generatedResources.set(resource.resourceId, resource);
|
|
500
|
+
}
|
|
501
|
+
observeTurnEvent({
|
|
502
|
+
providerKey: input.providerKey,
|
|
503
|
+
event: input.output.event,
|
|
504
|
+
state: input.state,
|
|
505
|
+
pendingRequests: input.pendingRequests,
|
|
506
|
+
openedRequestIds: input.openedRequestIds,
|
|
507
|
+
contextUsageState: input.contextUsageState
|
|
508
|
+
});
|
|
509
|
+
}
|
|
229
510
|
function assertStableTurnBoundary(input) {
|
|
230
511
|
const terminal = input.state.terminal && !input.state.waiting;
|
|
231
512
|
const waiting = !input.state.terminal && input.state.waiting && input.pendingRequests.size === 1;
|
|
@@ -261,14 +542,15 @@ function interruptionTerminalizesTurn(input) {
|
|
|
261
542
|
}
|
|
262
543
|
function observeTurnOperationOutputs(input) {
|
|
263
544
|
for (const output of input.outputs ?? []) {
|
|
264
|
-
|
|
265
|
-
observeTurnEvent({
|
|
545
|
+
observeTurnOutput({
|
|
266
546
|
providerKey: input.providerKey,
|
|
267
|
-
|
|
547
|
+
output,
|
|
268
548
|
state: input.state,
|
|
269
549
|
pendingRequests: input.pendingRequests,
|
|
270
550
|
openedRequestIds: input.openedRequestIds,
|
|
271
|
-
contextUsageState: input.contextUsageState
|
|
551
|
+
contextUsageState: input.contextUsageState,
|
|
552
|
+
capabilities: input.capabilities,
|
|
553
|
+
interactionState: input.interactionState
|
|
272
554
|
});
|
|
273
555
|
}
|
|
274
556
|
}
|
|
@@ -278,17 +560,9 @@ function reconstructWaitingTurn(input) {
|
|
|
278
560
|
([, pending]) => pending.turnId === input.turnId
|
|
279
561
|
)
|
|
280
562
|
);
|
|
281
|
-
if (targetedPendingRequests.size === 0) return null;
|
|
563
|
+
if (targetedPendingRequests.size === 0 || input.state === void 0 || input.state.terminal || !input.state.waiting) return null;
|
|
282
564
|
return {
|
|
283
|
-
state:
|
|
284
|
-
fileChangeMode: input.fileChangeMode,
|
|
285
|
-
observedItems: /* @__PURE__ */ new Map(),
|
|
286
|
-
proposedPlans: /* @__PURE__ */ new Map(),
|
|
287
|
-
started: true,
|
|
288
|
-
terminal: false,
|
|
289
|
-
waiting: true,
|
|
290
|
-
finalDiffObserved: false
|
|
291
|
-
},
|
|
565
|
+
state: input.state,
|
|
292
566
|
pendingRequests: targetedPendingRequests
|
|
293
567
|
};
|
|
294
568
|
}
|
|
@@ -376,12 +650,22 @@ function validateAgentProviderSession(input) {
|
|
|
376
650
|
}
|
|
377
651
|
validateSessionPorts(capabilities, input.candidate);
|
|
378
652
|
const pendingRequests = /* @__PURE__ */ new Map();
|
|
653
|
+
const waitingTurnStates = /* @__PURE__ */ new Map();
|
|
379
654
|
const openedRequestIds = /* @__PURE__ */ new Set();
|
|
380
655
|
const contextUsageState = {
|
|
381
656
|
latestByScope: /* @__PURE__ */ new Map(),
|
|
382
657
|
cumulativeByScope: /* @__PURE__ */ new Map(),
|
|
383
658
|
compactionReadyScopes: /* @__PURE__ */ new Set()
|
|
384
659
|
};
|
|
660
|
+
const interactionState = {
|
|
661
|
+
operationResults: /* @__PURE__ */ new Map(),
|
|
662
|
+
operationInvocations: /* @__PURE__ */ new Map(),
|
|
663
|
+
collaborationNodes: /* @__PURE__ */ new Map(),
|
|
664
|
+
collaborationSpawnReservations: /* @__PURE__ */ new Map(),
|
|
665
|
+
generatedResources: /* @__PURE__ */ new Map()
|
|
666
|
+
};
|
|
667
|
+
const collaborationNodes = interactionState.collaborationNodes;
|
|
668
|
+
const collaborationSpawnReservations = interactionState.collaborationSpawnReservations;
|
|
385
669
|
let activeTurn = null;
|
|
386
670
|
let closePromise = null;
|
|
387
671
|
let closed = false;
|
|
@@ -421,6 +705,39 @@ function validateAgentProviderSession(input) {
|
|
|
421
705
|
throw error;
|
|
422
706
|
}
|
|
423
707
|
};
|
|
708
|
+
const delegateProviderMutation = async (input2) => {
|
|
709
|
+
let executionStarted = false;
|
|
710
|
+
const onProviderExecutionStarted = () => {
|
|
711
|
+
if (executionStarted) {
|
|
712
|
+
throwAgentProviderContractError(
|
|
713
|
+
providerKey,
|
|
714
|
+
"invalid_operation_result",
|
|
715
|
+
"Provider reported operation execution more than once."
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
input2.observer?.();
|
|
719
|
+
executionStarted = true;
|
|
720
|
+
};
|
|
721
|
+
try {
|
|
722
|
+
const candidate = await input2.invoke(onProviderExecutionStarted);
|
|
723
|
+
if (!executionStarted) {
|
|
724
|
+
throwAgentProviderContractError(
|
|
725
|
+
providerKey,
|
|
726
|
+
"invalid_operation_result",
|
|
727
|
+
"Provider completed an operation without reporting execution start."
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
return input2.validate(candidate);
|
|
731
|
+
} catch (error) {
|
|
732
|
+
if (!executionStarted) throw error;
|
|
733
|
+
unusable = true;
|
|
734
|
+
throw new AgentProviderDelegatedOperationError(
|
|
735
|
+
providerKey,
|
|
736
|
+
input2.operation,
|
|
737
|
+
error
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
};
|
|
424
741
|
const runTurn = async function* (turnInput) {
|
|
425
742
|
requireUsable();
|
|
426
743
|
throwIfAgentOperationAborted(turnInput.signal);
|
|
@@ -465,6 +782,7 @@ function validateAgentProviderSession(input) {
|
|
|
465
782
|
fileChangeMode: capabilities.output.fileChanges,
|
|
466
783
|
observedItems: /* @__PURE__ */ new Map(),
|
|
467
784
|
proposedPlans: /* @__PURE__ */ new Map(),
|
|
785
|
+
generatedResourceIds: /* @__PURE__ */ new Set(),
|
|
468
786
|
started: false,
|
|
469
787
|
terminal: false,
|
|
470
788
|
waiting: false,
|
|
@@ -483,16 +801,16 @@ function validateAgentProviderSession(input) {
|
|
|
483
801
|
candidate,
|
|
484
802
|
outputContext(parsedInput.turnId)
|
|
485
803
|
);
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
804
|
+
observeTurnOutput({
|
|
805
|
+
capabilities,
|
|
806
|
+
providerKey,
|
|
807
|
+
output,
|
|
808
|
+
state: currentTurn.state,
|
|
809
|
+
pendingRequests: currentTurn.pendingRequests,
|
|
810
|
+
openedRequestIds,
|
|
811
|
+
contextUsageState,
|
|
812
|
+
interactionState
|
|
813
|
+
});
|
|
496
814
|
yield output;
|
|
497
815
|
}
|
|
498
816
|
while (currentTurn.inFlightOperations.size > 0) {
|
|
@@ -511,6 +829,9 @@ function validateAgentProviderSession(input) {
|
|
|
511
829
|
});
|
|
512
830
|
if (currentTurn.state.waiting) {
|
|
513
831
|
replacePendingRequests(pendingRequests, currentTurn.pendingRequests);
|
|
832
|
+
waitingTurnStates.set(currentTurn.turnId, currentTurn.state);
|
|
833
|
+
} else {
|
|
834
|
+
waitingTurnStates.delete(currentTurn.turnId);
|
|
514
835
|
}
|
|
515
836
|
reachedStableBoundary = true;
|
|
516
837
|
} finally {
|
|
@@ -555,18 +876,19 @@ function validateAgentProviderSession(input) {
|
|
|
555
876
|
"Provider session already has an active turn."
|
|
556
877
|
);
|
|
557
878
|
}
|
|
879
|
+
const waitingTurnState = waitingTurnStates.get(pending.turnId);
|
|
880
|
+
if (waitingTurnState === void 0 || waitingTurnState.terminal || !waitingTurnState.waiting) {
|
|
881
|
+
throwAgentProviderContractError(
|
|
882
|
+
providerKey,
|
|
883
|
+
"request_resolution_mismatch",
|
|
884
|
+
"Provider request resolution does not identify a waiting turn."
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
waitingTurnState.waiting = false;
|
|
558
888
|
const currentTurn = {
|
|
559
889
|
turnId: pending.turnId,
|
|
560
890
|
pendingRequests: nextPendingRequests,
|
|
561
|
-
state:
|
|
562
|
-
fileChangeMode: capabilities.output.fileChanges,
|
|
563
|
-
observedItems: /* @__PURE__ */ new Map(),
|
|
564
|
-
proposedPlans: /* @__PURE__ */ new Map(),
|
|
565
|
-
started: true,
|
|
566
|
-
terminal: false,
|
|
567
|
-
waiting: false,
|
|
568
|
-
finalDiffObserved: false
|
|
569
|
-
},
|
|
891
|
+
state: waitingTurnState,
|
|
570
892
|
inFlightOperations: /* @__PURE__ */ new Set()
|
|
571
893
|
};
|
|
572
894
|
let reachedStableBoundary = false;
|
|
@@ -580,16 +902,16 @@ function validateAgentProviderSession(input) {
|
|
|
580
902
|
candidate,
|
|
581
903
|
outputContext(pending.turnId)
|
|
582
904
|
);
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
905
|
+
observeTurnOutput({
|
|
906
|
+
capabilities,
|
|
907
|
+
providerKey,
|
|
908
|
+
output,
|
|
909
|
+
state: currentTurn.state,
|
|
910
|
+
pendingRequests: currentTurn.pendingRequests,
|
|
911
|
+
openedRequestIds,
|
|
912
|
+
contextUsageState,
|
|
913
|
+
interactionState
|
|
914
|
+
});
|
|
593
915
|
yield output;
|
|
594
916
|
}
|
|
595
917
|
while (currentTurn.inFlightOperations.size > 0) {
|
|
@@ -601,6 +923,11 @@ function validateAgentProviderSession(input) {
|
|
|
601
923
|
pendingRequests: currentTurn.pendingRequests
|
|
602
924
|
});
|
|
603
925
|
replacePendingRequests(pendingRequests, currentTurn.pendingRequests);
|
|
926
|
+
if (currentTurn.state.waiting) {
|
|
927
|
+
waitingTurnStates.set(currentTurn.turnId, currentTurn.state);
|
|
928
|
+
} else {
|
|
929
|
+
waitingTurnStates.delete(currentTurn.turnId);
|
|
930
|
+
}
|
|
604
931
|
reachedStableBoundary = true;
|
|
605
932
|
} finally {
|
|
606
933
|
if (!reachedStableBoundary) unusable = true;
|
|
@@ -622,7 +949,7 @@ function validateAgentProviderSession(input) {
|
|
|
622
949
|
const targetedWaitingTurn = targetedActiveTurn === null ? reconstructWaitingTurn({
|
|
623
950
|
pendingRequests,
|
|
624
951
|
turnId: parsed.turnId,
|
|
625
|
-
|
|
952
|
+
state: waitingTurnStates.get(parsed.turnId)
|
|
626
953
|
}) : null;
|
|
627
954
|
const targetedTurn = targetedActiveTurn ?? targetedWaitingTurn;
|
|
628
955
|
if (targetedTurn === null) {
|
|
@@ -654,15 +981,18 @@ function validateAgentProviderSession(input) {
|
|
|
654
981
|
parsed.turnId
|
|
655
982
|
);
|
|
656
983
|
targetedTurn.state.waiting = false;
|
|
984
|
+
waitingTurnStates.delete(parsed.turnId);
|
|
657
985
|
}
|
|
658
986
|
try {
|
|
659
987
|
observeTurnOperationOutputs({
|
|
988
|
+
capabilities,
|
|
660
989
|
providerKey,
|
|
661
990
|
outputs: result.outputs,
|
|
662
991
|
state: targetedTurn.state,
|
|
663
992
|
pendingRequests: targetedTurn.pendingRequests,
|
|
664
993
|
openedRequestIds,
|
|
665
|
-
contextUsageState
|
|
994
|
+
contextUsageState,
|
|
995
|
+
interactionState
|
|
666
996
|
});
|
|
667
997
|
if (terminalized && !targetedTurn.state.terminal) {
|
|
668
998
|
completeTurnSequence({
|
|
@@ -735,22 +1065,433 @@ function validateAgentProviderSession(input) {
|
|
|
735
1065
|
const declaredConfiguration = input.candidate.configuration;
|
|
736
1066
|
const configuration = declaredConfiguration.kind === "managed" ? Object.freeze({ kind: "managed" }) : Object.freeze({
|
|
737
1067
|
kind: "selectable",
|
|
738
|
-
|
|
1068
|
+
listConfiguration: async (listInput = {}) => {
|
|
739
1069
|
requireUsable();
|
|
740
|
-
throwIfAgentOperationAborted(
|
|
741
|
-
|
|
742
|
-
|
|
1070
|
+
throwIfAgentOperationAborted(listInput.signal);
|
|
1071
|
+
return validateAgentConfigurationCatalogForCapabilities(
|
|
1072
|
+
capabilities,
|
|
1073
|
+
await declaredConfiguration.listConfiguration(
|
|
1074
|
+
listInput.signal === void 0 ? {} : { signal: listInput.signal }
|
|
1075
|
+
)
|
|
743
1076
|
);
|
|
744
|
-
|
|
1077
|
+
},
|
|
1078
|
+
applyConfigurationSelection: async (configurationInput) => {
|
|
1079
|
+
requireUsable();
|
|
1080
|
+
throwIfAgentOperationAborted(configurationInput.signal);
|
|
1081
|
+
const catalog = validateAgentConfigurationCatalogForCapabilities(
|
|
745
1082
|
capabilities,
|
|
746
|
-
|
|
1083
|
+
await declaredConfiguration.listConfiguration(
|
|
1084
|
+
configurationInput.signal === void 0 ? {} : { signal: configurationInput.signal }
|
|
1085
|
+
)
|
|
747
1086
|
);
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1087
|
+
let selection;
|
|
1088
|
+
try {
|
|
1089
|
+
selection = parseAgentConfigurationSelectionFor(
|
|
1090
|
+
catalog,
|
|
1091
|
+
configurationInput.selection
|
|
1092
|
+
);
|
|
1093
|
+
} catch {
|
|
1094
|
+
return throwAgentProviderContractError(
|
|
1095
|
+
providerKey,
|
|
1096
|
+
"configuration_value_unsupported",
|
|
1097
|
+
"Configuration selection is stale or outside the offered catalog."
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
return delegateProviderMutation({
|
|
1101
|
+
operation: "apply_configuration",
|
|
1102
|
+
observer: configurationInput.onProviderExecutionStarted,
|
|
1103
|
+
invoke: (onProviderExecutionStarted) => declaredConfiguration.applyConfigurationSelection({
|
|
1104
|
+
selection,
|
|
1105
|
+
onProviderExecutionStarted,
|
|
751
1106
|
...configurationInput.signal === void 0 ? {} : { signal: configurationInput.signal }
|
|
1107
|
+
}),
|
|
1108
|
+
validate: (candidate) => validateAgentProviderOperationResult(
|
|
1109
|
+
candidate,
|
|
1110
|
+
outputContext()
|
|
1111
|
+
)
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
const declaredOperations = input.candidate.operations;
|
|
1116
|
+
const operations = declaredOperations.kind === "unsupported" ? Object.freeze({ kind: "unsupported" }) : Object.freeze({
|
|
1117
|
+
kind: "supported",
|
|
1118
|
+
listOperations: async (listInput = {}) => {
|
|
1119
|
+
requireUsable();
|
|
1120
|
+
throwIfAgentOperationAborted(listInput.signal);
|
|
1121
|
+
return validateAgentOperationCatalogForCapabilities(
|
|
1122
|
+
capabilities,
|
|
1123
|
+
await declaredOperations.listOperations(
|
|
1124
|
+
listInput.signal === void 0 ? {} : { signal: listInput.signal }
|
|
1125
|
+
)
|
|
1126
|
+
);
|
|
1127
|
+
},
|
|
1128
|
+
invokeOperation: async (operationInput) => {
|
|
1129
|
+
requireUsable();
|
|
1130
|
+
throwIfAgentOperationAborted(operationInput.signal);
|
|
1131
|
+
let invocation;
|
|
1132
|
+
try {
|
|
1133
|
+
invocation = parseAgentOperationInvocation(operationInput.invocation);
|
|
1134
|
+
} catch {
|
|
1135
|
+
return throwAgentProviderContractError(
|
|
1136
|
+
providerKey,
|
|
1137
|
+
"input_operation_mismatch",
|
|
1138
|
+
"Operation invocation is invalid."
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
const catalog = validateAgentOperationCatalogForCapabilities(
|
|
1142
|
+
capabilities,
|
|
1143
|
+
await declaredOperations.listOperations(
|
|
1144
|
+
operationInput.signal === void 0 ? {} : { signal: operationInput.signal }
|
|
1145
|
+
)
|
|
1146
|
+
);
|
|
1147
|
+
const descriptor = catalog.operations.find(
|
|
1148
|
+
(operation) => operation.operationId === invocation.operationId
|
|
1149
|
+
);
|
|
1150
|
+
if (descriptor === void 0) {
|
|
1151
|
+
return throwAgentProviderContractError(
|
|
1152
|
+
providerKey,
|
|
1153
|
+
"input_operation_mismatch",
|
|
1154
|
+
"Operation invocation does not identify an offered operation."
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
try {
|
|
1158
|
+
invocation = parseAgentOperationInvocationFor(
|
|
1159
|
+
descriptor,
|
|
1160
|
+
invocation
|
|
1161
|
+
);
|
|
1162
|
+
} catch {
|
|
1163
|
+
return throwAgentProviderContractError(
|
|
1164
|
+
providerKey,
|
|
1165
|
+
"input_operation_mismatch",
|
|
1166
|
+
"Operation invocation is stale or outside the offered descriptor."
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
const existing = interactionState.operationInvocations.get(
|
|
1170
|
+
invocation.invocationId
|
|
1171
|
+
);
|
|
1172
|
+
if (existing !== void 0) {
|
|
1173
|
+
if (JSON.stringify(existing.invocation) !== JSON.stringify(invocation)) {
|
|
1174
|
+
return throwAgentProviderContractError(
|
|
1175
|
+
providerKey,
|
|
1176
|
+
"input_operation_mismatch",
|
|
1177
|
+
"Operation invocation ID was reused with conflicting input."
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
if (descriptor.idempotency === "required" && existing.result !== void 0) {
|
|
1181
|
+
return existing.result;
|
|
1182
|
+
}
|
|
1183
|
+
return throwAgentProviderContractError(
|
|
1184
|
+
providerKey,
|
|
1185
|
+
"input_operation_mismatch",
|
|
1186
|
+
"Operation invocation ID is already active or is not replayable."
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
const trackedInvocation = {
|
|
1190
|
+
invocation
|
|
1191
|
+
};
|
|
1192
|
+
interactionState.operationInvocations.set(
|
|
1193
|
+
invocation.invocationId,
|
|
1194
|
+
trackedInvocation
|
|
1195
|
+
);
|
|
1196
|
+
try {
|
|
1197
|
+
const result = await delegateProviderMutation({
|
|
1198
|
+
operation: "invoke_operation",
|
|
1199
|
+
observer: operationInput.onProviderExecutionStarted,
|
|
1200
|
+
invoke: (onProviderExecutionStarted) => declaredOperations.invokeOperation({
|
|
1201
|
+
invocation,
|
|
1202
|
+
onProviderExecutionStarted,
|
|
1203
|
+
...operationInput.signal === void 0 ? {} : { signal: operationInput.signal }
|
|
1204
|
+
}),
|
|
1205
|
+
validate: (candidate) => {
|
|
1206
|
+
const result2 = validateAgentOperationResultTransition({
|
|
1207
|
+
providerKey,
|
|
1208
|
+
candidate: validateAgentOperationResultForInvocation(
|
|
1209
|
+
providerKey,
|
|
1210
|
+
descriptor,
|
|
1211
|
+
invocation,
|
|
1212
|
+
candidate
|
|
1213
|
+
),
|
|
1214
|
+
previous: interactionState.operationResults.get(
|
|
1215
|
+
invocation.invocationId
|
|
1216
|
+
)
|
|
1217
|
+
});
|
|
1218
|
+
interactionState.operationResults.set(
|
|
1219
|
+
result2.invocationId,
|
|
1220
|
+
result2
|
|
1221
|
+
);
|
|
1222
|
+
return result2;
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
trackedInvocation.result = result;
|
|
1226
|
+
return result;
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
if (!(error instanceof AgentProviderDelegatedOperationError)) {
|
|
1229
|
+
interactionState.operationInvocations.delete(
|
|
1230
|
+
invocation.invocationId
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
throw error;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
const declaredManagedContent = input.candidate.managedContent;
|
|
1238
|
+
const managedContent = declaredManagedContent.kind === "unsupported" ? Object.freeze({ kind: "unsupported" }) : Object.freeze({
|
|
1239
|
+
kind: "supported",
|
|
1240
|
+
listManagedContent: async (listInput = {}) => {
|
|
1241
|
+
requireUsable();
|
|
1242
|
+
throwIfAgentOperationAborted(listInput.signal);
|
|
1243
|
+
return validateAgentManagedContentCatalogForCapabilities(
|
|
1244
|
+
capabilities,
|
|
1245
|
+
await declaredManagedContent.listManagedContent(
|
|
1246
|
+
listInput.signal === void 0 ? {} : { signal: listInput.signal }
|
|
1247
|
+
)
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
const declaredIntegrations = input.candidate.integrations;
|
|
1252
|
+
const integrations = declaredIntegrations.kind === "unsupported" ? Object.freeze({ kind: "unsupported" }) : Object.freeze({
|
|
1253
|
+
kind: "supported",
|
|
1254
|
+
observeIntegrations: async (listInput = {}) => {
|
|
1255
|
+
requireUsable();
|
|
1256
|
+
throwIfAgentOperationAborted(listInput.signal);
|
|
1257
|
+
return validateAgentIntegrationCatalogForCapabilities(
|
|
1258
|
+
capabilities,
|
|
1259
|
+
await declaredIntegrations.observeIntegrations(
|
|
1260
|
+
listInput.signal === void 0 ? {} : { signal: listInput.signal }
|
|
1261
|
+
)
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
const declaredCollaboration = input.candidate.collaboration;
|
|
1266
|
+
const collaboration = declaredCollaboration.kind === "unsupported" ? Object.freeze({ kind: "unsupported" }) : Object.freeze({
|
|
1267
|
+
kind: "supported",
|
|
1268
|
+
spawnCollaboration: async (collaborationInput) => {
|
|
1269
|
+
requireUsable();
|
|
1270
|
+
throwIfAgentOperationAborted(collaborationInput.signal);
|
|
1271
|
+
let spawn;
|
|
1272
|
+
try {
|
|
1273
|
+
spawn = parseAgentCollaborationSpawnInput(collaborationInput.spawn);
|
|
1274
|
+
} catch {
|
|
1275
|
+
return throwAgentProviderContractError(
|
|
1276
|
+
providerKey,
|
|
1277
|
+
"input_operation_mismatch",
|
|
1278
|
+
"Collaboration spawn input is invalid."
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
const capability = capabilities.collaboration;
|
|
1282
|
+
if (capability.kind !== "supported" || !capability.controlActions.includes("spawn") || !capability.roles.includes(spawn.role) || collaborationNodes.has(spawn.collaborationId) || collaborationSpawnReservations.has(spawn.collaborationId)) {
|
|
1283
|
+
return throwAgentProviderContractError(
|
|
1284
|
+
providerKey,
|
|
1285
|
+
"input_capability_mismatch",
|
|
1286
|
+
"Collaboration spawn is outside the provider's declared capability."
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
const observedParent = spawn.parentCollaborationId === void 0 ? void 0 : collaborationNodes.get(spawn.parentCollaborationId);
|
|
1290
|
+
const admission = evaluateCollaborationGraphAdmission({
|
|
1291
|
+
capability,
|
|
1292
|
+
candidate: {
|
|
1293
|
+
collaborationId: spawn.collaborationId,
|
|
1294
|
+
rootCollaborationId: observedParent?.rootCollaborationId ?? spawn.collaborationId,
|
|
1295
|
+
...spawn.parentCollaborationId === void 0 ? {} : { parentCollaborationId: spawn.parentCollaborationId },
|
|
1296
|
+
active: true
|
|
1297
|
+
},
|
|
1298
|
+
nodes: collaborationNodes,
|
|
1299
|
+
reservations: collaborationSpawnReservations
|
|
1300
|
+
});
|
|
1301
|
+
if (admission.kind === "conflict") {
|
|
1302
|
+
return throwAgentProviderContractError(
|
|
1303
|
+
providerKey,
|
|
1304
|
+
"input_operation_mismatch",
|
|
1305
|
+
"Collaboration parent is unavailable in the canonical graph."
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
if (admission.kind === "capacity_exceeded") {
|
|
1309
|
+
return throwAgentProviderContractError(
|
|
1310
|
+
providerKey,
|
|
1311
|
+
"input_capability_mismatch",
|
|
1312
|
+
"Collaboration graph limit would be exceeded."
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
const parent = admission.parent;
|
|
1316
|
+
collaborationSpawnReservations.set(
|
|
1317
|
+
spawn.collaborationId,
|
|
1318
|
+
Object.freeze({
|
|
1319
|
+
...spawn.parentCollaborationId === void 0 ? {} : { parentCollaborationId: spawn.parentCollaborationId }
|
|
752
1320
|
})
|
|
753
1321
|
);
|
|
1322
|
+
try {
|
|
1323
|
+
const node = await delegateProviderMutation({
|
|
1324
|
+
operation: "spawn_collaboration",
|
|
1325
|
+
observer: collaborationInput.onProviderExecutionStarted,
|
|
1326
|
+
invoke: (onProviderExecutionStarted) => declaredCollaboration.spawnCollaboration({
|
|
1327
|
+
spawn,
|
|
1328
|
+
onProviderExecutionStarted,
|
|
1329
|
+
...collaborationInput.signal === void 0 ? {} : { signal: collaborationInput.signal }
|
|
1330
|
+
}),
|
|
1331
|
+
validate: (candidate) => {
|
|
1332
|
+
const node2 = validateAgentCollaborationNodeForCapabilities({
|
|
1333
|
+
capabilities,
|
|
1334
|
+
candidate
|
|
1335
|
+
});
|
|
1336
|
+
if (node2.collaborationId !== spawn.collaborationId || node2.parentCollaborationId !== spawn.parentCollaborationId || node2.role !== spawn.role || node2.title !== spawn.title || node2.objective !== spawn.objective || node2.createdAt !== spawn.createdAt || node2.rootCollaborationId !== (parent?.rootCollaborationId ?? spawn.collaborationId)) {
|
|
1337
|
+
return throwAgentProviderContractError(
|
|
1338
|
+
providerKey,
|
|
1339
|
+
"output_collaboration_mismatch",
|
|
1340
|
+
"Provider collaboration node does not match the spawn input."
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
return node2;
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
collaborationNodes.set(node.collaborationId, node);
|
|
1347
|
+
collaborationSpawnReservations.delete(spawn.collaborationId);
|
|
1348
|
+
return node;
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
if (!(error instanceof AgentProviderDelegatedOperationError)) {
|
|
1351
|
+
collaborationSpawnReservations.delete(spawn.collaborationId);
|
|
1352
|
+
}
|
|
1353
|
+
throw error;
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
controlCollaboration: async (collaborationInput) => {
|
|
1357
|
+
requireUsable();
|
|
1358
|
+
throwIfAgentOperationAborted(collaborationInput.signal);
|
|
1359
|
+
let control;
|
|
1360
|
+
try {
|
|
1361
|
+
control = parseAgentCollaborationControlInput(
|
|
1362
|
+
collaborationInput.control
|
|
1363
|
+
);
|
|
1364
|
+
} catch {
|
|
1365
|
+
return throwAgentProviderContractError(
|
|
1366
|
+
providerKey,
|
|
1367
|
+
"input_operation_mismatch",
|
|
1368
|
+
"Collaboration control input is invalid."
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
const capability = capabilities.collaboration;
|
|
1372
|
+
const previous = collaborationNodes.get(control.collaborationId);
|
|
1373
|
+
if (capability.kind !== "supported" || !capability.controlActions.includes(control.action) || previous === void 0) {
|
|
1374
|
+
return throwAgentProviderContractError(
|
|
1375
|
+
providerKey,
|
|
1376
|
+
"input_capability_mismatch",
|
|
1377
|
+
"Collaboration control is outside the observed graph or declared capability."
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
const terminal = ["completed", "failed", "canceled"].includes(
|
|
1381
|
+
previous.status
|
|
1382
|
+
);
|
|
1383
|
+
if (control.action === "close" && previous.closedAt !== void 0) {
|
|
1384
|
+
return previous;
|
|
1385
|
+
}
|
|
1386
|
+
if (control.action === "close" && !terminal || control.action === "stop" && terminal || control.action === "steer" && terminal || control.action !== "inspect" && previous.closedAt !== void 0) {
|
|
1387
|
+
return throwAgentProviderContractError(
|
|
1388
|
+
providerKey,
|
|
1389
|
+
"input_capability_mismatch",
|
|
1390
|
+
"Collaboration control is unavailable in the current lifecycle state."
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
const node = await delegateProviderMutation({
|
|
1394
|
+
operation: "control_collaboration",
|
|
1395
|
+
observer: collaborationInput.onProviderExecutionStarted,
|
|
1396
|
+
invoke: (onProviderExecutionStarted) => declaredCollaboration.controlCollaboration({
|
|
1397
|
+
control,
|
|
1398
|
+
onProviderExecutionStarted,
|
|
1399
|
+
...collaborationInput.signal === void 0 ? {} : { signal: collaborationInput.signal }
|
|
1400
|
+
}),
|
|
1401
|
+
validate: (candidate) => {
|
|
1402
|
+
const validated = validateAgentCollaborationNodeForCapabilities({
|
|
1403
|
+
capabilities,
|
|
1404
|
+
candidate,
|
|
1405
|
+
previous
|
|
1406
|
+
});
|
|
1407
|
+
if (control.action === "stop" && validated.status !== "canceled" || control.action === "close" && (validated.status !== previous.status || validated.closedAt === void 0)) {
|
|
1408
|
+
return throwAgentProviderContractError(
|
|
1409
|
+
providerKey,
|
|
1410
|
+
"output_collaboration_mismatch",
|
|
1411
|
+
"Provider collaboration result does not settle the requested control action."
|
|
1412
|
+
);
|
|
1413
|
+
}
|
|
1414
|
+
return validated;
|
|
1415
|
+
}
|
|
1416
|
+
});
|
|
1417
|
+
collaborationNodes.set(node.collaborationId, node);
|
|
1418
|
+
return node;
|
|
1419
|
+
}
|
|
1420
|
+
});
|
|
1421
|
+
function validateGeneratedResourceInspection(candidate, resourceId) {
|
|
1422
|
+
if (candidate === null || typeof candidate !== "object" || !hasExactOwnKeys(candidate, [
|
|
1423
|
+
"descriptor",
|
|
1424
|
+
..."candidate" in candidate ? ["candidate"] : []
|
|
1425
|
+
])) {
|
|
1426
|
+
return throwAgentProviderContractError(
|
|
1427
|
+
providerKey,
|
|
1428
|
+
"output_resource_mismatch",
|
|
1429
|
+
"Provider returned an invalid generated-resource inspection."
|
|
1430
|
+
);
|
|
1431
|
+
}
|
|
1432
|
+
const rawInspection = candidate;
|
|
1433
|
+
const descriptor = validateAgentGeneratedResourceForCapabilities({
|
|
1434
|
+
capabilities,
|
|
1435
|
+
candidate: rawInspection.descriptor,
|
|
1436
|
+
expectedResourceId: resourceId,
|
|
1437
|
+
previous: interactionState.generatedResources.get(resourceId)
|
|
1438
|
+
});
|
|
1439
|
+
const hasCandidate = Object.prototype.hasOwnProperty.call(
|
|
1440
|
+
rawInspection,
|
|
1441
|
+
"candidate"
|
|
1442
|
+
);
|
|
1443
|
+
if (descriptor.status === "available" !== hasCandidate) {
|
|
1444
|
+
return throwAgentProviderContractError(
|
|
1445
|
+
providerKey,
|
|
1446
|
+
"output_resource_mismatch",
|
|
1447
|
+
"Exactly available generated resources must include owned artifact bytes."
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
if (!hasCandidate) return Object.freeze({ descriptor });
|
|
1451
|
+
let artifactCandidate;
|
|
1452
|
+
try {
|
|
1453
|
+
artifactCandidate = createAgentArtifactCandidate(
|
|
1454
|
+
rawInspection.candidate
|
|
1455
|
+
);
|
|
1456
|
+
} catch {
|
|
1457
|
+
return throwAgentProviderContractError(
|
|
1458
|
+
providerKey,
|
|
1459
|
+
"output_resource_mismatch",
|
|
1460
|
+
"Provider returned invalid generated-resource artifact bytes."
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
const artifact = artifactCandidate.descriptor;
|
|
1464
|
+
const expectedArtifactKind = descriptor.kind === "image" ? "image" : "file";
|
|
1465
|
+
if (artifactCandidate.delivery !== "required_before_reference" || artifact.artifactId !== descriptor.artifactId || artifact.kind !== expectedArtifactKind || artifact.displayName !== descriptor.displayName || artifact.mediaType !== descriptor.mediaType || artifact.byteSize !== descriptor.byteSize || artifact.digest?.algorithm !== "sha256" || artifact.digest?.value !== descriptor.sha256 || artifact.summary !== descriptor.summary) {
|
|
1466
|
+
return throwAgentProviderContractError(
|
|
1467
|
+
providerKey,
|
|
1468
|
+
"output_resource_mismatch",
|
|
1469
|
+
"Generated-resource metadata does not match its immutable artifact candidate."
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
return Object.freeze({ descriptor, candidate: artifactCandidate });
|
|
1473
|
+
}
|
|
1474
|
+
const declaredGeneratedResources = input.candidate.generatedResources;
|
|
1475
|
+
const generatedResources = declaredGeneratedResources.kind === "unsupported" ? Object.freeze({ kind: "unsupported" }) : Object.freeze({
|
|
1476
|
+
kind: "supported",
|
|
1477
|
+
getGeneratedResource: async (resourceInput) => {
|
|
1478
|
+
requireUsable();
|
|
1479
|
+
throwIfAgentOperationAborted(resourceInput.signal);
|
|
1480
|
+
const resourceId = parseAgentGeneratedResourceId(
|
|
1481
|
+
resourceInput.resourceId
|
|
1482
|
+
);
|
|
1483
|
+
const inspection = validateGeneratedResourceInspection(
|
|
1484
|
+
await declaredGeneratedResources.getGeneratedResource({
|
|
1485
|
+
resourceId,
|
|
1486
|
+
...resourceInput.signal === void 0 ? {} : { signal: resourceInput.signal }
|
|
1487
|
+
}),
|
|
1488
|
+
resourceId
|
|
1489
|
+
);
|
|
1490
|
+
interactionState.generatedResources.set(
|
|
1491
|
+
inspection.descriptor.resourceId,
|
|
1492
|
+
inspection.descriptor
|
|
1493
|
+
);
|
|
1494
|
+
return inspection;
|
|
754
1495
|
}
|
|
755
1496
|
});
|
|
756
1497
|
const close = (closeInput) => {
|
|
@@ -782,6 +1523,11 @@ function validateAgentProviderSession(input) {
|
|
|
782
1523
|
interruption,
|
|
783
1524
|
steering,
|
|
784
1525
|
configuration,
|
|
1526
|
+
operations,
|
|
1527
|
+
managedContent,
|
|
1528
|
+
integrations,
|
|
1529
|
+
collaboration,
|
|
1530
|
+
generatedResources,
|
|
785
1531
|
close
|
|
786
1532
|
});
|
|
787
1533
|
}
|