@fevex/core 0.1.0-alpha.4 → 0.1.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +240 -11
  2. package/dist/agents/index.d.mts +1 -1
  3. package/dist/artifacts/index.d.mts +2 -0
  4. package/dist/artifacts/index.mjs +2 -0
  5. package/dist/artifacts-gUzemET7.mjs +440 -0
  6. package/dist/channels/index.d.mts +1 -1
  7. package/dist/evals/index.d.mts +4 -4
  8. package/dist/fevex-B4QY5Z6e.d.mts +119 -0
  9. package/dist/http/index.d.mts +65 -5
  10. package/dist/http/index.mjs +238 -16
  11. package/dist/{index-C-IFfCNw.d.mts → index-1cHr7KB_.d.mts} +3 -3
  12. package/dist/{index-CqOjvtQ9.d.mts → index-5p3q2yIp.d.mts} +4 -2
  13. package/dist/{index-hhbgvfIt.d.mts → index-BJktlD4E.d.mts} +2 -2
  14. package/dist/{index-CTIwrVf9.d.mts → index-BgKFd9yw.d.mts} +1 -1
  15. package/dist/{index-dkFyTNh4.d.mts → index-CbACw6xZ.d.mts} +2 -2
  16. package/dist/index-CpLDt6Rw.d.mts +118 -0
  17. package/dist/{index-CknU-glw.d.mts → index-CseuTR-F.d.mts} +2 -2
  18. package/dist/{index-CaE864Kz.d.mts → index-CzAi2Y_Z.d.mts} +45 -6
  19. package/dist/{index-DyyNLuwO.d.mts → index-DVw0zOc_.d.mts} +3 -3
  20. package/dist/{index-CrOId2px.d.mts → index-Df84VWmN.d.mts} +1 -1
  21. package/dist/index.d.mts +13 -13
  22. package/dist/index.mjs +904 -153
  23. package/dist/knowledge/index.d.mts +1 -1
  24. package/dist/model-stream-JQdHDDcj.mjs +131 -0
  25. package/dist/models/index.d.mts +1 -1
  26. package/dist/observability/index.d.mts +2 -2
  27. package/dist/policies/index.d.mts +1 -1
  28. package/dist/{model-stream-u2xMsEaf.mjs → run-support-CmZ6Xo6H.mjs} +41 -130
  29. package/dist/runtime/index.d.mts +2 -2
  30. package/dist/runtime/index.mjs +43 -1
  31. package/dist/sandbox/index.d.mts +1 -1
  32. package/dist/sandbox/index.mjs +1 -1
  33. package/dist/teams/index.d.mts +1 -1
  34. package/dist/testing/index.d.mts +8 -7
  35. package/dist/testing/index.mjs +94 -2
  36. package/dist/tools/index.d.mts +2 -2
  37. package/dist/types-B8laW1hM.d.mts +561 -0
  38. package/dist/workflows/index.d.mts +1 -1
  39. package/package.json +6 -2
  40. package/dist/fevex-BoPbZ-Tw.d.mts +0 -60
  41. package/dist/index-voIZhQXB.d.mts +0 -80
  42. package/dist/types-CuoRx12Q.d.mts +0 -333
@@ -0,0 +1,440 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ //#region src/artifacts/artifacts.ts
5
+ var ArtifactError = class extends Error {
6
+ code;
7
+ constructor(code, message) {
8
+ super(message);
9
+ this.name = "ArtifactError";
10
+ this.code = code;
11
+ }
12
+ };
13
+ const TENANT_PATTERN = /^[A-Za-z0-9._-]{1,128}$/u;
14
+ const ARTIFACT_ID_PATTERN = /^[A-Za-z0-9_-]{8,128}$/u;
15
+ /** Resolves and validates the tenant scope used by stores and catalogs. */
16
+ function artifactTenantId(context) {
17
+ const tenantId = context?.tenantId ?? "default";
18
+ if (typeof tenantId !== "string" || !TENANT_PATTERN.test(tenantId) || tenantId === "." || tenantId === "..") throw new ArtifactError("ARTIFACT_INVALID_CONTEXT", "Artifact tenantId must contain only letters, numbers, dot, underscore or hyphen");
19
+ return tenantId;
20
+ }
21
+ /** Validates an artifact id before using it in a query or storage key. */
22
+ function assertArtifactId(artifactId) {
23
+ if (typeof artifactId !== "string" || !ARTIFACT_ID_PATTERN.test(artifactId)) throw new ArtifactError("ARTIFACT_INVALID_ID", "Artifact id is not valid");
24
+ }
25
+ //#endregion
26
+ //#region src/artifacts/internal.ts
27
+ const DEFAULT_MAX_BYTES = 25 * 1024 * 1024;
28
+ const MIME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*\/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$/;
29
+ function resolveLimits(options = {}) {
30
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
31
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new ArtifactError("ARTIFACT_INVALID_INPUT", "Artifact maxBytes must be a non-negative integer");
32
+ if (options.retentionMs !== void 0 && (!Number.isFinite(options.retentionMs) || options.retentionMs < 0)) throw new ArtifactError("ARTIFACT_INVALID_INPUT", "Artifact retentionMs must be a non-negative number");
33
+ return {
34
+ ...options,
35
+ maxBytes
36
+ };
37
+ }
38
+ function assertArtifactWrite(input, limits) {
39
+ if (!input || typeof input !== "object") throw new ArtifactError("ARTIFACT_INVALID_INPUT", "Artifact write input must be an object");
40
+ if (typeof input.filename !== "string" || input.filename.trim().length === 0 || input.filename.length > 255 || /[\\/\0]/u.test(input.filename)) throw new ArtifactError("ARTIFACT_INVALID_INPUT", "Artifact filename must be a non-empty display name without path separators");
41
+ if (typeof input.mimeType !== "string" || !MIME_PATTERN.test(input.mimeType)) throw new ArtifactError("ARTIFACT_INVALID_INPUT", "Artifact mimeType must be a valid MIME type");
42
+ if (!(input.bytes instanceof Uint8Array)) throw new ArtifactError("ARTIFACT_INVALID_INPUT", "Artifact bytes must be a Uint8Array");
43
+ if (input.bytes.byteLength > (limits.maxBytes ?? DEFAULT_MAX_BYTES)) throw new ArtifactError("ARTIFACT_TOO_LARGE", `Artifact exceeds the ${limits.maxBytes ?? DEFAULT_MAX_BYTES} byte limit`);
44
+ if (limits.allowedMimeTypes !== void 0 && !limits.allowedMimeTypes.some((mimeType) => mimeType.toLowerCase() === input.mimeType.toLowerCase())) throw new ArtifactError("ARTIFACT_MIME_UNSUPPORTED", `Artifact MIME type "${input.mimeType}" is not allowed`);
45
+ if (input.expiresAt !== void 0 && Number.isNaN(Date.parse(input.expiresAt))) throw new ArtifactError("ARTIFACT_INVALID_INPUT", "Artifact expiresAt must be a valid ISO date");
46
+ if (input.metadata !== void 0 && !isJsonObject(input.metadata)) throw new ArtifactError("ARTIFACT_INVALID_INPUT", "Artifact metadata must be a JSON object");
47
+ }
48
+ function createDescriptor(input, limits) {
49
+ const artifactId = randomUUID();
50
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
51
+ const expiresAt = input.expiresAt ?? (limits.retentionMs === void 0 ? void 0 : new Date(Date.now() + limits.retentionMs).toISOString());
52
+ return {
53
+ artifactId,
54
+ uri: `artifact://${artifactId}`,
55
+ filename: input.filename,
56
+ mimeType: input.mimeType,
57
+ size: input.bytes.byteLength,
58
+ sha256: createHash("sha256").update(input.bytes).digest("hex"),
59
+ createdAt,
60
+ ...expiresAt === void 0 ? {} : { expiresAt },
61
+ ...input.metadata === void 0 ? {} : { metadata: structuredClone(input.metadata) }
62
+ };
63
+ }
64
+ function assertNotAborted(context) {
65
+ context?.signal?.throwIfAborted();
66
+ }
67
+ function isExpired$1(descriptor) {
68
+ return descriptor.expiresAt !== void 0 && Date.parse(descriptor.expiresAt) <= Date.now();
69
+ }
70
+ function copyBytes(bytes) {
71
+ return new Uint8Array(bytes);
72
+ }
73
+ function parseDescriptor(value) {
74
+ let parsed;
75
+ try {
76
+ parsed = JSON.parse(value);
77
+ } catch {
78
+ throw new ArtifactError("ARTIFACT_CORRUPT", "Artifact metadata is not valid JSON");
79
+ }
80
+ if (!isJsonObject(parsed) || typeof parsed.artifactId !== "string" || typeof parsed.uri !== "string" || typeof parsed.filename !== "string" || typeof parsed.mimeType !== "string" || typeof parsed.size !== "number" || typeof parsed.sha256 !== "string" || typeof parsed.createdAt !== "string") throw new ArtifactError("ARTIFACT_CORRUPT", "Artifact metadata is malformed");
81
+ return parsed;
82
+ }
83
+ function isJsonObject(value) {
84
+ return typeof value === "object" && value !== null && !Array.isArray(value);
85
+ }
86
+ //#endregion
87
+ //#region src/artifacts/cataloged-artifact-store.ts
88
+ /**
89
+ * Composes a byte store with a queryable metadata catalog.
90
+ *
91
+ * `put` writes bytes first and then indexes metadata. If indexing fails, the
92
+ * bytes are deleted so the catalog never points at a half-written artifact.
93
+ */
94
+ var CatalogedArtifactStore = class {
95
+ #store;
96
+ #catalog;
97
+ constructor(options) {
98
+ this.#store = options.store;
99
+ this.#catalog = options.catalog;
100
+ }
101
+ async put(input, context) {
102
+ const descriptor = await this.#store.put(input, context);
103
+ try {
104
+ await this.#catalog.save(this.#record(descriptor, context), context);
105
+ } catch (error) {
106
+ await this.#store.delete?.(descriptor.artifactId, context);
107
+ throw error;
108
+ }
109
+ return descriptor;
110
+ }
111
+ async get(artifactId, context) {
112
+ assertNotAborted(context);
113
+ assertArtifactId(artifactId);
114
+ if (!await this.#catalog.get(artifactId, context)) return null;
115
+ const content = await this.#store.get(artifactId, context);
116
+ if (!content) throw new ArtifactError("ARTIFACT_CORRUPT", `Artifact "${artifactId}" is indexed but its bytes are missing`);
117
+ return content;
118
+ }
119
+ async head(artifactId, context) {
120
+ assertNotAborted(context);
121
+ assertArtifactId(artifactId);
122
+ return (await this.#catalog.get(artifactId, context))?.descriptor ?? null;
123
+ }
124
+ async delete(artifactId, context) {
125
+ assertNotAborted(context);
126
+ assertArtifactId(artifactId);
127
+ const existed = await this.#catalog.get(artifactId, context) !== null;
128
+ await this.#catalog.delete(artifactId, context);
129
+ await this.#store.delete?.(artifactId, context);
130
+ return existed;
131
+ }
132
+ async list(query = {}, context) {
133
+ assertNotAborted(context);
134
+ return this.#catalog.list(query, context);
135
+ }
136
+ async deleteByRun(runId, context) {
137
+ assertNotAborted(context);
138
+ if (typeof runId !== "string" || runId.length === 0) throw new ArtifactError("ARTIFACT_INVALID_INPUT", "Artifact runId must be non-empty");
139
+ let deleted = 0;
140
+ for (;;) {
141
+ const records = await this.#catalog.list({
142
+ runId,
143
+ includeExpired: true,
144
+ limit: 100
145
+ }, context);
146
+ if (records.length === 0) break;
147
+ for (const record of records) {
148
+ await this.delete(record.descriptor.artifactId, context);
149
+ deleted += 1;
150
+ }
151
+ }
152
+ return deleted;
153
+ }
154
+ #record(descriptor, context) {
155
+ return {
156
+ descriptor,
157
+ tenantId: artifactTenantId(context),
158
+ ...context?.runId === void 0 ? {} : { runId: context.runId },
159
+ ...context?.sessionId === void 0 ? {} : { sessionId: context.sessionId },
160
+ ...context?.actor?.id === void 0 ? {} : { actorId: context.actor.id }
161
+ };
162
+ }
163
+ };
164
+ //#endregion
165
+ //#region src/artifacts/file-system-artifact-store.ts
166
+ /**
167
+ * Local filesystem artifact store.
168
+ *
169
+ * Layout:
170
+ * <rootDir>/tenants/<tenantId>/artifacts/<artifactId>
171
+ * <rootDir>/tenants/<tenantId>/artifacts/<artifactId>.json
172
+ *
173
+ * Bytes are written before metadata, and both use an atomic rename. This is
174
+ * intended for local development and single-node deployments; use
175
+ * `S3ArtifactStore` for multi-instance production.
176
+ */
177
+ var FileSystemArtifactStore = class {
178
+ #rootDir;
179
+ #limits;
180
+ constructor(options) {
181
+ if (typeof options.rootDir !== "string" || !options.rootDir.trim()) throw new ArtifactError("ARTIFACT_INVALID_INPUT", "Artifact rootDir must be non-empty");
182
+ this.#rootDir = resolve(options.rootDir);
183
+ this.#limits = resolveLimits(options);
184
+ }
185
+ async put(input, context) {
186
+ assertNotAborted(context);
187
+ assertArtifactWrite(input, this.#limits);
188
+ const tenantId = artifactTenantId(context);
189
+ const descriptor = createDescriptor(input, this.#limits);
190
+ const dataPath = this.#dataPath(tenantId, descriptor.artifactId);
191
+ const metadataPath = `${dataPath}.json`;
192
+ await mkdir(join(this.#rootDir, "tenants", tenantId, "artifacts"), { recursive: true });
193
+ await writeAtomic(dataPath, input.bytes);
194
+ try {
195
+ await writeAtomic(metadataPath, JSON.stringify(descriptor));
196
+ } catch (error) {
197
+ await remove(dataPath);
198
+ throw error;
199
+ }
200
+ return structuredClone(descriptor);
201
+ }
202
+ async get(artifactId, context) {
203
+ assertNotAborted(context);
204
+ assertArtifactId(artifactId);
205
+ const descriptor = await this.#readDescriptor(artifactTenantId(context), artifactId);
206
+ if (!descriptor || isExpired$1(descriptor)) return null;
207
+ try {
208
+ return {
209
+ descriptor,
210
+ bytes: copyBytes(await readFile(this.#dataPath(artifactTenantId(context), artifactId)))
211
+ };
212
+ } catch (error) {
213
+ if (isNotFound(error)) return null;
214
+ throw error;
215
+ }
216
+ }
217
+ async head(artifactId, context) {
218
+ assertNotAborted(context);
219
+ assertArtifactId(artifactId);
220
+ const descriptor = await this.#readDescriptor(artifactTenantId(context), artifactId);
221
+ return descriptor && !isExpired$1(descriptor) ? descriptor : null;
222
+ }
223
+ async delete(artifactId, context) {
224
+ assertNotAborted(context);
225
+ assertArtifactId(artifactId);
226
+ const tenantId = artifactTenantId(context);
227
+ const dataPath = this.#dataPath(tenantId, artifactId);
228
+ const existed = await this.#readDescriptor(tenantId, artifactId) !== null;
229
+ await remove(dataPath);
230
+ await remove(`${dataPath}.json`);
231
+ return existed;
232
+ }
233
+ async #readDescriptor(tenantId, artifactId) {
234
+ try {
235
+ return parseDescriptor(await readFile(`${this.#dataPath(tenantId, artifactId)}.json`, "utf8"));
236
+ } catch (error) {
237
+ if (isNotFound(error)) return null;
238
+ throw error;
239
+ }
240
+ }
241
+ #dataPath(tenantId, artifactId) {
242
+ return join(this.#rootDir, "tenants", tenantId, "artifacts", artifactId);
243
+ }
244
+ };
245
+ async function writeAtomic(path, data) {
246
+ const temporary = `${path}.${randomUUID()}.tmp`;
247
+ try {
248
+ await writeFile(temporary, data);
249
+ await rename(temporary, path);
250
+ } catch (error) {
251
+ await remove(temporary);
252
+ throw error;
253
+ }
254
+ }
255
+ async function remove(path) {
256
+ try {
257
+ await unlink(path);
258
+ } catch (error) {
259
+ if (!isNotFound(error)) throw error;
260
+ }
261
+ }
262
+ function isNotFound(error) {
263
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
264
+ }
265
+ //#endregion
266
+ //#region src/artifacts/in-memory-artifact-catalog.ts
267
+ /** Volatile artifact metadata catalog for tests and local development. */
268
+ var InMemoryArtifactCatalog = class {
269
+ #records = /* @__PURE__ */ new Map();
270
+ async save(record, context) {
271
+ const tenantId = artifactTenantId(context);
272
+ if (record.tenantId !== tenantId) throw new ArtifactError("ARTIFACT_INVALID_CONTEXT", "Artifact record tenant does not match the store context");
273
+ assertArtifactId(record.descriptor.artifactId);
274
+ this.#records.set(key$1(tenantId, record.descriptor.artifactId), structuredClone(record));
275
+ }
276
+ async get(artifactId, context) {
277
+ assertArtifactId(artifactId);
278
+ const record = this.#records.get(key$1(artifactTenantId(context), artifactId));
279
+ return record && !isExpired(record) ? structuredClone(record) : null;
280
+ }
281
+ async list(query = {}, context) {
282
+ const tenantId = artifactTenantId(context);
283
+ const limit = query.limit ?? 100;
284
+ if (!Number.isSafeInteger(limit) || limit < 1) throw new ArtifactError("ARTIFACT_INVALID_INPUT", "Artifact query limit must be a positive integer");
285
+ return [...this.#records.values()].filter((record) => record.tenantId === tenantId && (query.includeExpired || !isExpired(record)) && (query.runId === void 0 || record.runId === query.runId) && (query.sessionId === void 0 || record.sessionId === query.sessionId) && (query.actorId === void 0 || record.actorId === query.actorId) && (query.mimeType === void 0 || record.descriptor.mimeType === query.mimeType)).sort((left, right) => right.descriptor.createdAt.localeCompare(left.descriptor.createdAt) || right.descriptor.artifactId.localeCompare(left.descriptor.artifactId)).slice(0, Math.min(limit, 1e3)).map((record) => structuredClone(record));
286
+ }
287
+ async delete(artifactId, context) {
288
+ assertArtifactId(artifactId);
289
+ return this.#records.delete(key$1(artifactTenantId(context), artifactId));
290
+ }
291
+ async deleteByRun(runId, context) {
292
+ if (typeof runId !== "string" || runId.length === 0) throw new ArtifactError("ARTIFACT_INVALID_INPUT", "Artifact runId must be non-empty");
293
+ const tenantId = artifactTenantId(context);
294
+ let deleted = 0;
295
+ for (const [recordKey, record] of this.#records) if (record.tenantId === tenantId && record.runId === runId) {
296
+ this.#records.delete(recordKey);
297
+ deleted += 1;
298
+ }
299
+ return deleted;
300
+ }
301
+ };
302
+ function key$1(tenantId, artifactId) {
303
+ return `${tenantId}:${artifactId}`;
304
+ }
305
+ function isExpired(record) {
306
+ return record.descriptor.expiresAt !== void 0 && Date.parse(record.descriptor.expiresAt) <= Date.now();
307
+ }
308
+ //#endregion
309
+ //#region src/artifacts/in-memory-artifact-store.ts
310
+ /** Volatile artifact store for tests and local development. */
311
+ var InMemoryArtifactStore = class {
312
+ #records = /* @__PURE__ */ new Map();
313
+ #limits;
314
+ constructor(options = {}) {
315
+ this.#limits = resolveLimits(options);
316
+ }
317
+ async put(input, context) {
318
+ assertNotAborted(context);
319
+ assertArtifactWrite(input, this.#limits);
320
+ const tenantId = artifactTenantId(context);
321
+ const descriptor = createDescriptor(input, this.#limits);
322
+ this.#records.set(key(tenantId, descriptor.artifactId), {
323
+ tenantId,
324
+ descriptor: structuredClone(descriptor),
325
+ bytes: copyBytes(input.bytes)
326
+ });
327
+ return structuredClone(descriptor);
328
+ }
329
+ async get(artifactId, context) {
330
+ assertNotAborted(context);
331
+ assertArtifactId(artifactId);
332
+ const record = this.#records.get(key(artifactTenantId(context), artifactId));
333
+ if (!record || isExpired$1(record.descriptor)) return null;
334
+ return {
335
+ descriptor: structuredClone(record.descriptor),
336
+ bytes: copyBytes(record.bytes)
337
+ };
338
+ }
339
+ async head(artifactId, context) {
340
+ assertNotAborted(context);
341
+ assertArtifactId(artifactId);
342
+ const record = this.#records.get(key(artifactTenantId(context), artifactId));
343
+ return record && !isExpired$1(record.descriptor) ? structuredClone(record.descriptor) : null;
344
+ }
345
+ async delete(artifactId, context) {
346
+ assertNotAborted(context);
347
+ assertArtifactId(artifactId);
348
+ return this.#records.delete(key(artifactTenantId(context), artifactId));
349
+ }
350
+ };
351
+ function key(tenantId, artifactId) {
352
+ return `${tenantId}:${artifactId}`;
353
+ }
354
+ //#endregion
355
+ //#region src/artifacts/s3-artifact-store.ts
356
+ /**
357
+ * Artifact store for S3-compatible object storage (S3, R2, MinIO, GCS via an
358
+ * S3 gateway, and similar).
359
+ *
360
+ * Bytes are stored at `<prefix>/<tenant>/artifacts/<artifactId>` and metadata
361
+ * in a `.json` sidecar next to them. The bucket should stay private; the host
362
+ * serves downloads through its authenticated API or short-lived signed URLs.
363
+ */
364
+ var S3ArtifactStore = class {
365
+ #client;
366
+ #prefix;
367
+ #limits;
368
+ constructor(options) {
369
+ if (!options.client) throw new ArtifactError("ARTIFACT_INVALID_INPUT", "S3 artifact client is required");
370
+ this.#client = options.client;
371
+ this.#prefix = normalizePrefix(options.prefix ?? "fevex");
372
+ this.#limits = resolveLimits(options);
373
+ }
374
+ async put(input, context) {
375
+ assertNotAborted(context);
376
+ assertArtifactWrite(input, this.#limits);
377
+ const descriptor = createDescriptor(input, this.#limits);
378
+ const dataKey = this.#key(artifactTenantId(context), descriptor.artifactId);
379
+ const metadataKey = `${dataKey}.json`;
380
+ await this.#client.putObject({
381
+ key: dataKey,
382
+ body: copyBytes(input.bytes),
383
+ contentType: descriptor.mimeType
384
+ });
385
+ try {
386
+ await this.#client.putObject({
387
+ key: metadataKey,
388
+ body: new TextEncoder().encode(JSON.stringify(descriptor)),
389
+ contentType: "application/json"
390
+ });
391
+ } catch (error) {
392
+ await this.#client.deleteObject({ key: dataKey });
393
+ throw error;
394
+ }
395
+ return structuredClone(descriptor);
396
+ }
397
+ async get(artifactId, context) {
398
+ assertNotAborted(context);
399
+ assertArtifactId(artifactId);
400
+ const descriptor = await this.#readDescriptor(artifactTenantId(context), artifactId);
401
+ if (!descriptor || isExpired$1(descriptor)) return null;
402
+ const bytes = await this.#client.getObject({ key: this.#key(artifactTenantId(context), artifactId) });
403
+ if (!bytes) throw new ArtifactError("ARTIFACT_CORRUPT", `Artifact "${artifactId}" metadata exists without bytes`);
404
+ return {
405
+ descriptor,
406
+ bytes: copyBytes(bytes)
407
+ };
408
+ }
409
+ async head(artifactId, context) {
410
+ assertNotAborted(context);
411
+ assertArtifactId(artifactId);
412
+ const descriptor = await this.#readDescriptor(artifactTenantId(context), artifactId);
413
+ return descriptor && !isExpired$1(descriptor) ? descriptor : null;
414
+ }
415
+ async delete(artifactId, context) {
416
+ assertNotAborted(context);
417
+ assertArtifactId(artifactId);
418
+ const tenantId = artifactTenantId(context);
419
+ const dataKey = this.#key(tenantId, artifactId);
420
+ const existed = await this.#readDescriptor(tenantId, artifactId) !== null;
421
+ await this.#client.deleteObject({ key: dataKey });
422
+ await this.#client.deleteObject({ key: `${dataKey}.json` });
423
+ return existed;
424
+ }
425
+ async #readDescriptor(tenantId, artifactId) {
426
+ const metadata = await this.#client.getObject({ key: `${this.#key(tenantId, artifactId)}.json` });
427
+ return metadata ? parseDescriptor(new TextDecoder().decode(metadata)) : null;
428
+ }
429
+ #key(tenantId, artifactId) {
430
+ return `${this.#prefix}/${tenantId}/artifacts/${artifactId}`;
431
+ }
432
+ };
433
+ function normalizePrefix(prefix) {
434
+ if (typeof prefix !== "string") throw new ArtifactError("ARTIFACT_INVALID_INPUT", "S3 artifact prefix must be a string");
435
+ const normalized = prefix.replace(/^\/+|\/+$/gu, "");
436
+ if (normalized.length === 0 || normalized.split("/").some((segment) => segment.length === 0 || segment === "." || segment === "..")) throw new ArtifactError("ARTIFACT_INVALID_INPUT", "S3 artifact prefix is not valid");
437
+ return normalized;
438
+ }
439
+ //#endregion
440
+ export { CatalogedArtifactStore as a, assertArtifactId as c, FileSystemArtifactStore as i, InMemoryArtifactStore as n, ArtifactError as o, InMemoryArtifactCatalog as r, artifactTenantId as s, S3ArtifactStore as t };
@@ -1,2 +1,2 @@
1
- import { a as ChannelDeliveryOptions, c as ChannelEvent, d as ChannelMessage, f as ChannelOutput, h as ChannelRunResult, i as ChannelContext, l as ChannelEventType, m as ChannelRunOptions, n as handleChannelInput, o as ChannelElicitationContext, p as ChannelRunEvent, r as ChannelAdapter, s as ChannelError, t as deliverChannelRun, u as ChannelFailurePhase } from "../index-DyyNLuwO.mjs";
1
+ import { a as ChannelDeliveryOptions, c as ChannelEvent, d as ChannelMessage, f as ChannelOutput, h as ChannelRunResult, i as ChannelContext, l as ChannelEventType, m as ChannelRunOptions, n as handleChannelInput, o as ChannelElicitationContext, p as ChannelRunEvent, r as ChannelAdapter, s as ChannelError, t as deliverChannelRun, u as ChannelFailurePhase } from "../index-DVw0zOc_.mjs";
2
2
  export { ChannelAdapter, ChannelContext, ChannelDeliveryOptions, ChannelElicitationContext, ChannelError, ChannelEvent, ChannelEventType, ChannelFailurePhase, ChannelMessage, ChannelOutput, ChannelRunEvent, ChannelRunOptions, ChannelRunResult, deliverChannelRun, handleChannelInput };
@@ -1,7 +1,7 @@
1
- import { l as JsonValue, o as ExecutionContext, t as AgentEvent } from "../types-CuoRx12Q.mjs";
2
- import { r as ModelMetadata, s as ModelUsage } from "../index-CTIwrVf9.mjs";
3
- import { l as TraceCost, t as CostCalculator } from "../index-voIZhQXB.mjs";
4
- import { t as Fevex } from "../fevex-BoPbZ-Tw.mjs";
1
+ import { l as JsonValue, o as ExecutionContext, t as AgentEvent } from "../types-B8laW1hM.mjs";
2
+ import { r as ModelMetadata, s as ModelUsage } from "../index-BgKFd9yw.mjs";
3
+ import { t as CostCalculator, u as TraceCost } from "../index-CpLDt6Rw.mjs";
4
+ import { t as Fevex } from "../fevex-B4QY5Z6e.mjs";
5
5
  //#region src/evals/canonical-json.d.ts
6
6
  /** Serializes JSON-like data with stable object-key ordering. */
7
7
  declare function serializeCanonical(value: unknown): string;
@@ -0,0 +1,119 @@
1
+ import { F as ArtifactStore, f as RunId, o as ExecutionContext, t as AgentEvent } from "./types-B8laW1hM.mjs";
2
+ import { t as ModelGateway } from "./index-BgKFd9yw.mjs";
3
+ import { B as TeamRun, C as PurgeRunsResult, F as RunStore, I as Session, J as WorkflowRun, L as SessionId, M as RunRequest, N as RunResult, S as PurgeRunsOptions, a as AgentRun, b as ListEventsOptions, f as CredentialStore, j as RunRecord, t as AgentDefinition, w as ResumeRunResolution } from "./index-CzAi2Y_Z.mjs";
4
+ import { r as ContextProvider, s as MemoryStore } from "./index-BJktlD4E.mjs";
5
+ import { i as ObservabilityStats, r as ObservabilityOptions } from "./index-CpLDt6Rw.mjs";
6
+ import { r as Sandbox } from "./index-Df84VWmN.mjs";
7
+ import { g as ToolDefinition, s as ConnectionDefinition } from "./index-5p3q2yIp.mjs";
8
+ import { r as PolicyDefinition } from "./index-CbACw6xZ.mjs";
9
+ import { n as WorkflowDefinition } from "./index-CseuTR-F.mjs";
10
+ import { t as TeamDefinition } from "./index-1cHr7KB_.mjs";
11
+ //#region src/fevex.d.ts
12
+ interface FevexAdmissionRequest {
13
+ kind: 'agent' | 'workflow' | 'team';
14
+ name: string;
15
+ request: RunRequest;
16
+ activeRuns: number;
17
+ activeTenantRuns: number;
18
+ }
19
+ /** Optional host-level admission controls applied before a top-level run starts. */
20
+ interface FevexAdmission {
21
+ /** Maximum concurrent top-level runs in this runtime. */
22
+ maxActiveRuns?: number;
23
+ /** Maximum concurrent top-level runs for one tenant. */
24
+ maxActiveRunsPerTenant?: number;
25
+ /** Defaults to a non-empty `context.attributes.tenantId` string. */
26
+ tenantKey?: (context?: ExecutionContext) => string | undefined;
27
+ /** Custom admission check, called after the built-in limits. */
28
+ authorize?: (request: FevexAdmissionRequest) => boolean | Promise<boolean>;
29
+ }
30
+ interface FevexShutdownOptions {
31
+ /**
32
+ * Maximum time to wait for active runs before cancelling them. Defaults to
33
+ * 30 seconds. Use `0` to cancel immediately. If the deadline expires,
34
+ * shutdown allows up to 5 additional seconds for forced cancellation and the
35
+ * final trace flush.
36
+ */
37
+ timeoutMs?: number;
38
+ }
39
+ interface FevexShutdownResult {
40
+ /** Runs that were still active when the grace period expired. */
41
+ cancelledRunIds: RunId[];
42
+ /** True when the grace deadline expired before all active runs finished. */
43
+ timedOut: boolean;
44
+ /** Error surfaced while flushing pending traces, if any. */
45
+ exportError?: unknown;
46
+ /** Observability counters after the shutdown flush. */
47
+ observability: ObservabilityStats;
48
+ }
49
+ interface FevexLimits {
50
+ /**
51
+ * Maximum non-control events persisted for one run. Control events such as
52
+ * pause, resume, approval and terminal transitions are always persisted.
53
+ */
54
+ maxEventsPerRun?: number;
55
+ }
56
+ /** Provider-neutral definitions and adapters used to create one Fevex runtime. */
57
+ interface FevexConfig {
58
+ models: Record<string, ModelGateway>;
59
+ agents: AgentDefinition[];
60
+ workflows?: WorkflowDefinition[];
61
+ teams?: TeamDefinition[];
62
+ tools?: ToolDefinition[];
63
+ connections?: ConnectionDefinition[];
64
+ contextProviders?: ContextProvider[];
65
+ memoryStore?: MemoryStore;
66
+ runStore?: RunStore;
67
+ credentialStore?: CredentialStore;
68
+ sandbox?: Sandbox;
69
+ /** Optional artifact store exposed to tools as `context.artifacts`. */
70
+ artifacts?: ArtifactStore;
71
+ policies?: PolicyDefinition[];
72
+ onEvent?: (event: AgentEvent) => void;
73
+ observability?: ObservabilityOptions;
74
+ admission?: FevexAdmission;
75
+ limits?: FevexLimits;
76
+ }
77
+ interface Fevex {
78
+ startAgent<TInput = unknown, TOutput = unknown>(name: string, request: RunRequest<TInput, TOutput>): Promise<AgentRun<TOutput>>;
79
+ runAgent<TInput = unknown, TOutput = unknown>(name: string, request: RunRequest<TInput, TOutput>): Promise<RunResult<TOutput>>;
80
+ streamAgent<TInput = unknown, TOutput = unknown>(name: string, request: RunRequest<TInput, TOutput>): AsyncIterable<AgentEvent>;
81
+ startWorkflow<TInput = unknown, TOutput = unknown>(name: string, request: RunRequest<TInput, TOutput>): Promise<WorkflowRun<TOutput>>;
82
+ runWorkflow<TInput = unknown, TOutput = unknown>(name: string, request: RunRequest<TInput, TOutput>): Promise<RunResult<TOutput>>;
83
+ startTeam<TInput = unknown, TOutput = unknown>(name: string, request: RunRequest<TInput, TOutput>): Promise<TeamRun<TOutput>>;
84
+ runTeam<TInput = unknown, TOutput = unknown>(name: string, request: RunRequest<TInput, TOutput>): Promise<RunResult<TOutput>>;
85
+ streamTeam<TInput = unknown, TOutput = unknown>(name: string, request: RunRequest<TInput, TOutput>): AsyncIterable<AgentEvent>;
86
+ getTeamRun<TOutput = unknown>(runId: RunId): Promise<TeamRun<TOutput> | undefined>;
87
+ getRun<TOutput = unknown>(runId: RunId): Promise<RunRecord<TOutput> | undefined>;
88
+ listEvents(runId: RunId, options?: ListEventsOptions): Promise<AgentEvent[]>;
89
+ cancelRun(runId: RunId): Promise<boolean>;
90
+ resumeRun<TOutput = unknown>(runId: RunId, resolution: ResumeRunResolution): Promise<RunRecord<TOutput>>;
91
+ recoverRun<TOutput = unknown>(runId: RunId, options: {
92
+ actor: {
93
+ id: string;
94
+ type?: string;
95
+ };
96
+ }): Promise<RunRecord<TOutput>>;
97
+ purgeRuns(options?: PurgeRunsOptions): Promise<PurgeRunsResult>;
98
+ compactSession(sessionId: SessionId, summary: string): Promise<Session>;
99
+ /** Waits for queued exports and rejects with failures accumulated since the previous flush. */
100
+ flushObservability(options?: {
101
+ timeoutMs?: number;
102
+ }): Promise<void>;
103
+ /** Cumulative trace export counters for this runtime. */
104
+ getObservabilityStats(): ObservabilityStats;
105
+ /**
106
+ * Stops admitting new work, drains or cancels active runs, and flushes trace
107
+ * exports. The host still owns closing its stores and external adapters.
108
+ */
109
+ shutdown(options?: FevexShutdownOptions): Promise<FevexShutdownResult>;
110
+ }
111
+ /**
112
+ * Validates a configuration snapshot and creates an isolated runtime.
113
+ *
114
+ * The runtime does not own external adapters; callers remain responsible for
115
+ * closing model, connection, store, and exporter resources they provide.
116
+ */
117
+ declare function createFevex(config: FevexConfig): Fevex;
118
+ //#endregion
119
+ export { FevexLimits as a, createFevex as c, FevexConfig as i, FevexAdmission as n, FevexShutdownOptions as o, FevexAdmissionRequest as r, FevexShutdownResult as s, Fevex as t };
@@ -1,7 +1,7 @@
1
- import { l as JsonValue, o as ExecutionContext, t as AgentEvent } from "../types-CuoRx12Q.mjs";
2
- import { c as ReasoningEffort } from "../index-CTIwrVf9.mjs";
3
- import { E as RunRecord, F as TeamRun, M as SessionId, U as WorkflowRun, a as AgentRun } from "../index-CaE864Kz.mjs";
4
- import { t as Fevex } from "../fevex-BoPbZ-Tw.mjs";
1
+ import { D as ArtifactCatalog, F as ArtifactStore, f as RunId, k as ArtifactDescriptor, l as JsonValue, o as ExecutionContext, t as AgentEvent } from "../types-B8laW1hM.mjs";
2
+ import { c as ReasoningEffort } from "../index-BgKFd9yw.mjs";
3
+ import { B as TeamRun, J as WorkflowRun, L as SessionId, a as AgentRun, j as RunRecord } from "../index-CzAi2Y_Z.mjs";
4
+ import { t as Fevex } from "../fevex-B4QY5Z6e.mjs";
5
5
  //#region src/http/protocol.d.ts
6
6
  declare const FEVEX_HTTP_PROTOCOL_VERSION = "3";
7
7
  declare const FEVEX_HTTP_PROTOCOL_VERSION_HEADER = "Fevex-Protocol-Version";
@@ -10,6 +10,8 @@ interface StartRunHttpRequest<TInput extends JsonValue = JsonValue> {
10
10
  model?: string;
11
11
  reasoning?: ReasoningEffort;
12
12
  sessionId?: SessionId;
13
+ /** Maximum wall-clock time for the run, including time spent paused. */
14
+ deadlineMs?: number;
13
15
  }
14
16
  type ResumeRunHttpRequest = {
15
17
  type: 'elicitation';
@@ -47,11 +49,66 @@ interface FevexProblemDetails {
47
49
  interface FevexHttpHandlerContext {
48
50
  context?: ExecutionContext;
49
51
  }
52
+ /** Host-facing resource access check. */
53
+ type FevexHttpAccess = {
54
+ action: 'start';
55
+ kind: 'agent' | 'workflow' | 'team';
56
+ name: string;
57
+ sessionId?: SessionId;
58
+ context?: ExecutionContext;
59
+ } | {
60
+ action: 'read' | 'observe' | 'cancel' | 'resume' | 'recover';
61
+ runId: RunId;
62
+ context?: ExecutionContext;
63
+ };
64
+ /** Host-facing artifact access check. */
65
+ type FevexArtifactAccess = {
66
+ action: 'read' | 'delete';
67
+ kind: 'artifact';
68
+ artifactId: string;
69
+ context?: ExecutionContext;
70
+ } | {
71
+ action: 'read';
72
+ kind: 'artifacts';
73
+ runId: RunId;
74
+ context?: ExecutionContext;
75
+ };
50
76
  interface FevexHttpHandlerOptions {
51
77
  fevex: Fevex;
52
78
  pollIntervalMs?: number;
53
79
  /** Events per SSE read (default 100, maximum 1000). */
54
80
  eventPageSize?: number;
81
+ /** Maximum concurrent SSE streams (default unlimited). */
82
+ maxConnections?: number;
83
+ /**
84
+ * Authorizes a resource operation before the handler reads or mutates a run,
85
+ * or starts a run (new or existing session). Return false for a 403 response.
86
+ * Omit to preserve the host's existing trusted-network behavior.
87
+ */
88
+ authorize?: (access: FevexHttpAccess) => boolean | Promise<boolean>;
89
+ /**
90
+ * Byte store used by the artifact routes. When omitted, artifact routes
91
+ * return 501.
92
+ */
93
+ artifacts?: ArtifactStore;
94
+ /**
95
+ * Metadata catalog used to list artifacts for a run. When omitted, the
96
+ * run-artifact listing route returns 501.
97
+ */
98
+ artifactCatalog?: ArtifactCatalog;
99
+ /**
100
+ * Authorizes artifact reads, listings and deletions. Omit to preserve the
101
+ * host's existing trusted-network behavior.
102
+ */
103
+ authorizeArtifact?: (access: FevexArtifactAccess) => boolean | Promise<boolean>;
104
+ /**
105
+ * Called after a run starts so the host can persist run and session ownership.
106
+ * If it throws, the handler cancels the new run and returns 500. A process
107
+ * crash between start and this callback leaves the run unowned; hosts must
108
+ * treat unowned runs as inaccessible. Runs created outside the HTTP handler
109
+ * are not reported.
110
+ */
111
+ onRunStarted?: (run: RunRecord, context?: ExecutionContext) => void | Promise<void>;
55
112
  onError?: (error: unknown, problem: FevexProblemDetails) => void;
56
113
  }
57
114
  type FevexHttpHandler = (request: Request, context?: FevexHttpHandlerContext) => Promise<Response>;
@@ -73,6 +130,9 @@ interface FevexHttpClient {
73
130
  resumeRun<TOutput = JsonValue>(runId: string, request: ResumeRunHttpRequest): Promise<RunRecord<TOutput>>;
74
131
  recoverRun<TOutput = JsonValue>(runId: string): Promise<RunRecord<TOutput>>;
75
132
  cancelRun(runId: string): Promise<void>;
133
+ getArtifact(artifactId: string): Promise<Response>;
134
+ listArtifacts(runId: string): Promise<ArtifactDescriptor[]>;
135
+ deleteArtifact(artifactId: string): Promise<void>;
76
136
  }
77
137
  //#endregion
78
138
  //#region src/http/transport.d.ts
@@ -87,4 +147,4 @@ declare class FevexHttpError extends Error {
87
147
  }
88
148
  declare function createFevexHttpClient(options: FevexHttpClientOptions): FevexHttpClient;
89
149
  //#endregion
90
- export { FEVEX_HTTP_PROTOCOL_VERSION, FEVEX_HTTP_PROTOCOL_VERSION_HEADER, FevexHttpClient, FevexHttpClientOptions, FevexHttpError, FevexHttpFetch, FevexHttpHandler, FevexHttpHandlerContext, FevexHttpHandlerOptions, FevexProblemDetails, ObserveRunOptions, ResumeRunHttpRequest, StartRunHttpRequest, createFevexHttpClient, createFevexHttpHandler };
150
+ export { FEVEX_HTTP_PROTOCOL_VERSION, FEVEX_HTTP_PROTOCOL_VERSION_HEADER, FevexArtifactAccess, FevexHttpAccess, FevexHttpClient, FevexHttpClientOptions, FevexHttpError, FevexHttpFetch, FevexHttpHandler, FevexHttpHandlerContext, FevexHttpHandlerOptions, FevexProblemDetails, ObserveRunOptions, ResumeRunHttpRequest, StartRunHttpRequest, createFevexHttpClient, createFevexHttpHandler };