@fevex/core 0.1.0-alpha.0 → 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 +1110 -52
- package/dist/agents/index.d.mts +2 -23
- 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 +153 -0
- package/dist/evals/index.mjs +422 -0
- 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-CFzrvDQ0.d.mts +21 -0
- 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 +39 -15
- package/dist/index.mjs +4962 -208
- 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 -27
- package/dist/observability/index.d.mts +2 -0
- package/dist/observability/index.mjs +1 -0
- package/dist/policies/index.d.mts +2 -0
- package/dist/policies/index.mjs +6 -0
- package/dist/run-error-Cr17wPGH.mjs +23 -0
- package/dist/run-support-CmZ6Xo6H.mjs +162 -0
- package/dist/runtime/index.d.mts +2 -17
- package/dist/runtime/index.mjs +354 -1
- 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 +40 -8
- package/dist/testing/index.mjs +668 -23
- package/dist/tools/index.d.mts +3 -31
- 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 +43 -3
- package/dist/exact-definition-NHBBFlFR.d.mts +0 -4
- package/dist/index-D_RBJmVt.d.mts +0 -44
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
import { o as ArtifactError } from "../artifacts-gUzemET7.mjs";
|
|
2
|
+
import { t as FevexRunError } from "../run-error-Cr17wPGH.mjs";
|
|
3
|
+
import { t as MAX_DEADLINE_MS } from "../run-support-CmZ6Xo6H.mjs";
|
|
4
|
+
//#region src/http/protocol.ts
|
|
5
|
+
const FEVEX_HTTP_PROTOCOL_VERSION = "3";
|
|
6
|
+
const FEVEX_HTTP_PROTOCOL_VERSION_HEADER = "Fevex-Protocol-Version";
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/http/transport.ts
|
|
9
|
+
const JSON_TYPE = "application/json";
|
|
10
|
+
const PROBLEM_TYPE = "application/problem+json";
|
|
11
|
+
const SSE_TYPE = "text/event-stream";
|
|
12
|
+
const FEVEX_RUN_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
13
|
+
"INVALID_CURSOR",
|
|
14
|
+
"AGENT_NOT_FOUND",
|
|
15
|
+
"WORKFLOW_NOT_FOUND",
|
|
16
|
+
"TEAM_NOT_FOUND",
|
|
17
|
+
"MODEL_NOT_FOUND",
|
|
18
|
+
"SESSION_NOT_FOUND",
|
|
19
|
+
"DURABLE_STORE_REQUIRED",
|
|
20
|
+
"RUN_CONFLICT",
|
|
21
|
+
"RUN_NOT_RESUMABLE",
|
|
22
|
+
"RUN_NOT_RECOVERABLE",
|
|
23
|
+
"CHECKPOINT_UNSUPPORTED",
|
|
24
|
+
"RUN_DEFINITION_CHANGED",
|
|
25
|
+
"POLICY_DENIED",
|
|
26
|
+
"ADMISSION_DENIED",
|
|
27
|
+
"RUNTIME_CLOSED",
|
|
28
|
+
"CREDENTIAL_NOT_FOUND",
|
|
29
|
+
"SANDBOX_REQUIRED",
|
|
30
|
+
"ELICITATION_INVALID",
|
|
31
|
+
"APPROVAL_INVALID",
|
|
32
|
+
"RUN_PAUSED",
|
|
33
|
+
"TOOL_EXECUTION_UNKNOWN"
|
|
34
|
+
]);
|
|
35
|
+
/** Exposes a Fevex instance through the versioned HTTP protocol. */
|
|
36
|
+
function createFevexHttpHandler(options) {
|
|
37
|
+
const pollIntervalMs = options.pollIntervalMs ?? 100;
|
|
38
|
+
const eventPageSize = options.eventPageSize ?? 100;
|
|
39
|
+
const maxConnections = options.maxConnections;
|
|
40
|
+
if (!Number.isSafeInteger(eventPageSize) || eventPageSize < 1 || eventPageSize > 1e3) throw new TypeError("eventPageSize must be an integer between 1 and 1000");
|
|
41
|
+
if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 1) throw new TypeError("pollIntervalMs must be a positive number");
|
|
42
|
+
if (maxConnections !== void 0 && (!Number.isSafeInteger(maxConnections) || maxConnections < 1)) throw new TypeError("maxConnections must be a positive safe integer");
|
|
43
|
+
let activeConnections = 0;
|
|
44
|
+
const authorize = async (access) => {
|
|
45
|
+
if (options.authorize && !await options.authorize(access)) throw new HttpProtocolError(403, "FORBIDDEN", "Forbidden");
|
|
46
|
+
};
|
|
47
|
+
const authorizeArtifact = async (access) => {
|
|
48
|
+
if (options.authorizeArtifact && !await options.authorizeArtifact(access)) throw new HttpProtocolError(403, "FORBIDDEN", "Forbidden");
|
|
49
|
+
};
|
|
50
|
+
return async (request, handlerContext = {}) => {
|
|
51
|
+
const url = new URL(request.url);
|
|
52
|
+
const path = url.pathname;
|
|
53
|
+
const commitStart = async (run) => {
|
|
54
|
+
try {
|
|
55
|
+
await options.onRunStarted?.(run, handlerContext.context);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
await options.fevex.cancelRun(run.id).catch(() => {});
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
try {
|
|
62
|
+
const start = path.match(/^\/v1\/agents\/([^/]+)\/runs$/);
|
|
63
|
+
if (start && request.method === "POST") {
|
|
64
|
+
const body = await readJson(request);
|
|
65
|
+
if (!hasOwn(body, "input")) throw badRequest("input is required");
|
|
66
|
+
const name = decodeURIComponent(start[1]);
|
|
67
|
+
const sessionId = optionalId(body.sessionId, "sessionId");
|
|
68
|
+
const model = optionalId(body.model, "model");
|
|
69
|
+
const reasoning = optionalReasoning(body.reasoning);
|
|
70
|
+
const deadlineMs = optionalDeadline(body.deadlineMs, "deadlineMs");
|
|
71
|
+
await authorize({
|
|
72
|
+
action: "start",
|
|
73
|
+
kind: "agent",
|
|
74
|
+
name,
|
|
75
|
+
sessionId,
|
|
76
|
+
context: handlerContext.context
|
|
77
|
+
});
|
|
78
|
+
const run = await options.fevex.startAgent(name, {
|
|
79
|
+
input: body.input,
|
|
80
|
+
...model ? { model } : {},
|
|
81
|
+
...reasoning === void 0 ? {} : { reasoning },
|
|
82
|
+
...deadlineMs === void 0 ? {} : { deadlineMs },
|
|
83
|
+
...sessionId ? { sessionId } : {},
|
|
84
|
+
...handlerContext.context ? { context: handlerContext.context } : {}
|
|
85
|
+
});
|
|
86
|
+
await commitStart(run);
|
|
87
|
+
return json(run, 202);
|
|
88
|
+
}
|
|
89
|
+
const startWorkflow = path.match(/^\/v1\/workflows\/([^/]+)\/runs$/);
|
|
90
|
+
if (startWorkflow && request.method === "POST") {
|
|
91
|
+
const body = await readJson(request);
|
|
92
|
+
if (!hasOwn(body, "input")) throw badRequest("input is required");
|
|
93
|
+
const name = decodeURIComponent(startWorkflow[1]);
|
|
94
|
+
const sessionId = optionalId(body.sessionId, "sessionId");
|
|
95
|
+
const model = optionalId(body.model, "model");
|
|
96
|
+
const reasoning = optionalReasoning(body.reasoning);
|
|
97
|
+
const deadlineMs = optionalDeadline(body.deadlineMs, "deadlineMs");
|
|
98
|
+
await authorize({
|
|
99
|
+
action: "start",
|
|
100
|
+
kind: "workflow",
|
|
101
|
+
name,
|
|
102
|
+
sessionId,
|
|
103
|
+
context: handlerContext.context
|
|
104
|
+
});
|
|
105
|
+
const run = await options.fevex.startWorkflow(name, {
|
|
106
|
+
input: body.input,
|
|
107
|
+
...model ? { model } : {},
|
|
108
|
+
...reasoning === void 0 ? {} : { reasoning },
|
|
109
|
+
...deadlineMs === void 0 ? {} : { deadlineMs },
|
|
110
|
+
...sessionId ? { sessionId } : {},
|
|
111
|
+
...handlerContext.context ? { context: handlerContext.context } : {}
|
|
112
|
+
});
|
|
113
|
+
await commitStart(run);
|
|
114
|
+
return json(run, 202);
|
|
115
|
+
}
|
|
116
|
+
const startTeam = path.match(/^\/v1\/teams\/([^/]+)\/runs$/);
|
|
117
|
+
if (startTeam && request.method === "POST") {
|
|
118
|
+
const body = await readJson(request);
|
|
119
|
+
if (!hasOwn(body, "input")) throw badRequest("Run input is required");
|
|
120
|
+
const name = decodeURIComponent(startTeam[1]);
|
|
121
|
+
const sessionId = optionalId(body.sessionId, "sessionId");
|
|
122
|
+
const model = optionalId(body.model, "model");
|
|
123
|
+
const reasoning = optionalReasoning(body.reasoning);
|
|
124
|
+
const deadlineMs = optionalDeadline(body.deadlineMs, "deadlineMs");
|
|
125
|
+
await authorize({
|
|
126
|
+
action: "start",
|
|
127
|
+
kind: "team",
|
|
128
|
+
name,
|
|
129
|
+
sessionId,
|
|
130
|
+
context: handlerContext.context
|
|
131
|
+
});
|
|
132
|
+
const run = await options.fevex.startTeam(name, {
|
|
133
|
+
input: body.input,
|
|
134
|
+
...model ? { model } : {},
|
|
135
|
+
...reasoning === void 0 ? {} : { reasoning },
|
|
136
|
+
...deadlineMs === void 0 ? {} : { deadlineMs },
|
|
137
|
+
...sessionId ? { sessionId } : {},
|
|
138
|
+
...handlerContext.context ? { context: handlerContext.context } : {}
|
|
139
|
+
});
|
|
140
|
+
await commitStart(run);
|
|
141
|
+
return json(run, 202);
|
|
142
|
+
}
|
|
143
|
+
const events = path.match(/^\/v1\/runs\/([^/]+)\/events$/);
|
|
144
|
+
if (events && request.method === "GET") {
|
|
145
|
+
const runId = decodeURIComponent(events[1]);
|
|
146
|
+
await authorize({
|
|
147
|
+
action: "observe",
|
|
148
|
+
runId,
|
|
149
|
+
context: handlerContext.context
|
|
150
|
+
});
|
|
151
|
+
if (!await options.fevex.getRun(runId)) throw notFound("RUN_NOT_FOUND", "Run not found");
|
|
152
|
+
const after = request.headers.get("last-event-id")?.trim() || void 0;
|
|
153
|
+
const initial = await options.fevex.listEvents(runId, {
|
|
154
|
+
after,
|
|
155
|
+
limit: eventPageSize
|
|
156
|
+
});
|
|
157
|
+
if (maxConnections !== void 0 && activeConnections >= maxConnections) throw new HttpProtocolError(429, "CONNECTION_LIMIT", "Too many event streams");
|
|
158
|
+
activeConnections += 1;
|
|
159
|
+
let released = false;
|
|
160
|
+
const release = () => {
|
|
161
|
+
if (released) return;
|
|
162
|
+
released = true;
|
|
163
|
+
activeConnections -= 1;
|
|
164
|
+
};
|
|
165
|
+
const disconnected = new AbortController();
|
|
166
|
+
const iterator = observeEvents(options.fevex, runId, initial, after, pollIntervalMs, AbortSignal.any([request.signal, disconnected.signal]), eventPageSize);
|
|
167
|
+
return versioned(new Response(iteratorStream(iterator, () => disconnected.abort(), release), { headers: {
|
|
168
|
+
"content-type": `${SSE_TYPE}; charset=utf-8`,
|
|
169
|
+
"cache-control": "no-cache, no-transform"
|
|
170
|
+
} }));
|
|
171
|
+
}
|
|
172
|
+
const resume = path.match(/^\/v1\/runs\/([^/]+)\/resume$/);
|
|
173
|
+
if (resume && request.method === "POST") {
|
|
174
|
+
const runId = decodeURIComponent(resume[1]);
|
|
175
|
+
const actor = handlerContext.context?.actor;
|
|
176
|
+
if (!actor?.id?.trim()) throw new HttpProtocolError(401, "ACTOR_REQUIRED", "Authenticated actor is required");
|
|
177
|
+
await authorize({
|
|
178
|
+
action: "resume",
|
|
179
|
+
runId,
|
|
180
|
+
context: handlerContext.context
|
|
181
|
+
});
|
|
182
|
+
if (!await options.fevex.getRun(runId)) throw notFound("RUN_NOT_FOUND", "Run not found");
|
|
183
|
+
const resolution = readResolution(await readJson(request), actor);
|
|
184
|
+
return json(await options.fevex.resumeRun(runId, resolution), 202);
|
|
185
|
+
}
|
|
186
|
+
const recover = path.match(/^\/v1\/runs\/([^/]+)\/recover$/);
|
|
187
|
+
if (recover && request.method === "POST") {
|
|
188
|
+
const runId = decodeURIComponent(recover[1]);
|
|
189
|
+
const actor = handlerContext.context?.actor;
|
|
190
|
+
if (!actor?.id?.trim()) throw new HttpProtocolError(401, "ACTOR_REQUIRED", "Authenticated actor is required");
|
|
191
|
+
await authorize({
|
|
192
|
+
action: "recover",
|
|
193
|
+
runId,
|
|
194
|
+
context: handlerContext.context
|
|
195
|
+
});
|
|
196
|
+
if (!await options.fevex.getRun(runId)) throw notFound("RUN_NOT_FOUND", "Run not found");
|
|
197
|
+
return json(await options.fevex.recoverRun(runId, { actor }), 202);
|
|
198
|
+
}
|
|
199
|
+
const runArtifacts = path.match(/^\/v1\/runs\/([^/]+)\/artifacts$/);
|
|
200
|
+
if (runArtifacts && request.method === "GET") {
|
|
201
|
+
const runId = decodeURIComponent(runArtifacts[1]);
|
|
202
|
+
await authorizeArtifact({
|
|
203
|
+
action: "read",
|
|
204
|
+
kind: "artifacts",
|
|
205
|
+
runId,
|
|
206
|
+
context: handlerContext.context
|
|
207
|
+
});
|
|
208
|
+
if (!options.artifactCatalog) throw new HttpProtocolError(501, "ARTIFACT_CATALOG_NOT_CONFIGURED", "Artifact catalog is not configured");
|
|
209
|
+
if (!await options.fevex.getRun(runId)) throw notFound("RUN_NOT_FOUND", "Run not found");
|
|
210
|
+
const limit = optionalLimit(url.searchParams.get("limit"));
|
|
211
|
+
return json((await options.artifactCatalog.list({
|
|
212
|
+
runId,
|
|
213
|
+
...limit === void 0 ? {} : { limit }
|
|
214
|
+
}, artifactContext(handlerContext, request))).map(({ descriptor }) => descriptor));
|
|
215
|
+
}
|
|
216
|
+
const artifactRoute = path.match(/^\/v1\/artifacts\/([^/]+)$/);
|
|
217
|
+
if (artifactRoute && (request.method === "GET" || request.method === "HEAD")) {
|
|
218
|
+
const artifactId = decodeURIComponent(artifactRoute[1]);
|
|
219
|
+
await authorizeArtifact({
|
|
220
|
+
action: "read",
|
|
221
|
+
kind: "artifact",
|
|
222
|
+
artifactId,
|
|
223
|
+
context: handlerContext.context
|
|
224
|
+
});
|
|
225
|
+
if (!options.artifacts) throw new HttpProtocolError(501, "ARTIFACTS_NOT_CONFIGURED", "Artifact store is not configured");
|
|
226
|
+
const context = artifactContext(handlerContext, request);
|
|
227
|
+
if (request.method === "HEAD" && options.artifacts.head) {
|
|
228
|
+
const descriptor = await options.artifacts.head(artifactId, context);
|
|
229
|
+
if (!descriptor) throw notFound("ARTIFACT_NOT_FOUND", "Artifact not found");
|
|
230
|
+
return artifactResponse(descriptor, null);
|
|
231
|
+
}
|
|
232
|
+
const content = await options.artifacts.get(artifactId, context);
|
|
233
|
+
if (!content) throw notFound("ARTIFACT_NOT_FOUND", "Artifact not found");
|
|
234
|
+
return artifactResponse(content.descriptor, request.method === "HEAD" ? null : content.bytes);
|
|
235
|
+
}
|
|
236
|
+
if (artifactRoute && request.method === "DELETE") {
|
|
237
|
+
const artifactId = decodeURIComponent(artifactRoute[1]);
|
|
238
|
+
await authorizeArtifact({
|
|
239
|
+
action: "delete",
|
|
240
|
+
kind: "artifact",
|
|
241
|
+
artifactId,
|
|
242
|
+
context: handlerContext.context
|
|
243
|
+
});
|
|
244
|
+
if (!options.artifacts) throw new HttpProtocolError(501, "ARTIFACTS_NOT_CONFIGURED", "Artifact store is not configured");
|
|
245
|
+
if (!options.artifacts.delete) throw new HttpProtocolError(501, "ARTIFACT_DELETE_UNSUPPORTED", "Artifact store does not support deletion");
|
|
246
|
+
if (!await options.artifacts.delete(artifactId, artifactContext(handlerContext, request))) throw notFound("ARTIFACT_NOT_FOUND", "Artifact not found");
|
|
247
|
+
return versioned(new Response(null, { status: 204 }));
|
|
248
|
+
}
|
|
249
|
+
const run = path.match(/^\/v1\/runs\/([^/]+)$/);
|
|
250
|
+
if (run && request.method === "GET") {
|
|
251
|
+
const runId = decodeURIComponent(run[1]);
|
|
252
|
+
await authorize({
|
|
253
|
+
action: "read",
|
|
254
|
+
runId,
|
|
255
|
+
context: handlerContext.context
|
|
256
|
+
});
|
|
257
|
+
const value = await options.fevex.getRun(runId);
|
|
258
|
+
if (!value) throw notFound("RUN_NOT_FOUND", "Run not found");
|
|
259
|
+
return json(value);
|
|
260
|
+
}
|
|
261
|
+
if (run && request.method === "DELETE") {
|
|
262
|
+
const runId = decodeURIComponent(run[1]);
|
|
263
|
+
await authorize({
|
|
264
|
+
action: "cancel",
|
|
265
|
+
runId,
|
|
266
|
+
context: handlerContext.context
|
|
267
|
+
});
|
|
268
|
+
const value = await options.fevex.getRun(runId);
|
|
269
|
+
if (!value) throw notFound("RUN_NOT_FOUND", "Run not found");
|
|
270
|
+
if (value.status === "running" || value.status === "paused") {
|
|
271
|
+
if (!await options.fevex.cancelRun(runId) && (await options.fevex.getRun(runId))?.status === value.status) throw new HttpProtocolError(409, "RUN_CONFLICT", "Run could not be cancelled");
|
|
272
|
+
}
|
|
273
|
+
return versioned(new Response(null, { status: 204 }));
|
|
274
|
+
}
|
|
275
|
+
if (start || startWorkflow || startTeam || events || resume || recover || runArtifacts || artifactRoute || run) throw new HttpProtocolError(405, "METHOD_NOT_ALLOWED", "Method not allowed");
|
|
276
|
+
throw notFound("ROUTE_NOT_FOUND", "Route not found");
|
|
277
|
+
} catch (error) {
|
|
278
|
+
return problem(error, url.pathname, options.onError);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
var FevexHttpError = class extends Error {
|
|
283
|
+
problem;
|
|
284
|
+
name = "FevexHttpError";
|
|
285
|
+
constructor(problem) {
|
|
286
|
+
super(problem.detail ?? problem.title);
|
|
287
|
+
this.problem = problem;
|
|
288
|
+
}
|
|
289
|
+
get status() {
|
|
290
|
+
return this.problem.status;
|
|
291
|
+
}
|
|
292
|
+
get code() {
|
|
293
|
+
return this.problem.code;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
function createFevexHttpClient(options) {
|
|
297
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
298
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
299
|
+
const request = async (path, init) => {
|
|
300
|
+
const response = await fetcher(`${baseUrl}${path}`, init);
|
|
301
|
+
await assertResponse(response, JSON_TYPE);
|
|
302
|
+
return response.json();
|
|
303
|
+
};
|
|
304
|
+
return {
|
|
305
|
+
startRun(agentName, body) {
|
|
306
|
+
return request(`/v1/agents/${encodeURIComponent(agentName)}/runs`, jsonInit("POST", body));
|
|
307
|
+
},
|
|
308
|
+
startWorkflow(workflowName, body) {
|
|
309
|
+
return request(`/v1/workflows/${encodeURIComponent(workflowName)}/runs`, jsonInit("POST", body));
|
|
310
|
+
},
|
|
311
|
+
startTeam(teamName, body) {
|
|
312
|
+
return request(`/v1/teams/${encodeURIComponent(teamName)}/runs`, jsonInit("POST", body));
|
|
313
|
+
},
|
|
314
|
+
getRun(runId) {
|
|
315
|
+
return request(`/v1/runs/${encodeURIComponent(runId)}`);
|
|
316
|
+
},
|
|
317
|
+
observeRun(runId, observeOptions = {}) {
|
|
318
|
+
return { async *[Symbol.asyncIterator]() {
|
|
319
|
+
const response = await fetcher(`${baseUrl}/v1/runs/${encodeURIComponent(runId)}/events`, {
|
|
320
|
+
headers: observeOptions.after ? { "last-event-id": observeOptions.after } : void 0,
|
|
321
|
+
signal: observeOptions.signal
|
|
322
|
+
});
|
|
323
|
+
await assertResponse(response, SSE_TYPE);
|
|
324
|
+
if (!response.body) throw new TypeError("Fevex event response has no body");
|
|
325
|
+
yield* parseEventStream(response.body);
|
|
326
|
+
} };
|
|
327
|
+
},
|
|
328
|
+
resumeRun(runId, body) {
|
|
329
|
+
return request(`/v1/runs/${encodeURIComponent(runId)}/resume`, jsonInit("POST", body));
|
|
330
|
+
},
|
|
331
|
+
recoverRun(runId) {
|
|
332
|
+
return request(`/v1/runs/${encodeURIComponent(runId)}/recover`, { method: "POST" });
|
|
333
|
+
},
|
|
334
|
+
async cancelRun(runId) {
|
|
335
|
+
await assertResponse(await fetcher(`${baseUrl}/v1/runs/${encodeURIComponent(runId)}`, { method: "DELETE" }));
|
|
336
|
+
},
|
|
337
|
+
async getArtifact(artifactId) {
|
|
338
|
+
const response = await fetcher(`${baseUrl}/v1/artifacts/${encodeURIComponent(artifactId)}`);
|
|
339
|
+
await assertResponse(response);
|
|
340
|
+
return response;
|
|
341
|
+
},
|
|
342
|
+
listArtifacts(runId) {
|
|
343
|
+
return request(`/v1/runs/${encodeURIComponent(runId)}/artifacts`);
|
|
344
|
+
},
|
|
345
|
+
async deleteArtifact(artifactId) {
|
|
346
|
+
await assertResponse(await fetcher(`${baseUrl}/v1/artifacts/${encodeURIComponent(artifactId)}`, { method: "DELETE" }));
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
async function* observeEvents(fevex, runId, initial, after, pollIntervalMs, signal, pageSize) {
|
|
351
|
+
const encoder = new TextEncoder();
|
|
352
|
+
let pending = initial;
|
|
353
|
+
let cursor = after;
|
|
354
|
+
let closedEmptyReads = 0;
|
|
355
|
+
while (!signal.aborted) {
|
|
356
|
+
for (const event of pending) {
|
|
357
|
+
if (signal.aborted) return;
|
|
358
|
+
cursor = event.id;
|
|
359
|
+
yield encoder.encode(formatEvent(event));
|
|
360
|
+
}
|
|
361
|
+
const run = await fevex.getRun(runId);
|
|
362
|
+
if (!run) return;
|
|
363
|
+
if (run.status !== "running" && pending.length === 0) {
|
|
364
|
+
closedEmptyReads += 1;
|
|
365
|
+
if (closedEmptyReads >= 2) return;
|
|
366
|
+
} else closedEmptyReads = 0;
|
|
367
|
+
if (pending.length < pageSize) await wait(pollIntervalMs, signal);
|
|
368
|
+
if (signal.aborted) return;
|
|
369
|
+
pending = await fevex.listEvents(runId, {
|
|
370
|
+
after: cursor,
|
|
371
|
+
limit: pageSize
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function iteratorStream(iterator, cancel, done) {
|
|
376
|
+
let finished = false;
|
|
377
|
+
const finish = () => {
|
|
378
|
+
if (finished) return;
|
|
379
|
+
finished = true;
|
|
380
|
+
done?.();
|
|
381
|
+
};
|
|
382
|
+
return new ReadableStream({
|
|
383
|
+
async pull(controller) {
|
|
384
|
+
try {
|
|
385
|
+
const next = await iterator.next();
|
|
386
|
+
if (next.done) {
|
|
387
|
+
finish();
|
|
388
|
+
controller.close();
|
|
389
|
+
} else controller.enqueue(next.value);
|
|
390
|
+
} catch (error) {
|
|
391
|
+
finish();
|
|
392
|
+
controller.error(error);
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
async cancel() {
|
|
396
|
+
finish();
|
|
397
|
+
cancel();
|
|
398
|
+
await iterator.return(void 0);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
async function* parseEventStream(body) {
|
|
403
|
+
const reader = body.getReader();
|
|
404
|
+
const decoder = new TextDecoder();
|
|
405
|
+
let buffer = "";
|
|
406
|
+
let finished = false;
|
|
407
|
+
try {
|
|
408
|
+
while (true) {
|
|
409
|
+
const { done, value } = await reader.read();
|
|
410
|
+
buffer += decoder.decode(value, { stream: !done });
|
|
411
|
+
let boundary = findBoundary(buffer);
|
|
412
|
+
while (boundary) {
|
|
413
|
+
const frame = buffer.slice(0, boundary.index);
|
|
414
|
+
buffer = buffer.slice(boundary.index + boundary.length);
|
|
415
|
+
const data = frame.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
416
|
+
if (data) yield JSON.parse(data);
|
|
417
|
+
boundary = findBoundary(buffer);
|
|
418
|
+
}
|
|
419
|
+
if (done) {
|
|
420
|
+
if (buffer.trim()) throw new TypeError("Fevex event stream ended with an incomplete frame");
|
|
421
|
+
finished = true;
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
} finally {
|
|
426
|
+
if (!finished) await reader.cancel();
|
|
427
|
+
reader.releaseLock();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function findBoundary(value) {
|
|
431
|
+
const lf = value.indexOf("\n\n");
|
|
432
|
+
const crlf = value.indexOf("\r\n\r\n");
|
|
433
|
+
if (lf < 0 && crlf < 0) return void 0;
|
|
434
|
+
return crlf >= 0 && (lf < 0 || crlf < lf) ? {
|
|
435
|
+
index: crlf,
|
|
436
|
+
length: 4
|
|
437
|
+
} : {
|
|
438
|
+
index: lf,
|
|
439
|
+
length: 2
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function formatEvent(event) {
|
|
443
|
+
return `id: ${event.id}\nevent: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`;
|
|
444
|
+
}
|
|
445
|
+
function artifactContext(handlerContext, request) {
|
|
446
|
+
const tenantId = handlerContext.context?.attributes?.tenantId;
|
|
447
|
+
if (tenantId !== void 0 && typeof tenantId !== "string") throw badRequest("tenantId attribute must be a string");
|
|
448
|
+
const actor = handlerContext.context?.actor;
|
|
449
|
+
return {
|
|
450
|
+
...typeof tenantId === "string" ? { tenantId } : {},
|
|
451
|
+
...actor === void 0 ? {} : { actor },
|
|
452
|
+
signal: request.signal
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function optionalLimit(value) {
|
|
456
|
+
if (value === null) return void 0;
|
|
457
|
+
const parsed = Number(value);
|
|
458
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1) throw badRequest("limit must be a positive integer");
|
|
459
|
+
return parsed;
|
|
460
|
+
}
|
|
461
|
+
function artifactResponse(descriptor, bytes) {
|
|
462
|
+
return versioned(new Response(bytes === null ? null : bytes.slice().buffer, { headers: {
|
|
463
|
+
"content-type": descriptor.mimeType,
|
|
464
|
+
"content-length": String(descriptor.size),
|
|
465
|
+
"content-disposition": contentDisposition(descriptor.filename),
|
|
466
|
+
"cache-control": "private, max-age=0, must-revalidate",
|
|
467
|
+
etag: `"${descriptor.sha256}"`,
|
|
468
|
+
"x-content-type-options": "nosniff",
|
|
469
|
+
"fevex-artifact-id": descriptor.artifactId,
|
|
470
|
+
"fevex-artifact-sha256": descriptor.sha256
|
|
471
|
+
} }));
|
|
472
|
+
}
|
|
473
|
+
function contentDisposition(filename) {
|
|
474
|
+
return `attachment; filename="${filename.replace(/[^\x20-\x7e]/gu, "_").replace(/["\\]/gu, "_")}"; filename*=UTF-8''${encodeURIComponent(filename).replace(/'/gu, "%27")}`;
|
|
475
|
+
}
|
|
476
|
+
function readResolution(body, actor) {
|
|
477
|
+
if (body.type === "elicitation" && typeof body.requestId === "string" && body.requestId.trim() && hasOwn(body, "value")) return {
|
|
478
|
+
type: "elicitation",
|
|
479
|
+
requestId: body.requestId,
|
|
480
|
+
value: body.value,
|
|
481
|
+
actor
|
|
482
|
+
};
|
|
483
|
+
if (body.type === "approval" && typeof body.approvalId === "string" && (body.decision === "approve" || body.decision === "reject")) return {
|
|
484
|
+
type: "approval",
|
|
485
|
+
approvalId: body.approvalId,
|
|
486
|
+
decision: body.decision,
|
|
487
|
+
actor
|
|
488
|
+
};
|
|
489
|
+
if (body.type === "tool_execution" && typeof body.toolCallId === "string" && body.decision === "retry") return {
|
|
490
|
+
type: "tool_execution",
|
|
491
|
+
toolCallId: body.toolCallId,
|
|
492
|
+
decision: "retry",
|
|
493
|
+
actor
|
|
494
|
+
};
|
|
495
|
+
if (body.type === "tool_execution" && typeof body.toolCallId === "string" && body.decision === "use_output" && hasOwn(body, "output")) return {
|
|
496
|
+
type: "tool_execution",
|
|
497
|
+
toolCallId: body.toolCallId,
|
|
498
|
+
decision: "use_output",
|
|
499
|
+
output: body.output,
|
|
500
|
+
actor
|
|
501
|
+
};
|
|
502
|
+
if (body.type === "timer") return {
|
|
503
|
+
type: "timer",
|
|
504
|
+
actor
|
|
505
|
+
};
|
|
506
|
+
if (body.type === "event" && typeof body.eventName === "string" && body.eventName.trim()) return {
|
|
507
|
+
type: "event",
|
|
508
|
+
eventName: body.eventName,
|
|
509
|
+
...hasOwn(body, "payload") ? { payload: body.payload } : {},
|
|
510
|
+
actor
|
|
511
|
+
};
|
|
512
|
+
throw badRequest("Invalid run resolution");
|
|
513
|
+
}
|
|
514
|
+
async function readJson(request) {
|
|
515
|
+
if (!request.headers.get("content-type")?.toLowerCase().includes(JSON_TYPE)) throw new HttpProtocolError(415, "UNSUPPORTED_MEDIA_TYPE", "Expected application/json");
|
|
516
|
+
let value;
|
|
517
|
+
try {
|
|
518
|
+
value = await request.json();
|
|
519
|
+
} catch {
|
|
520
|
+
throw badRequest("Request body must be valid JSON");
|
|
521
|
+
}
|
|
522
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw badRequest("Request body must be a JSON object");
|
|
523
|
+
return value;
|
|
524
|
+
}
|
|
525
|
+
function optionalId(value, name) {
|
|
526
|
+
if (value === void 0) return void 0;
|
|
527
|
+
if (typeof value !== "string" || !value.trim()) throw badRequest(`${name} must be a string`);
|
|
528
|
+
return value.trim();
|
|
529
|
+
}
|
|
530
|
+
function optionalPositiveInteger(value, name) {
|
|
531
|
+
if (value === void 0) return void 0;
|
|
532
|
+
if (!Number.isSafeInteger(value) || value < 1) throw badRequest(`${name} must be a positive integer`);
|
|
533
|
+
return value;
|
|
534
|
+
}
|
|
535
|
+
function optionalDeadline(value, name) {
|
|
536
|
+
const deadlineMs = optionalPositiveInteger(value, name);
|
|
537
|
+
if (deadlineMs !== void 0 && deadlineMs > 2147483647) throw badRequest(`${name} must be at most ${MAX_DEADLINE_MS}`);
|
|
538
|
+
return deadlineMs;
|
|
539
|
+
}
|
|
540
|
+
function optionalReasoning(value) {
|
|
541
|
+
if (value === void 0) return void 0;
|
|
542
|
+
if (typeof value !== "string" || ![
|
|
543
|
+
"provider-default",
|
|
544
|
+
"none",
|
|
545
|
+
"minimal",
|
|
546
|
+
"low",
|
|
547
|
+
"medium",
|
|
548
|
+
"high",
|
|
549
|
+
"xhigh",
|
|
550
|
+
"max"
|
|
551
|
+
].includes(value)) throw badRequest("reasoning must be a valid effort");
|
|
552
|
+
return value;
|
|
553
|
+
}
|
|
554
|
+
function hasOwn(value, key) {
|
|
555
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
556
|
+
}
|
|
557
|
+
function jsonInit(method, body) {
|
|
558
|
+
return {
|
|
559
|
+
method,
|
|
560
|
+
headers: { "content-type": JSON_TYPE },
|
|
561
|
+
body: JSON.stringify(body)
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function json(value, status = 200) {
|
|
565
|
+
return versioned(Response.json(value, {
|
|
566
|
+
status,
|
|
567
|
+
headers: { "content-type": JSON_TYPE }
|
|
568
|
+
}));
|
|
569
|
+
}
|
|
570
|
+
function versioned(response) {
|
|
571
|
+
response.headers.set(FEVEX_HTTP_PROTOCOL_VERSION_HEADER, "3");
|
|
572
|
+
return response;
|
|
573
|
+
}
|
|
574
|
+
async function assertResponse(response, contentType) {
|
|
575
|
+
const version = response.headers.get(FEVEX_HTTP_PROTOCOL_VERSION_HEADER);
|
|
576
|
+
if (version !== "3") throw new TypeError(`Unsupported Fevex HTTP protocol version "${version ?? "missing"}"`);
|
|
577
|
+
if (!response.ok) {
|
|
578
|
+
if (!response.headers.get("content-type")?.includes(PROBLEM_TYPE)) throw new TypeError(`Expected ${PROBLEM_TYPE} response`);
|
|
579
|
+
const value = await response.json().catch(() => void 0);
|
|
580
|
+
if (!isProblemDetails(value)) throw new TypeError("Invalid Fevex Problem Details response");
|
|
581
|
+
throw new FevexHttpError(value);
|
|
582
|
+
}
|
|
583
|
+
if (contentType && !response.headers.get("content-type")?.includes(contentType)) throw new TypeError(`Expected ${contentType} response`);
|
|
584
|
+
}
|
|
585
|
+
function isProblemDetails(value) {
|
|
586
|
+
if (!value || typeof value !== "object") return false;
|
|
587
|
+
const problem = value;
|
|
588
|
+
return typeof problem.type === "string" && typeof problem.title === "string" && typeof problem.status === "number" && typeof problem.code === "string" && (problem.detail === void 0 || typeof problem.detail === "string") && (problem.instance === void 0 || typeof problem.instance === "string");
|
|
589
|
+
}
|
|
590
|
+
var HttpProtocolError = class extends Error {
|
|
591
|
+
status;
|
|
592
|
+
code;
|
|
593
|
+
constructor(status, code, message) {
|
|
594
|
+
super(message);
|
|
595
|
+
this.status = status;
|
|
596
|
+
this.code = code;
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
function badRequest(message, code = "INVALID_REQUEST") {
|
|
600
|
+
return new HttpProtocolError(400, code, message);
|
|
601
|
+
}
|
|
602
|
+
function notFound(code, message) {
|
|
603
|
+
return new HttpProtocolError(404, code, message);
|
|
604
|
+
}
|
|
605
|
+
function problem(error, instance, onError) {
|
|
606
|
+
let status = 500;
|
|
607
|
+
let code = "INTERNAL_ERROR";
|
|
608
|
+
let title = "Internal Server Error";
|
|
609
|
+
let detail;
|
|
610
|
+
if (error instanceof HttpProtocolError) {
|
|
611
|
+
({status, code} = error);
|
|
612
|
+
title = error.message;
|
|
613
|
+
detail = error.message;
|
|
614
|
+
} else if (error instanceof ArtifactError) {
|
|
615
|
+
code = error.code;
|
|
616
|
+
status = statusForArtifactErrorCode(error.code);
|
|
617
|
+
title = titleForStatus(status);
|
|
618
|
+
detail = error.message;
|
|
619
|
+
} else {
|
|
620
|
+
const runError = readFevexRunError(error);
|
|
621
|
+
if (runError) {
|
|
622
|
+
code = runError.code;
|
|
623
|
+
status = statusForRunErrorCode(runError.code);
|
|
624
|
+
title = titleForStatus(status);
|
|
625
|
+
detail = runError.message;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const value = {
|
|
629
|
+
type: `urn:fevex:problem:${code.toLowerCase()}`,
|
|
630
|
+
title,
|
|
631
|
+
status,
|
|
632
|
+
code,
|
|
633
|
+
...detail ? { detail } : {},
|
|
634
|
+
instance
|
|
635
|
+
};
|
|
636
|
+
if (status >= 500) try {
|
|
637
|
+
onError?.(error, value);
|
|
638
|
+
} catch {}
|
|
639
|
+
return versioned(new Response(JSON.stringify(value), {
|
|
640
|
+
status,
|
|
641
|
+
headers: { "content-type": PROBLEM_TYPE }
|
|
642
|
+
}));
|
|
643
|
+
}
|
|
644
|
+
function readFevexRunError(error) {
|
|
645
|
+
if (error instanceof FevexRunError) return error;
|
|
646
|
+
if (!error || typeof error !== "object") return void 0;
|
|
647
|
+
const value = error;
|
|
648
|
+
return typeof value.message === "string" && typeof value.code === "string" && FEVEX_RUN_ERROR_CODES.has(value.code) ? {
|
|
649
|
+
code: value.code,
|
|
650
|
+
message: value.message
|
|
651
|
+
} : void 0;
|
|
652
|
+
}
|
|
653
|
+
function statusForRunErrorCode(code) {
|
|
654
|
+
if (code === "AGENT_NOT_FOUND" || code === "WORKFLOW_NOT_FOUND" || code === "TEAM_NOT_FOUND" || code === "SESSION_NOT_FOUND") return 404;
|
|
655
|
+
if (code === "APPROVAL_INVALID" || code === "INVALID_CURSOR") return 400;
|
|
656
|
+
if (code === "POLICY_DENIED") return 403;
|
|
657
|
+
if (code === "ADMISSION_DENIED") return 429;
|
|
658
|
+
if (code === "RUNTIME_CLOSED") return 503;
|
|
659
|
+
return 409;
|
|
660
|
+
}
|
|
661
|
+
function statusForArtifactErrorCode(code) {
|
|
662
|
+
if (code === "ARTIFACT_INVALID_ID" || code === "ARTIFACT_INVALID_INPUT" || code === "ARTIFACT_INVALID_CONTEXT") return 400;
|
|
663
|
+
if (code === "ARTIFACT_TOO_LARGE") return 413;
|
|
664
|
+
if (code === "ARTIFACT_MIME_UNSUPPORTED") return 415;
|
|
665
|
+
if (code === "ARTIFACT_CONFLICT") return 409;
|
|
666
|
+
return 500;
|
|
667
|
+
}
|
|
668
|
+
function titleForStatus(status) {
|
|
669
|
+
if (status === 400) return "Bad Request";
|
|
670
|
+
if (status === 403) return "Forbidden";
|
|
671
|
+
if (status === 404) return "Not Found";
|
|
672
|
+
if (status === 409) return "Conflict";
|
|
673
|
+
if (status === 413) return "Payload Too Large";
|
|
674
|
+
if (status === 415) return "Unsupported Media Type";
|
|
675
|
+
if (status === 429) return "Too Many Requests";
|
|
676
|
+
if (status === 501) return "Not Implemented";
|
|
677
|
+
return "Internal Server Error";
|
|
678
|
+
}
|
|
679
|
+
function wait(ms, signal) {
|
|
680
|
+
return new Promise((resolve) => {
|
|
681
|
+
const timeout = setTimeout(finish, ms);
|
|
682
|
+
function finish() {
|
|
683
|
+
clearTimeout(timeout);
|
|
684
|
+
signal.removeEventListener("abort", finish);
|
|
685
|
+
resolve();
|
|
686
|
+
}
|
|
687
|
+
if (signal.aborted) finish();
|
|
688
|
+
else signal.addEventListener("abort", finish, { once: true });
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
//#endregion
|
|
692
|
+
export { FEVEX_HTTP_PROTOCOL_VERSION, FEVEX_HTTP_PROTOCOL_VERSION_HEADER, FevexHttpError, createFevexHttpClient, createFevexHttpHandler };
|