@agen-ai/agent-runtime 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -14
- package/dist/environment/index.d.ts +3 -0
- package/dist/environment/index.js +2 -0
- package/dist/environment/types.d.ts +59 -0
- package/dist/environment/types.js +0 -0
- package/dist/environment/validation.d.ts +17 -0
- package/dist/environment/validation.js +533 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/interactionValidation.js +1 -3
- package/dist/providerDriver.d.ts +2 -0
- package/dist/providerInstanceRegistry.js +9 -0
- package/dist/sessionValidation.js +75 -50
- package/dist/sessions.d.ts +3 -8
- package/dist/testing/conformance.d.ts +2 -1
- package/dist/testing/conformance.js +93 -15
- package/dist/testing/fakeProvider.d.ts +2 -0
- package/dist/testing/fakeProvider.js +103 -13
- package/package.json +7 -3
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
AGENT_ENVIRONMENT_DOMAINS,
|
|
4
|
+
parseAgentEnvironmentId,
|
|
5
|
+
parseAgentEnvironmentInvalidation,
|
|
6
|
+
parseAgentEnvironmentSnapshot,
|
|
7
|
+
parseAgentSessionConfiguration
|
|
8
|
+
} from "@agen-ai/agent-protocol";
|
|
9
|
+
import { throwAgentProviderContractError } from "../contractErrors.js";
|
|
10
|
+
import {
|
|
11
|
+
throwIfAgentOperationAborted
|
|
12
|
+
} from "../foundation.js";
|
|
13
|
+
import { containsAgentControlCharacter } from "../internal/controlCharacters.js";
|
|
14
|
+
const AGENT_ENVIRONMENT_ITERATOR_CLEANUP_TIMEOUT_MS = 1e3;
|
|
15
|
+
function isObject(value) {
|
|
16
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
function exactKeys(value, expected) {
|
|
19
|
+
const actual = Reflect.ownKeys(value);
|
|
20
|
+
return actual.length === expected.length && actual.every((key) => typeof key === "string" && expected.includes(key)) && expected.every((key) => Object.prototype.hasOwnProperty.call(value, key));
|
|
21
|
+
}
|
|
22
|
+
function exactKeysWithOptional(value, required, optional) {
|
|
23
|
+
const actual = Reflect.ownKeys(value);
|
|
24
|
+
const allowed = [...required, ...optional];
|
|
25
|
+
return actual.length >= required.length && actual.length <= allowed.length && actual.every((key) => typeof key === "string" && allowed.includes(key)) && required.every((key) => Object.prototype.hasOwnProperty.call(value, key));
|
|
26
|
+
}
|
|
27
|
+
function assertSignal(value, field) {
|
|
28
|
+
if (!(value instanceof AbortSignal)) {
|
|
29
|
+
throw new TypeError(`Environment ${field} must be an AbortSignal.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function abortReason(signal) {
|
|
33
|
+
return signal.reason instanceof Error ? signal.reason : new DOMException("The agent operation was aborted.", "AbortError");
|
|
34
|
+
}
|
|
35
|
+
function environmentContractError(providerKey, detail) {
|
|
36
|
+
return throwAgentProviderContractError(
|
|
37
|
+
providerKey,
|
|
38
|
+
"capability_port_mismatch",
|
|
39
|
+
`Provider ${providerKey} returned an invalid environment ${detail}.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function invalidEnvironmentMetadata(providerKey, detail) {
|
|
43
|
+
return throwAgentProviderContractError(
|
|
44
|
+
providerKey,
|
|
45
|
+
"invalid_inventory",
|
|
46
|
+
`Provider ${providerKey} returned invalid environment metadata: ${detail}.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
function sameDomains(actual, expected) {
|
|
50
|
+
return actual.length === expected.length && actual.every((domain, index) => domain === expected[index]);
|
|
51
|
+
}
|
|
52
|
+
function parsePortDomains(providerKey, value) {
|
|
53
|
+
if (!Array.isArray(value) || value.length < 1) {
|
|
54
|
+
return environmentContractError(providerKey, "port domains");
|
|
55
|
+
}
|
|
56
|
+
const domains = [];
|
|
57
|
+
for (const candidate of value) {
|
|
58
|
+
if (typeof candidate !== "string" || !AGENT_ENVIRONMENT_DOMAINS.includes(candidate) || domains.includes(candidate)) {
|
|
59
|
+
return environmentContractError(providerKey, "port domains");
|
|
60
|
+
}
|
|
61
|
+
domains.push(candidate);
|
|
62
|
+
}
|
|
63
|
+
return Object.freeze(domains);
|
|
64
|
+
}
|
|
65
|
+
function validateDeclaredPort(providerKey, capability, candidate) {
|
|
66
|
+
if (!isObject(candidate)) return environmentContractError(providerKey, "port");
|
|
67
|
+
const kind = candidate.kind;
|
|
68
|
+
if (kind === "unsupported") {
|
|
69
|
+
if (!exactKeys(candidate, ["kind"]) || capability.kind !== "unsupported") {
|
|
70
|
+
return environmentContractError(providerKey, "port");
|
|
71
|
+
}
|
|
72
|
+
return "unsupported";
|
|
73
|
+
}
|
|
74
|
+
if (kind !== "read" && kind !== "read_and_watch") {
|
|
75
|
+
return environmentContractError(providerKey, "port");
|
|
76
|
+
}
|
|
77
|
+
if (capability.kind === "unsupported" || capability.kind !== kind) {
|
|
78
|
+
return environmentContractError(providerKey, "port capability parity");
|
|
79
|
+
}
|
|
80
|
+
const expectedKeys = kind === "read" ? ["kind", "domains", "readEnvironment"] : ["kind", "domains", "readEnvironment", "watchEnvironment"];
|
|
81
|
+
if (!exactKeys(candidate, expectedKeys)) {
|
|
82
|
+
return environmentContractError(providerKey, "port shape");
|
|
83
|
+
}
|
|
84
|
+
const domains = parsePortDomains(
|
|
85
|
+
providerKey,
|
|
86
|
+
candidate.domains
|
|
87
|
+
);
|
|
88
|
+
if (!sameDomains(domains, capability.domains)) {
|
|
89
|
+
return environmentContractError(providerKey, "port capability domains");
|
|
90
|
+
}
|
|
91
|
+
if (typeof candidate.readEnvironment !== "function" || kind === "read_and_watch" && typeof candidate.watchEnvironment !== "function") {
|
|
92
|
+
return environmentContractError(providerKey, "port methods");
|
|
93
|
+
}
|
|
94
|
+
return kind;
|
|
95
|
+
}
|
|
96
|
+
function parseCanonicalWorkingDirectory(value) {
|
|
97
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 4096 || !path.isAbsolute(value) || path.normalize(value) !== value || containsAgentControlCharacter(value)) {
|
|
98
|
+
throw new TypeError(
|
|
99
|
+
"Environment workingDirectory must be a canonical absolute path."
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
function parseDiscoveryInput(input) {
|
|
105
|
+
if (!isObject(input) || !exactKeysWithOptional(input, [
|
|
106
|
+
"signal",
|
|
107
|
+
"environmentId",
|
|
108
|
+
"workingDirectory",
|
|
109
|
+
"configuration"
|
|
110
|
+
], ["onProviderExecutionStarted"])) {
|
|
111
|
+
throw new TypeError(
|
|
112
|
+
"Environment discovery input must contain signal, environmentId, workingDirectory, and configuration."
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const candidate = input;
|
|
116
|
+
assertSignal(candidate.signal, "discovery signal");
|
|
117
|
+
if (candidate.onProviderExecutionStarted !== void 0 && typeof candidate.onProviderExecutionStarted !== "function") {
|
|
118
|
+
throw new TypeError(
|
|
119
|
+
"Environment discovery onProviderExecutionStarted must be callable."
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
signal: candidate.signal,
|
|
124
|
+
environmentId: parseAgentEnvironmentId(candidate.environmentId),
|
|
125
|
+
workingDirectory: parseCanonicalWorkingDirectory(candidate.workingDirectory),
|
|
126
|
+
configuration: parseAgentSessionConfiguration(candidate.configuration),
|
|
127
|
+
...candidate.onProviderExecutionStarted === void 0 ? {} : {
|
|
128
|
+
onProviderExecutionStarted: candidate.onProviderExecutionStarted
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function parseDiscoveryWatchInput(input) {
|
|
133
|
+
if (!isObject(input) || !exactKeys(input, [
|
|
134
|
+
"signal",
|
|
135
|
+
"environmentId",
|
|
136
|
+
"workingDirectory",
|
|
137
|
+
"configuration"
|
|
138
|
+
])) {
|
|
139
|
+
throw new TypeError(
|
|
140
|
+
"Environment discovery watch input must contain signal, environmentId, workingDirectory, and configuration."
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
const candidate = input;
|
|
144
|
+
assertSignal(candidate.signal, "discovery watch signal");
|
|
145
|
+
return {
|
|
146
|
+
signal: candidate.signal,
|
|
147
|
+
environmentId: parseAgentEnvironmentId(candidate.environmentId),
|
|
148
|
+
workingDirectory: parseCanonicalWorkingDirectory(candidate.workingDirectory),
|
|
149
|
+
configuration: parseAgentSessionConfiguration(candidate.configuration)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function parseObservationInput(input) {
|
|
153
|
+
if (!isObject(input) || !exactKeysWithOptional(
|
|
154
|
+
input,
|
|
155
|
+
["signal", "environmentId"],
|
|
156
|
+
["onProviderExecutionStarted"]
|
|
157
|
+
)) {
|
|
158
|
+
throw new TypeError(
|
|
159
|
+
"Environment observation input must contain signal and environmentId."
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const candidate = input;
|
|
163
|
+
assertSignal(candidate.signal, "observation signal");
|
|
164
|
+
if (candidate.onProviderExecutionStarted !== void 0 && typeof candidate.onProviderExecutionStarted !== "function") {
|
|
165
|
+
throw new TypeError(
|
|
166
|
+
"Environment observation onProviderExecutionStarted must be callable."
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
signal: candidate.signal,
|
|
171
|
+
environmentId: parseAgentEnvironmentId(candidate.environmentId),
|
|
172
|
+
...candidate.onProviderExecutionStarted === void 0 ? {} : {
|
|
173
|
+
onProviderExecutionStarted: candidate.onProviderExecutionStarted
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function parseObservationWatchInput(input) {
|
|
178
|
+
if (!isObject(input) || !exactKeys(input, ["signal", "environmentId"])) {
|
|
179
|
+
throw new TypeError(
|
|
180
|
+
"Environment observation watch input must contain signal and environmentId."
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
const candidate = input;
|
|
184
|
+
assertSignal(candidate.signal, "observation watch signal");
|
|
185
|
+
return {
|
|
186
|
+
signal: candidate.signal,
|
|
187
|
+
environmentId: parseAgentEnvironmentId(candidate.environmentId)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function validateSnapshotDomains(providerKey, snapshot, declaredDomains, maximumExtent) {
|
|
191
|
+
for (const domain of AGENT_ENVIRONMENT_DOMAINS) {
|
|
192
|
+
const result = snapshot[domain];
|
|
193
|
+
if (maximumExtent === "workspace_discovery" && result.kind === "available" && result.extent === "session") {
|
|
194
|
+
return invalidEnvironmentMetadata(
|
|
195
|
+
providerKey,
|
|
196
|
+
`instance discovery cannot claim session evidence for domain ${domain}`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (!declaredDomains.includes(domain) && result.kind === "available") {
|
|
200
|
+
return invalidEnvironmentMetadata(
|
|
201
|
+
providerKey,
|
|
202
|
+
`domain ${domain} was marked available by an undeclared port domain`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return snapshot;
|
|
207
|
+
}
|
|
208
|
+
function validateSnapshot(providerKey, candidate, expectedEnvironmentId, declaredDomains, maximumExtent) {
|
|
209
|
+
let snapshot;
|
|
210
|
+
try {
|
|
211
|
+
snapshot = parseAgentEnvironmentSnapshot(candidate);
|
|
212
|
+
} catch {
|
|
213
|
+
return invalidEnvironmentMetadata(providerKey, "snapshot");
|
|
214
|
+
}
|
|
215
|
+
if (snapshot.environmentId !== expectedEnvironmentId) {
|
|
216
|
+
return invalidEnvironmentMetadata(providerKey, "snapshot environmentId does not echo the request");
|
|
217
|
+
}
|
|
218
|
+
return validateSnapshotDomains(providerKey, snapshot, declaredDomains, maximumExtent);
|
|
219
|
+
}
|
|
220
|
+
function validateInvalidation(providerKey, candidate, expectedEnvironmentId, declaredDomains) {
|
|
221
|
+
let invalidation;
|
|
222
|
+
try {
|
|
223
|
+
invalidation = parseAgentEnvironmentInvalidation(candidate);
|
|
224
|
+
} catch {
|
|
225
|
+
return invalidEnvironmentMetadata(providerKey, "invalidation");
|
|
226
|
+
}
|
|
227
|
+
if (invalidation.environmentId !== expectedEnvironmentId) {
|
|
228
|
+
return invalidEnvironmentMetadata(providerKey, "invalidation environmentId does not echo the request");
|
|
229
|
+
}
|
|
230
|
+
if (invalidation.domains.some((domain) => !declaredDomains.includes(domain))) {
|
|
231
|
+
return invalidEnvironmentMetadata(providerKey, "invalidation names an undeclared domain");
|
|
232
|
+
}
|
|
233
|
+
return invalidation;
|
|
234
|
+
}
|
|
235
|
+
function awaitCancellable(candidate, signal) {
|
|
236
|
+
const providerResult = Promise.resolve(candidate);
|
|
237
|
+
return new Promise((resolve, reject) => {
|
|
238
|
+
let settled = false;
|
|
239
|
+
const remove = () => signal.removeEventListener("abort", onAbort);
|
|
240
|
+
const onAbort = () => {
|
|
241
|
+
if (settled) return;
|
|
242
|
+
settled = true;
|
|
243
|
+
remove();
|
|
244
|
+
reject(abortReason(signal));
|
|
245
|
+
};
|
|
246
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
247
|
+
providerResult.then(
|
|
248
|
+
(value) => {
|
|
249
|
+
if (settled) return;
|
|
250
|
+
settled = true;
|
|
251
|
+
remove();
|
|
252
|
+
resolve(value);
|
|
253
|
+
},
|
|
254
|
+
(error) => {
|
|
255
|
+
if (settled) return;
|
|
256
|
+
settled = true;
|
|
257
|
+
remove();
|
|
258
|
+
reject(error);
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
if (signal.aborted) onAbort();
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
function providerIterator(providerKey, iterable) {
|
|
265
|
+
if (!isObject(iterable)) return environmentContractError(providerKey, "watch result");
|
|
266
|
+
const factory = iterable[Symbol.asyncIterator];
|
|
267
|
+
if (typeof factory !== "function") {
|
|
268
|
+
return environmentContractError(providerKey, "watch iterator");
|
|
269
|
+
}
|
|
270
|
+
const iterator = factory.call(iterable);
|
|
271
|
+
if (!isObject(iterator)) return environmentContractError(providerKey, "watch iterator");
|
|
272
|
+
const next = iterator.next;
|
|
273
|
+
if (typeof next !== "function") {
|
|
274
|
+
return environmentContractError(providerKey, "watch iterator");
|
|
275
|
+
}
|
|
276
|
+
const returnMethod = iterator.return;
|
|
277
|
+
if (returnMethod !== void 0 && typeof returnMethod !== "function") {
|
|
278
|
+
return environmentContractError(providerKey, "watch iterator");
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
iterator,
|
|
282
|
+
next,
|
|
283
|
+
returnMethod
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function iteratorResult(value) {
|
|
287
|
+
if (!isObject(value)) return false;
|
|
288
|
+
const done = value.done;
|
|
289
|
+
return done === void 0 || typeof done === "boolean";
|
|
290
|
+
}
|
|
291
|
+
function boundedCleanup(result, providerKey) {
|
|
292
|
+
let timer;
|
|
293
|
+
const timeout = new Promise((_, reject) => {
|
|
294
|
+
timer = setTimeout(() => reject(new Error(
|
|
295
|
+
`Provider ${providerKey} environment watch cleanup exceeded ${AGENT_ENVIRONMENT_ITERATOR_CLEANUP_TIMEOUT_MS}ms.`
|
|
296
|
+
)), AGENT_ENVIRONMENT_ITERATOR_CLEANUP_TIMEOUT_MS);
|
|
297
|
+
});
|
|
298
|
+
return Promise.race([Promise.resolve(result), timeout]).then(() => void 0).finally(() => {
|
|
299
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
function beginCleanup(state) {
|
|
303
|
+
if (state.cleanupPromise !== void 0) return state.cleanupPromise;
|
|
304
|
+
const source = state.source;
|
|
305
|
+
if (source === void 0) return Promise.resolve();
|
|
306
|
+
if (source.returnMethod === void 0) {
|
|
307
|
+
state.cleanupPromise = Promise.resolve();
|
|
308
|
+
} else {
|
|
309
|
+
try {
|
|
310
|
+
state.cleanupPromise = boundedCleanup(
|
|
311
|
+
source.returnMethod.call(source.iterator),
|
|
312
|
+
state.providerKey
|
|
313
|
+
);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
state.cleanupPromise = Promise.reject(error);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
state.cleanupPromise.catch(() => void 0);
|
|
319
|
+
return state.cleanupPromise;
|
|
320
|
+
}
|
|
321
|
+
function closeState(state) {
|
|
322
|
+
if (state.closed) {
|
|
323
|
+
void beginCleanup(state);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
state.closed = true;
|
|
327
|
+
state.signal.removeEventListener("abort", state.onAbort);
|
|
328
|
+
if (!state.controller.signal.aborted) state.controller.abort();
|
|
329
|
+
void beginCleanup(state);
|
|
330
|
+
}
|
|
331
|
+
function createWatchIterator(providerKey, declaredWatch, receiver, lifetimeSignal, watchInput, declaredDomains) {
|
|
332
|
+
const controller = new AbortController();
|
|
333
|
+
const signal = AbortSignal.any([
|
|
334
|
+
lifetimeSignal,
|
|
335
|
+
watchInput.signal,
|
|
336
|
+
controller.signal
|
|
337
|
+
]);
|
|
338
|
+
const state = {
|
|
339
|
+
providerKey,
|
|
340
|
+
controller,
|
|
341
|
+
signal,
|
|
342
|
+
onAbort: () => closeState(state),
|
|
343
|
+
declaredWatch,
|
|
344
|
+
receiver,
|
|
345
|
+
watchInput,
|
|
346
|
+
declaredDomains,
|
|
347
|
+
closed: false,
|
|
348
|
+
closedNormally: false,
|
|
349
|
+
pendingNext: false
|
|
350
|
+
};
|
|
351
|
+
if (signal.aborted) closeState(state);
|
|
352
|
+
else signal.addEventListener("abort", state.onAbort, { once: true });
|
|
353
|
+
const source = () => {
|
|
354
|
+
if (state.source !== void 0) return state.source;
|
|
355
|
+
throwIfAgentOperationAborted(signal);
|
|
356
|
+
const iterable = state.declaredWatch.call(state.receiver, {
|
|
357
|
+
...state.watchInput,
|
|
358
|
+
signal
|
|
359
|
+
});
|
|
360
|
+
state.source = providerIterator(providerKey, iterable);
|
|
361
|
+
if (signal.aborted) closeState(state);
|
|
362
|
+
return state.source;
|
|
363
|
+
};
|
|
364
|
+
const next = async () => {
|
|
365
|
+
if (state.pendingNext) {
|
|
366
|
+
throw new TypeError("Concurrent environment watch next calls are not supported.");
|
|
367
|
+
}
|
|
368
|
+
if (state.closed) {
|
|
369
|
+
if (state.closedNormally || !signal.aborted) {
|
|
370
|
+
return { done: true, value: void 0 };
|
|
371
|
+
}
|
|
372
|
+
throwIfAgentOperationAborted(signal);
|
|
373
|
+
}
|
|
374
|
+
throwIfAgentOperationAborted(signal);
|
|
375
|
+
state.pendingNext = true;
|
|
376
|
+
try {
|
|
377
|
+
const iterator = source();
|
|
378
|
+
throwIfAgentOperationAborted(signal);
|
|
379
|
+
const result = await awaitCancellable(
|
|
380
|
+
iterator.next.call(iterator.iterator),
|
|
381
|
+
signal
|
|
382
|
+
);
|
|
383
|
+
throwIfAgentOperationAborted(signal);
|
|
384
|
+
if (!iteratorResult(result)) {
|
|
385
|
+
return environmentContractError(providerKey, "watch iterator result");
|
|
386
|
+
}
|
|
387
|
+
if (result.done === true) {
|
|
388
|
+
state.closedNormally = true;
|
|
389
|
+
closeState(state);
|
|
390
|
+
await beginCleanup(state);
|
|
391
|
+
return { done: true, value: void 0 };
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
done: false,
|
|
395
|
+
value: validateInvalidation(
|
|
396
|
+
providerKey,
|
|
397
|
+
result.value,
|
|
398
|
+
state.watchInput.environmentId,
|
|
399
|
+
state.declaredDomains
|
|
400
|
+
)
|
|
401
|
+
};
|
|
402
|
+
} catch (error) {
|
|
403
|
+
closeState(state);
|
|
404
|
+
throw error;
|
|
405
|
+
} finally {
|
|
406
|
+
state.pendingNext = false;
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
const returnIterator = async (value) => {
|
|
410
|
+
state.closedNormally = true;
|
|
411
|
+
state.controller.abort();
|
|
412
|
+
closeState(state);
|
|
413
|
+
await beginCleanup(state);
|
|
414
|
+
return { done: true, value };
|
|
415
|
+
};
|
|
416
|
+
return { next, return: returnIterator };
|
|
417
|
+
}
|
|
418
|
+
function wrapRead(input) {
|
|
419
|
+
return async (rawInput) => {
|
|
420
|
+
const parsedInput = input.parseInput(rawInput);
|
|
421
|
+
const signal = AbortSignal.any([
|
|
422
|
+
input.lifetimeSignal,
|
|
423
|
+
parsedInput.signal
|
|
424
|
+
]);
|
|
425
|
+
throwIfAgentOperationAborted(signal);
|
|
426
|
+
const {
|
|
427
|
+
onProviderExecutionStarted,
|
|
428
|
+
...providerInput
|
|
429
|
+
} = parsedInput;
|
|
430
|
+
onProviderExecutionStarted?.();
|
|
431
|
+
const candidate = input.declaredRead.call(input.receiver, {
|
|
432
|
+
...providerInput,
|
|
433
|
+
signal
|
|
434
|
+
});
|
|
435
|
+
const result = await awaitCancellable(candidate, signal);
|
|
436
|
+
throwIfAgentOperationAborted(signal);
|
|
437
|
+
return validateSnapshot(
|
|
438
|
+
input.providerKey,
|
|
439
|
+
result,
|
|
440
|
+
parsedInput.environmentId,
|
|
441
|
+
input.declaredDomains,
|
|
442
|
+
input.maximumExtent
|
|
443
|
+
);
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function wrapWatch(input) {
|
|
447
|
+
return (rawInput) => {
|
|
448
|
+
const parsedInput = input.parseInput(rawInput);
|
|
449
|
+
return {
|
|
450
|
+
[Symbol.asyncIterator]() {
|
|
451
|
+
return createWatchIterator(
|
|
452
|
+
input.providerKey,
|
|
453
|
+
input.declaredWatch,
|
|
454
|
+
input.receiver,
|
|
455
|
+
input.lifetimeSignal,
|
|
456
|
+
parsedInput,
|
|
457
|
+
input.declaredDomains
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function validateAgentEnvironmentDiscoveryPort(input) {
|
|
464
|
+
assertSignal(input.signal, "instance signal");
|
|
465
|
+
const kind = validateDeclaredPort(input.providerKey, input.capability, input.port);
|
|
466
|
+
if (kind === "unsupported") return Object.freeze({ kind: "unsupported" });
|
|
467
|
+
const declared = input.port;
|
|
468
|
+
const domains = Object.freeze([...declared.domains]);
|
|
469
|
+
const readEnvironment = wrapRead({
|
|
470
|
+
providerKey: input.providerKey,
|
|
471
|
+
declaredRead: declared.readEnvironment,
|
|
472
|
+
receiver: declared,
|
|
473
|
+
lifetimeSignal: input.signal,
|
|
474
|
+
parseInput: parseDiscoveryInput,
|
|
475
|
+
declaredDomains: domains,
|
|
476
|
+
maximumExtent: "workspace_discovery"
|
|
477
|
+
});
|
|
478
|
+
if (kind === "read") {
|
|
479
|
+
return Object.freeze({ kind: "read", domains, readEnvironment });
|
|
480
|
+
}
|
|
481
|
+
const watchEnvironment = wrapWatch({
|
|
482
|
+
providerKey: input.providerKey,
|
|
483
|
+
declaredWatch: declared.watchEnvironment,
|
|
484
|
+
receiver: declared,
|
|
485
|
+
lifetimeSignal: input.signal,
|
|
486
|
+
declaredDomains: domains,
|
|
487
|
+
parseInput: parseDiscoveryWatchInput
|
|
488
|
+
});
|
|
489
|
+
return Object.freeze({
|
|
490
|
+
kind: "read_and_watch",
|
|
491
|
+
domains,
|
|
492
|
+
readEnvironment,
|
|
493
|
+
watchEnvironment
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
function validateAgentEnvironmentObservationPort(input) {
|
|
497
|
+
assertSignal(input.signal, "session signal");
|
|
498
|
+
const kind = validateDeclaredPort(input.providerKey, input.capability, input.port);
|
|
499
|
+
if (kind === "unsupported") return Object.freeze({ kind: "unsupported" });
|
|
500
|
+
const declared = input.port;
|
|
501
|
+
const domains = Object.freeze([...declared.domains]);
|
|
502
|
+
const readEnvironment = wrapRead({
|
|
503
|
+
providerKey: input.providerKey,
|
|
504
|
+
declaredRead: declared.readEnvironment,
|
|
505
|
+
receiver: declared,
|
|
506
|
+
lifetimeSignal: input.signal,
|
|
507
|
+
parseInput: parseObservationInput,
|
|
508
|
+
declaredDomains: domains,
|
|
509
|
+
maximumExtent: "session"
|
|
510
|
+
});
|
|
511
|
+
if (kind === "read") {
|
|
512
|
+
return Object.freeze({ kind: "read", domains, readEnvironment });
|
|
513
|
+
}
|
|
514
|
+
const watchEnvironment = wrapWatch({
|
|
515
|
+
providerKey: input.providerKey,
|
|
516
|
+
declaredWatch: declared.watchEnvironment,
|
|
517
|
+
receiver: declared,
|
|
518
|
+
lifetimeSignal: input.signal,
|
|
519
|
+
declaredDomains: domains,
|
|
520
|
+
parseInput: parseObservationWatchInput
|
|
521
|
+
});
|
|
522
|
+
return Object.freeze({
|
|
523
|
+
kind: "read_and_watch",
|
|
524
|
+
domains,
|
|
525
|
+
readEnvironment,
|
|
526
|
+
watchEnvironment
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
export {
|
|
530
|
+
AGENT_ENVIRONMENT_ITERATOR_CLEANUP_TIMEOUT_MS,
|
|
531
|
+
validateAgentEnvironmentDiscoveryPort,
|
|
532
|
+
validateAgentEnvironmentObservationPort
|
|
533
|
+
};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -77,9 +77,7 @@ function validateAgentIntegrationCatalogForCapabilities(capabilities, candidate)
|
|
|
77
77
|
}
|
|
78
78
|
const capability = capabilities.integrations;
|
|
79
79
|
if (capability.kind !== "supported" || catalog.integrations.length > capability.maxIntegrations || catalog.integrations.some(
|
|
80
|
-
(integration) => !capability.integrationKinds.includes(integration.kind) || integration.servers.length > capability.maxServersPerIntegration || integration.servers.some(
|
|
81
|
-
(server) => server.tools.length > capability.maxToolsPerServer || server.resources.length > capability.maxResourcesPerServer
|
|
82
|
-
)
|
|
80
|
+
(integration) => !capability.integrationKinds.includes(integration.kind) || (integration.kind === "mcp" ? integration.servers.length > capability.maxServersPerIntegration || integration.servers.some((server) => server.tools.length > capability.maxToolsPerServer || server.resources.length > capability.maxResourcesPerServer) : (integration.tools?.length ?? 0) > capability.maxToolsPerServer)
|
|
83
81
|
)) {
|
|
84
82
|
return invalidInventory(
|
|
85
83
|
capabilities.providerKey,
|
package/dist/providerDriver.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type AgentCapabilities, type AgentInstanceId, type AgentProviderKey } from "@agen-ai/agent-protocol";
|
|
2
2
|
import type { MaybePromise } from "./foundation.js";
|
|
3
3
|
import type { AgentAccountQuotaPort } from "./accountQuota/types.js";
|
|
4
|
+
import type { AgentEnvironmentDiscoveryPort } from "./environment/types.js";
|
|
4
5
|
import type { AgentProviderReadiness } from "./readiness.js";
|
|
5
6
|
import type { AgentProviderAdapter } from "./sessions.js";
|
|
6
7
|
export interface AgentProviderInstanceDefinition {
|
|
@@ -16,6 +17,7 @@ export interface MaterializedAgentProviderInstance {
|
|
|
16
17
|
readonly capabilities: AgentCapabilities;
|
|
17
18
|
readonly adapter: AgentProviderAdapter;
|
|
18
19
|
readonly accountQuota: AgentAccountQuotaPort;
|
|
20
|
+
readonly environment: AgentEnvironmentDiscoveryPort;
|
|
19
21
|
readonly checkReadiness: (input?: AgentProviderReadinessCheckInput) => MaybePromise<AgentProviderReadiness>;
|
|
20
22
|
readonly dispose: () => MaybePromise<void>;
|
|
21
23
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "@agen-ai/agent-protocol";
|
|
6
6
|
import { validateAgentProviderAdapter } from "./adapterValidation.js";
|
|
7
7
|
import { validateAgentAccountQuotaPort } from "./accountQuota/validation.js";
|
|
8
|
+
import { validateAgentEnvironmentDiscoveryPort } from "./environment/validation.js";
|
|
8
9
|
import {
|
|
9
10
|
createAgentProviderCatalogEntries,
|
|
10
11
|
createAgentProviderInstanceCatalogEntries
|
|
@@ -140,6 +141,7 @@ function managedInstance(rawInstance, providerKey) {
|
|
|
140
141
|
disposePromise: null
|
|
141
142
|
};
|
|
142
143
|
const quotaLifetime = new AbortController();
|
|
144
|
+
const environmentLifetime = new AbortController();
|
|
143
145
|
const instance = Object.freeze({
|
|
144
146
|
instanceId: parseAgentInstanceId(rawInstance.instanceId),
|
|
145
147
|
capabilities,
|
|
@@ -149,6 +151,12 @@ function managedInstance(rawInstance, providerKey) {
|
|
|
149
151
|
port: rawInstance.accountQuota,
|
|
150
152
|
signal: quotaLifetime.signal
|
|
151
153
|
}),
|
|
154
|
+
environment: validateAgentEnvironmentDiscoveryPort({
|
|
155
|
+
providerKey,
|
|
156
|
+
capability: capabilities.environment.instance,
|
|
157
|
+
port: rawInstance.environment,
|
|
158
|
+
signal: environmentLifetime.signal
|
|
159
|
+
}),
|
|
152
160
|
checkReadiness: async (input) => {
|
|
153
161
|
const readiness = await checkReadiness(input);
|
|
154
162
|
return validateAgentProviderReadiness(readiness);
|
|
@@ -158,6 +166,7 @@ function managedInstance(rawInstance, providerKey) {
|
|
|
158
166
|
if (lifecycle.disposePromise) return lifecycle.disposePromise;
|
|
159
167
|
lifecycle.status = "disposing";
|
|
160
168
|
quotaLifetime.abort(new DOMException("The provider instance was disposed.", "AbortError"));
|
|
169
|
+
environmentLifetime.abort(new DOMException("The provider instance was disposed.", "AbortError"));
|
|
161
170
|
lifecycle.disposePromise = Promise.resolve().then(() => dispose()).then(() => {
|
|
162
171
|
lifecycle.status = "disposed";
|
|
163
172
|
}).catch((error) => {
|