@fevex/core 0.1.0-alpha.1 → 0.1.0-alpha.10
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 +771 -13
- package/dist/agents/index.d.mts +2 -2
- package/dist/agents/index.mjs +1 -1
- package/dist/artifacts/index.d.mts +2 -0
- package/dist/artifacts/index.mjs +2 -0
- package/dist/artifacts-gUzemET7.mjs +440 -0
- package/dist/channels/index.d.mts +2 -0
- package/dist/channels/index.mjs +192 -0
- package/dist/evals/index.d.mts +19 -11
- package/dist/evals/index.mjs +125 -122
- package/dist/fevex-_KOsOsB8.d.mts +129 -0
- package/dist/http/index.d.mts +150 -0
- package/dist/http/index.mjs +692 -0
- package/dist/index-26ilbzAs.d.mts +50 -0
- package/dist/index-B8EcvWeN.d.mts +103 -0
- package/dist/index-BEqYWean.d.mts +118 -0
- package/dist/index-Btth7rdL.d.mts +58 -0
- package/dist/{index-QTkd6a7C.d.mts → index-CFzrvDQ0.d.mts} +4 -3
- package/dist/index-CIHWc5V1.d.mts +116 -0
- package/dist/index-D7r0puBi.d.mts +64 -0
- package/dist/index-DHcfIfh-.d.mts +224 -0
- package/dist/index-DUhNa7IR.d.mts +73 -0
- package/dist/index-lCCFokc6.d.mts +617 -0
- package/dist/index.d.mts +22 -10
- package/dist/index.mjs +4178 -671
- package/dist/knowledge/index.d.mts +2 -0
- package/dist/knowledge/index.mjs +289 -0
- package/dist/model-stream-JQdHDDcj.mjs +131 -0
- package/dist/models/index.d.mts +2 -46
- package/dist/observability/index.d.mts +2 -2
- package/dist/policies/index.d.mts +1 -1
- package/dist/policies/index.mjs +1 -1
- package/dist/run-error-Cr17wPGH.mjs +23 -0
- package/dist/{model-stream-u2xMsEaf.mjs → run-support-CmZ6Xo6H.mjs} +41 -130
- package/dist/runtime/index.d.mts +2 -2
- package/dist/runtime/index.mjs +271 -9
- package/dist/sandbox/index.d.mts +2 -0
- package/dist/sandbox/index.mjs +147 -0
- package/dist/teams/index.d.mts +2 -0
- package/dist/teams/index.mjs +2 -0
- package/dist/teams-Ctz676G4.mjs +82 -0
- package/dist/testing/index.d.mts +39 -9
- package/dist/testing/index.mjs +512 -28
- package/dist/tools/index.d.mts +3 -3
- package/dist/tools/index.mjs +442 -2
- package/dist/types-CckDlhYo.d.mts +575 -0
- package/dist/workflows/index.d.mts +2 -0
- package/dist/workflows/index.mjs +6 -0
- package/package.json +31 -3
- package/dist/exact-definition-NHBBFlFR.d.mts +0 -4
- package/dist/fevex-Dz6GBz9o.d.mts +0 -32
- package/dist/index-BzxHfyYV.d.mts +0 -23
- package/dist/index-CZGyhNIQ.d.mts +0 -47
- package/dist/index-C_Zpr13f.d.mts +0 -124
- package/dist/index-DGWOGXzc.d.mts +0 -181
- package/dist/index-ojCY3lwm.d.mts +0 -79
package/dist/agents/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as AgentLimits, r as defineAgent, t as AgentDefinition } from "../index-
|
|
2
|
-
export { AgentDefinition, AgentLimits, defineAgent };
|
|
1
|
+
import { a as ToolSearchOptions, i as ToolSearchCheckpointOptions, n as AgentLimits, o as ToolSearchSemanticOptions, r as ContextBudget, s as defineAgent, t as AgentDefinition } from "../index-lCCFokc6.mjs";
|
|
2
|
+
export { AgentDefinition, AgentLimits, ContextBudget, ToolSearchCheckpointOptions, ToolSearchOptions, ToolSearchSemanticOptions, defineAgent };
|
package/dist/agents/index.mjs
CHANGED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { A as ArtifactError, C as FileSystemArtifactStore, D as ArtifactCatalog, E as CatalogedArtifactStoreOptions, F as ArtifactStore, I as ArtifactStoreContext, L as ArtifactWrite, M as ArtifactId, N as ArtifactQuery, O as ArtifactContent, P as ArtifactRecord, R as artifactTenantId, S as InMemoryArtifactCatalog, T as CatalogedArtifactStore, _ as S3ArtifactStore, b as InMemoryArtifactStore, j as ArtifactErrorCode, k as ArtifactDescriptor, v as S3ArtifactStoreOptions, w as FileSystemArtifactStoreOptions, x as InMemoryArtifactStoreOptions, y as S3ObjectClient, z as assertArtifactId } from "../types-CckDlhYo.mjs";
|
|
2
|
+
export { ArtifactCatalog, ArtifactContent, ArtifactDescriptor, ArtifactError, ArtifactErrorCode, ArtifactId, ArtifactQuery, ArtifactRecord, ArtifactStore, ArtifactStoreContext, ArtifactWrite, CatalogedArtifactStore, CatalogedArtifactStoreOptions, FileSystemArtifactStore, FileSystemArtifactStoreOptions, InMemoryArtifactCatalog, InMemoryArtifactStore, InMemoryArtifactStoreOptions, S3ArtifactStore, S3ArtifactStoreOptions, S3ObjectClient, artifactTenantId, assertArtifactId };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as CatalogedArtifactStore, c as assertArtifactId, i as FileSystemArtifactStore, n as InMemoryArtifactStore, o as ArtifactError, r as InMemoryArtifactCatalog, s as artifactTenantId, t as S3ArtifactStore } from "../artifacts-gUzemET7.mjs";
|
|
2
|
+
export { ArtifactError, CatalogedArtifactStore, FileSystemArtifactStore, InMemoryArtifactCatalog, InMemoryArtifactStore, S3ArtifactStore, artifactTenantId, assertArtifactId };
|
|
@@ -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 };
|
|
@@ -0,0 +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-B8EcvWeN.mjs";
|
|
2
|
+
export { ChannelAdapter, ChannelContext, ChannelDeliveryOptions, ChannelElicitationContext, ChannelError, ChannelEvent, ChannelEventType, ChannelFailurePhase, ChannelMessage, ChannelOutput, ChannelRunEvent, ChannelRunOptions, ChannelRunResult, deliverChannelRun, handleChannelInput };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { n as RunPausedError } from "../run-error-Cr17wPGH.mjs";
|
|
2
|
+
//#region src/channels/channel.ts
|
|
3
|
+
var ChannelError = class extends Error {
|
|
4
|
+
code;
|
|
5
|
+
safeMessage;
|
|
6
|
+
name = "ChannelError";
|
|
7
|
+
runId;
|
|
8
|
+
constructor(code, safeMessage, options) {
|
|
9
|
+
super(safeMessage, options);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.safeMessage = safeMessage;
|
|
12
|
+
this.runId = options?.runId;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/channels/handle-channel-input.ts
|
|
17
|
+
/** Parses one channel input, runs the configured agent, and delivers its result or elicitation. */
|
|
18
|
+
async function handleChannelInput(input, options) {
|
|
19
|
+
const channelEvents = [];
|
|
20
|
+
const emit = (event) => {
|
|
21
|
+
channelEvents.push(event);
|
|
22
|
+
options.onEvent?.(event);
|
|
23
|
+
};
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = await options.adapter.parse(input, { signal: options.signal });
|
|
27
|
+
} catch (cause) {
|
|
28
|
+
const error = channelError("CHANNEL_PARSE_FAILED", "Channel parse failed", cause);
|
|
29
|
+
emit(failed(options.adapter.name, "parse", error));
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
if (parsed === null) return {
|
|
33
|
+
ignored: true,
|
|
34
|
+
events: []
|
|
35
|
+
};
|
|
36
|
+
const message = parsed;
|
|
37
|
+
emit(received(options.adapter.name, message));
|
|
38
|
+
let runId;
|
|
39
|
+
try {
|
|
40
|
+
const sessionId = await options.resolveSessionId?.(message);
|
|
41
|
+
runId = (await options.fevex.runAgent(options.agentName, {
|
|
42
|
+
input: message.content,
|
|
43
|
+
...sessionId ? { sessionId } : {},
|
|
44
|
+
context: channelContext(options.adapter.name, message),
|
|
45
|
+
signal: options.signal
|
|
46
|
+
})).runId;
|
|
47
|
+
} catch (cause) {
|
|
48
|
+
if (cause instanceof RunPausedError && cause.pause.type === "elicitation") runId = cause.runId;
|
|
49
|
+
else {
|
|
50
|
+
const error = channelError("CHANNEL_RUN_FAILED", "Channel run failed", cause);
|
|
51
|
+
emit(failed(options.adapter.name, "run", error, message));
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const delivered = await deliverChannelRun(runId, message, options);
|
|
56
|
+
return {
|
|
57
|
+
...delivered,
|
|
58
|
+
events: [channelEvents[0], ...delivered.events]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Delivers a stored agent result or elicitation without executing the agent again. */
|
|
62
|
+
async function deliverChannelRun(runId, message, options) {
|
|
63
|
+
options.signal?.throwIfAborted();
|
|
64
|
+
const run = await options.fevex.getRun(runId);
|
|
65
|
+
const elicitation = run?.status === "paused" && run.pause?.type === "elicitation" ? run.pause.request : void 0;
|
|
66
|
+
if (!run || run.kind === "workflow" || run.kind === "team" || run.status !== "completed" && !elicitation) {
|
|
67
|
+
const error = channelError("CHANNEL_RUN_FAILED", "Channel run is not ready for delivery", void 0, runId);
|
|
68
|
+
options.onEvent?.(failed(options.adapter.name, "run", error, message));
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
const output = {
|
|
72
|
+
deliveryId: elicitation ? `elicitation:${runId}:${elicitation.id}` : message.deliveryId,
|
|
73
|
+
conversationId: message.conversationId,
|
|
74
|
+
...message.threadId ? { threadId: message.threadId } : {},
|
|
75
|
+
content: elicitation ? elicitation.prompt : stringifyOutput(run.output ?? null),
|
|
76
|
+
metadata: {
|
|
77
|
+
runId,
|
|
78
|
+
sessionId: run.sessionId,
|
|
79
|
+
...elicitation ? { requestId: elicitation.id } : {}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
let delivery;
|
|
83
|
+
try {
|
|
84
|
+
if (elicitation) {
|
|
85
|
+
if (!options.adapter.deliverElicitation) throw channelError("CHANNEL_ELICITATION_UNSUPPORTED", "Channel does not support elicitation delivery", void 0, runId);
|
|
86
|
+
if (elicitation.expiresAt && Date.now() > Date.parse(elicitation.expiresAt)) throw channelError("CHANNEL_ELICITATION_EXPIRED", "Channel elicitation has expired", void 0, runId);
|
|
87
|
+
delivery = await options.adapter.deliverElicitation(elicitation, {
|
|
88
|
+
signal: options.signal,
|
|
89
|
+
deliveryId: output.deliveryId,
|
|
90
|
+
runId,
|
|
91
|
+
sessionId: run.sessionId,
|
|
92
|
+
conversationId: message.conversationId,
|
|
93
|
+
...message.threadId ? { threadId: message.threadId } : {}
|
|
94
|
+
});
|
|
95
|
+
} else delivery = await options.adapter.deliver(output, { signal: options.signal });
|
|
96
|
+
} catch (cause) {
|
|
97
|
+
const error = cause instanceof ChannelError ? channelError(cause.code, cause.safeMessage, cause, runId) : channelError("CHANNEL_DELIVERY_FAILED", "Channel delivery failed", cause, runId);
|
|
98
|
+
options.onEvent?.({
|
|
99
|
+
...failed(options.adapter.name, "deliver", error, {
|
|
100
|
+
...message,
|
|
101
|
+
deliveryId: output.deliveryId
|
|
102
|
+
}),
|
|
103
|
+
...elicitation ? { requestId: elicitation.id } : {}
|
|
104
|
+
});
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
const deliveredEvent = {
|
|
108
|
+
...delivered(options.adapter.name, {
|
|
109
|
+
...message,
|
|
110
|
+
deliveryId: output.deliveryId
|
|
111
|
+
}),
|
|
112
|
+
runId,
|
|
113
|
+
...elicitation ? { requestId: elicitation.id } : {}
|
|
114
|
+
};
|
|
115
|
+
options.onEvent?.(deliveredEvent);
|
|
116
|
+
return {
|
|
117
|
+
ignored: false,
|
|
118
|
+
message,
|
|
119
|
+
run,
|
|
120
|
+
output,
|
|
121
|
+
delivery,
|
|
122
|
+
...elicitation ? { elicitation } : {},
|
|
123
|
+
events: [...await options.fevex.listEvents(runId), deliveredEvent]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function channelContext(adapter, message) {
|
|
127
|
+
return {
|
|
128
|
+
namespace: adapter,
|
|
129
|
+
...message.actor ? { actor: message.actor } : {},
|
|
130
|
+
attributes: {
|
|
131
|
+
...message.metadata ?? {},
|
|
132
|
+
channel: adapter,
|
|
133
|
+
messageId: message.id,
|
|
134
|
+
deliveryId: message.deliveryId,
|
|
135
|
+
conversationId: message.conversationId,
|
|
136
|
+
...message.threadId ? { threadId: message.threadId } : {}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function received(adapter, message) {
|
|
141
|
+
return {
|
|
142
|
+
type: "channel.received",
|
|
143
|
+
id: crypto.randomUUID(),
|
|
144
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
145
|
+
adapter,
|
|
146
|
+
messageId: message.id,
|
|
147
|
+
deliveryId: message.deliveryId,
|
|
148
|
+
conversationId: message.conversationId,
|
|
149
|
+
...message.threadId ? { threadId: message.threadId } : {},
|
|
150
|
+
...message.actor ? { actor: message.actor } : {}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function delivered(adapter, message) {
|
|
154
|
+
return {
|
|
155
|
+
type: "channel.delivered",
|
|
156
|
+
id: crypto.randomUUID(),
|
|
157
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
158
|
+
adapter,
|
|
159
|
+
messageId: message.id,
|
|
160
|
+
deliveryId: message.deliveryId,
|
|
161
|
+
conversationId: message.conversationId,
|
|
162
|
+
...message.threadId ? { threadId: message.threadId } : {}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function failed(adapter, phase, error, message) {
|
|
166
|
+
return {
|
|
167
|
+
type: "channel.failed",
|
|
168
|
+
id: crypto.randomUUID(),
|
|
169
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
170
|
+
adapter,
|
|
171
|
+
phase,
|
|
172
|
+
error: error.safeMessage,
|
|
173
|
+
...error.runId ? { runId: error.runId } : {},
|
|
174
|
+
...message ? {
|
|
175
|
+
messageId: message.id,
|
|
176
|
+
deliveryId: message.deliveryId,
|
|
177
|
+
conversationId: message.conversationId,
|
|
178
|
+
...message.threadId ? { threadId: message.threadId } : {}
|
|
179
|
+
} : {}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function channelError(code, safeMessage, cause, runId) {
|
|
183
|
+
return new ChannelError(code, safeMessage, {
|
|
184
|
+
...cause === void 0 ? {} : { cause },
|
|
185
|
+
...runId ? { runId } : {}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
function stringifyOutput(value) {
|
|
189
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
192
|
+
export { ChannelError, deliverChannelRun, handleChannelInput };
|