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

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 (40) hide show
  1. package/README.md +167 -2
  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-BoPbZ-Tw.d.mts → fevex-B5NxFKcV.d.mts} +41 -11
  9. package/dist/http/index.d.mts +65 -5
  10. package/dist/http/index.mjs +236 -16
  11. package/dist/{index-DyyNLuwO.d.mts → index--_LdSEuF.d.mts} +3 -3
  12. package/dist/{index-CrOId2px.d.mts → index-BFnWUu7j.d.mts} +1 -1
  13. package/dist/{index-CTIwrVf9.d.mts → index-BPwkgdo3.d.mts} +1 -1
  14. package/dist/{index-CknU-glw.d.mts → index-Br-zPxZH.d.mts} +2 -2
  15. package/dist/{index-CaE864Kz.d.mts → index-CGmHXV31.d.mts} +35 -3
  16. package/dist/{index-CqOjvtQ9.d.mts → index-CKQCNvJz.d.mts} +4 -2
  17. package/dist/{index-dkFyTNh4.d.mts → index-Cb3AB98n.d.mts} +2 -2
  18. package/dist/{index-voIZhQXB.d.mts → index-D6lnI92z.d.mts} +3 -3
  19. package/dist/{index-C-IFfCNw.d.mts → index-Dsulk-n6.d.mts} +3 -3
  20. package/dist/{index-hhbgvfIt.d.mts → index-aayvtf0J.d.mts} +2 -2
  21. package/dist/index.d.mts +13 -13
  22. package/dist/index.mjs +444 -95
  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 +1 -1
  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-BDVUoAU6.d.mts +559 -0
  38. package/dist/workflows/index.d.mts +1 -1
  39. package/package.json +6 -2
  40. package/dist/types-CuoRx12Q.d.mts +0 -333
@@ -1,14 +1,39 @@
1
- import { f as RunId, t as AgentEvent } from "./types-CuoRx12Q.mjs";
2
- import { t as ModelGateway } from "./index-CTIwrVf9.mjs";
3
- import { A as RunStore, D as RunRequest, E as RunRecord, F as TeamRun, M as SessionId, O as RunResult, S as ResumeRunResolution, U as WorkflowRun, a as AgentRun, b as ListEventsOptions, f as CredentialStore, j as Session, t as AgentDefinition } from "./index-CaE864Kz.mjs";
4
- import { r as ContextProvider, s as MemoryStore } from "./index-hhbgvfIt.mjs";
5
- import { r as ObservabilityOptions } from "./index-voIZhQXB.mjs";
6
- import { r as Sandbox } from "./index-CrOId2px.mjs";
7
- import { g as ToolDefinition, s as ConnectionDefinition } from "./index-CqOjvtQ9.mjs";
8
- import { r as PolicyDefinition } from "./index-dkFyTNh4.mjs";
9
- import { n as WorkflowDefinition } from "./index-CknU-glw.mjs";
10
- import { t as TeamDefinition } from "./index-C-IFfCNw.mjs";
1
+ import { F as ArtifactStore, f as RunId, o as ExecutionContext, t as AgentEvent } from "./types-BDVUoAU6.mjs";
2
+ import { t as ModelGateway } from "./index-BPwkgdo3.mjs";
3
+ import { A as RunRecord, C as PurgeRunsResult, F as Session, I as SessionId, M as RunResult, P as RunStore, S as PurgeRunsOptions, a as AgentRun, b as ListEventsOptions, f as CredentialStore, j as RunRequest, q as WorkflowRun, t as AgentDefinition, w as ResumeRunResolution, z as TeamRun } from "./index-CGmHXV31.mjs";
4
+ import { r as ContextProvider, s as MemoryStore } from "./index-aayvtf0J.mjs";
5
+ import { r as ObservabilityOptions } from "./index-D6lnI92z.mjs";
6
+ import { r as Sandbox } from "./index-BFnWUu7j.mjs";
7
+ import { g as ToolDefinition, s as ConnectionDefinition } from "./index-CKQCNvJz.mjs";
8
+ import { r as PolicyDefinition } from "./index-Cb3AB98n.mjs";
9
+ import { n as WorkflowDefinition } from "./index-Br-zPxZH.mjs";
10
+ import { t as TeamDefinition } from "./index-Dsulk-n6.mjs";
11
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 FevexLimits {
31
+ /**
32
+ * Maximum non-control events persisted for one run. Control events such as
33
+ * pause, resume, approval and terminal transitions are always persisted.
34
+ */
35
+ maxEventsPerRun?: number;
36
+ }
12
37
  /** Provider-neutral definitions and adapters used to create one Fevex runtime. */
13
38
  interface FevexConfig {
14
39
  models: Record<string, ModelGateway>;
@@ -22,9 +47,13 @@ interface FevexConfig {
22
47
  runStore?: RunStore;
23
48
  credentialStore?: CredentialStore;
24
49
  sandbox?: Sandbox;
50
+ /** Optional artifact store exposed to tools as `context.artifacts`. */
51
+ artifacts?: ArtifactStore;
25
52
  policies?: PolicyDefinition[];
26
53
  onEvent?: (event: AgentEvent) => void;
27
54
  observability?: ObservabilityOptions;
55
+ admission?: FevexAdmission;
56
+ limits?: FevexLimits;
28
57
  }
29
58
  interface Fevex {
30
59
  startAgent<TInput = unknown, TOutput = unknown>(name: string, request: RunRequest<TInput, TOutput>): Promise<AgentRun<TOutput>>;
@@ -46,6 +75,7 @@ interface Fevex {
46
75
  type?: string;
47
76
  };
48
77
  }): Promise<RunRecord<TOutput>>;
78
+ purgeRuns(options?: PurgeRunsOptions): Promise<PurgeRunsResult>;
49
79
  compactSession(sessionId: SessionId, summary: string): Promise<Session>;
50
80
  flushObservability(): Promise<void>;
51
81
  }
@@ -57,4 +87,4 @@ interface Fevex {
57
87
  */
58
88
  declare function createFevex(config: FevexConfig): Fevex;
59
89
  //#endregion
60
- export { FevexConfig as n, createFevex as r, Fevex as t };
90
+ export { FevexLimits as a, FevexConfig as i, FevexAdmission as n, createFevex as o, FevexAdmissionRequest as r, 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-BDVUoAU6.mjs";
2
+ import { c as ReasoningEffort } from "../index-BPwkgdo3.mjs";
3
+ import { A as RunRecord, I as SessionId, a as AgentRun, q as WorkflowRun, z as TeamRun } from "../index-CGmHXV31.mjs";
4
+ import { t as Fevex } from "../fevex-B5NxFKcV.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 };
@@ -1,4 +1,6 @@
1
+ import { o as ArtifactError } from "../artifacts-gUzemET7.mjs";
1
2
  import { t as FevexRunError } from "../run-error-Cr17wPGH.mjs";
3
+ import { t as MAX_DEADLINE_MS } from "../run-support-CmZ6Xo6H.mjs";
2
4
  //#region src/http/protocol.ts
3
5
  const FEVEX_HTTP_PROTOCOL_VERSION = "3";
4
6
  const FEVEX_HTTP_PROTOCOL_VERSION_HEADER = "Fevex-Protocol-Version";
@@ -21,6 +23,7 @@ const FEVEX_RUN_ERROR_CODES = /* @__PURE__ */ new Set([
21
23
  "CHECKPOINT_UNSUPPORTED",
22
24
  "RUN_DEFINITION_CHANGED",
23
25
  "POLICY_DENIED",
26
+ "ADMISSION_DENIED",
24
27
  "CREDENTIAL_NOT_FOUND",
25
28
  "SANDBOX_REQUIRED",
26
29
  "ELICITATION_INVALID",
@@ -32,69 +35,135 @@ const FEVEX_RUN_ERROR_CODES = /* @__PURE__ */ new Set([
32
35
  function createFevexHttpHandler(options) {
33
36
  const pollIntervalMs = options.pollIntervalMs ?? 100;
34
37
  const eventPageSize = options.eventPageSize ?? 100;
38
+ const maxConnections = options.maxConnections;
35
39
  if (!Number.isSafeInteger(eventPageSize) || eventPageSize < 1 || eventPageSize > 1e3) throw new TypeError("eventPageSize must be an integer between 1 and 1000");
36
40
  if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 1) throw new TypeError("pollIntervalMs must be a positive number");
41
+ if (maxConnections !== void 0 && (!Number.isSafeInteger(maxConnections) || maxConnections < 1)) throw new TypeError("maxConnections must be a positive safe integer");
42
+ let activeConnections = 0;
43
+ const authorize = async (access) => {
44
+ if (options.authorize && !await options.authorize(access)) throw new HttpProtocolError(403, "FORBIDDEN", "Forbidden");
45
+ };
46
+ const authorizeArtifact = async (access) => {
47
+ if (options.authorizeArtifact && !await options.authorizeArtifact(access)) throw new HttpProtocolError(403, "FORBIDDEN", "Forbidden");
48
+ };
37
49
  return async (request, handlerContext = {}) => {
38
50
  const url = new URL(request.url);
39
51
  const path = url.pathname;
52
+ const commitStart = async (run) => {
53
+ try {
54
+ await options.onRunStarted?.(run, handlerContext.context);
55
+ } catch (error) {
56
+ await options.fevex.cancelRun(run.id).catch(() => {});
57
+ throw error;
58
+ }
59
+ };
40
60
  try {
41
61
  const start = path.match(/^\/v1\/agents\/([^/]+)\/runs$/);
42
62
  if (start && request.method === "POST") {
43
63
  const body = await readJson(request);
44
64
  if (!hasOwn(body, "input")) throw badRequest("input is required");
65
+ const name = decodeURIComponent(start[1]);
45
66
  const sessionId = optionalId(body.sessionId, "sessionId");
46
67
  const model = optionalId(body.model, "model");
47
68
  const reasoning = optionalReasoning(body.reasoning);
48
- return json(await options.fevex.startAgent(decodeURIComponent(start[1]), {
69
+ const deadlineMs = optionalDeadline(body.deadlineMs, "deadlineMs");
70
+ await authorize({
71
+ action: "start",
72
+ kind: "agent",
73
+ name,
74
+ sessionId,
75
+ context: handlerContext.context
76
+ });
77
+ const run = await options.fevex.startAgent(name, {
49
78
  input: body.input,
50
79
  ...model ? { model } : {},
51
80
  ...reasoning === void 0 ? {} : { reasoning },
81
+ ...deadlineMs === void 0 ? {} : { deadlineMs },
52
82
  ...sessionId ? { sessionId } : {},
53
83
  ...handlerContext.context ? { context: handlerContext.context } : {}
54
- }), 202);
84
+ });
85
+ await commitStart(run);
86
+ return json(run, 202);
55
87
  }
56
88
  const startWorkflow = path.match(/^\/v1\/workflows\/([^/]+)\/runs$/);
57
89
  if (startWorkflow && request.method === "POST") {
58
90
  const body = await readJson(request);
59
91
  if (!hasOwn(body, "input")) throw badRequest("input is required");
92
+ const name = decodeURIComponent(startWorkflow[1]);
60
93
  const sessionId = optionalId(body.sessionId, "sessionId");
61
94
  const model = optionalId(body.model, "model");
62
95
  const reasoning = optionalReasoning(body.reasoning);
63
- return json(await options.fevex.startWorkflow(decodeURIComponent(startWorkflow[1]), {
96
+ const deadlineMs = optionalDeadline(body.deadlineMs, "deadlineMs");
97
+ await authorize({
98
+ action: "start",
99
+ kind: "workflow",
100
+ name,
101
+ sessionId,
102
+ context: handlerContext.context
103
+ });
104
+ const run = await options.fevex.startWorkflow(name, {
64
105
  input: body.input,
65
106
  ...model ? { model } : {},
66
107
  ...reasoning === void 0 ? {} : { reasoning },
108
+ ...deadlineMs === void 0 ? {} : { deadlineMs },
67
109
  ...sessionId ? { sessionId } : {},
68
110
  ...handlerContext.context ? { context: handlerContext.context } : {}
69
- }), 202);
111
+ });
112
+ await commitStart(run);
113
+ return json(run, 202);
70
114
  }
71
115
  const startTeam = path.match(/^\/v1\/teams\/([^/]+)\/runs$/);
72
116
  if (startTeam && request.method === "POST") {
73
117
  const body = await readJson(request);
74
118
  if (!hasOwn(body, "input")) throw badRequest("Run input is required");
119
+ const name = decodeURIComponent(startTeam[1]);
75
120
  const sessionId = optionalId(body.sessionId, "sessionId");
76
121
  const model = optionalId(body.model, "model");
77
122
  const reasoning = optionalReasoning(body.reasoning);
78
- return json(await options.fevex.startTeam(decodeURIComponent(startTeam[1]), {
123
+ const deadlineMs = optionalDeadline(body.deadlineMs, "deadlineMs");
124
+ await authorize({
125
+ action: "start",
126
+ kind: "team",
127
+ name,
128
+ sessionId,
129
+ context: handlerContext.context
130
+ });
131
+ const run = await options.fevex.startTeam(name, {
79
132
  input: body.input,
80
133
  ...model ? { model } : {},
81
134
  ...reasoning === void 0 ? {} : { reasoning },
135
+ ...deadlineMs === void 0 ? {} : { deadlineMs },
82
136
  ...sessionId ? { sessionId } : {},
83
137
  ...handlerContext.context ? { context: handlerContext.context } : {}
84
- }), 202);
138
+ });
139
+ await commitStart(run);
140
+ return json(run, 202);
85
141
  }
86
142
  const events = path.match(/^\/v1\/runs\/([^/]+)\/events$/);
87
143
  if (events && request.method === "GET") {
88
144
  const runId = decodeURIComponent(events[1]);
145
+ await authorize({
146
+ action: "observe",
147
+ runId,
148
+ context: handlerContext.context
149
+ });
89
150
  if (!await options.fevex.getRun(runId)) throw notFound("RUN_NOT_FOUND", "Run not found");
90
151
  const after = request.headers.get("last-event-id")?.trim() || void 0;
91
152
  const initial = await options.fevex.listEvents(runId, {
92
153
  after,
93
154
  limit: eventPageSize
94
155
  });
156
+ if (maxConnections !== void 0 && activeConnections >= maxConnections) throw new HttpProtocolError(429, "CONNECTION_LIMIT", "Too many event streams");
157
+ activeConnections += 1;
158
+ let released = false;
159
+ const release = () => {
160
+ if (released) return;
161
+ released = true;
162
+ activeConnections -= 1;
163
+ };
95
164
  const disconnected = new AbortController();
96
165
  const iterator = observeEvents(options.fevex, runId, initial, after, pollIntervalMs, AbortSignal.any([request.signal, disconnected.signal]), eventPageSize);
97
- return versioned(new Response(iteratorStream(iterator, () => disconnected.abort()), { headers: {
166
+ return versioned(new Response(iteratorStream(iterator, () => disconnected.abort(), release), { headers: {
98
167
  "content-type": `${SSE_TYPE}; charset=utf-8`,
99
168
  "cache-control": "no-cache, no-transform"
100
169
  } }));
@@ -102,28 +171,99 @@ function createFevexHttpHandler(options) {
102
171
  const resume = path.match(/^\/v1\/runs\/([^/]+)\/resume$/);
103
172
  if (resume && request.method === "POST") {
104
173
  const runId = decodeURIComponent(resume[1]);
105
- if (!await options.fevex.getRun(runId)) throw notFound("RUN_NOT_FOUND", "Run not found");
106
174
  const actor = handlerContext.context?.actor;
107
175
  if (!actor?.id?.trim()) throw new HttpProtocolError(401, "ACTOR_REQUIRED", "Authenticated actor is required");
176
+ await authorize({
177
+ action: "resume",
178
+ runId,
179
+ context: handlerContext.context
180
+ });
181
+ if (!await options.fevex.getRun(runId)) throw notFound("RUN_NOT_FOUND", "Run not found");
108
182
  const resolution = readResolution(await readJson(request), actor);
109
183
  return json(await options.fevex.resumeRun(runId, resolution), 202);
110
184
  }
111
185
  const recover = path.match(/^\/v1\/runs\/([^/]+)\/recover$/);
112
186
  if (recover && request.method === "POST") {
113
187
  const runId = decodeURIComponent(recover[1]);
114
- if (!await options.fevex.getRun(runId)) throw notFound("RUN_NOT_FOUND", "Run not found");
115
188
  const actor = handlerContext.context?.actor;
116
189
  if (!actor?.id?.trim()) throw new HttpProtocolError(401, "ACTOR_REQUIRED", "Authenticated actor is required");
190
+ await authorize({
191
+ action: "recover",
192
+ runId,
193
+ context: handlerContext.context
194
+ });
195
+ if (!await options.fevex.getRun(runId)) throw notFound("RUN_NOT_FOUND", "Run not found");
117
196
  return json(await options.fevex.recoverRun(runId, { actor }), 202);
118
197
  }
198
+ const runArtifacts = path.match(/^\/v1\/runs\/([^/]+)\/artifacts$/);
199
+ if (runArtifacts && request.method === "GET") {
200
+ const runId = decodeURIComponent(runArtifacts[1]);
201
+ await authorizeArtifact({
202
+ action: "read",
203
+ kind: "artifacts",
204
+ runId,
205
+ context: handlerContext.context
206
+ });
207
+ if (!options.artifactCatalog) throw new HttpProtocolError(501, "ARTIFACT_CATALOG_NOT_CONFIGURED", "Artifact catalog is not configured");
208
+ if (!await options.fevex.getRun(runId)) throw notFound("RUN_NOT_FOUND", "Run not found");
209
+ const limit = optionalLimit(url.searchParams.get("limit"));
210
+ return json((await options.artifactCatalog.list({
211
+ runId,
212
+ ...limit === void 0 ? {} : { limit }
213
+ }, artifactContext(handlerContext, request))).map(({ descriptor }) => descriptor));
214
+ }
215
+ const artifactRoute = path.match(/^\/v1\/artifacts\/([^/]+)$/);
216
+ if (artifactRoute && (request.method === "GET" || request.method === "HEAD")) {
217
+ const artifactId = decodeURIComponent(artifactRoute[1]);
218
+ await authorizeArtifact({
219
+ action: "read",
220
+ kind: "artifact",
221
+ artifactId,
222
+ context: handlerContext.context
223
+ });
224
+ if (!options.artifacts) throw new HttpProtocolError(501, "ARTIFACTS_NOT_CONFIGURED", "Artifact store is not configured");
225
+ const context = artifactContext(handlerContext, request);
226
+ if (request.method === "HEAD" && options.artifacts.head) {
227
+ const descriptor = await options.artifacts.head(artifactId, context);
228
+ if (!descriptor) throw notFound("ARTIFACT_NOT_FOUND", "Artifact not found");
229
+ return artifactResponse(descriptor, null);
230
+ }
231
+ const content = await options.artifacts.get(artifactId, context);
232
+ if (!content) throw notFound("ARTIFACT_NOT_FOUND", "Artifact not found");
233
+ return artifactResponse(content.descriptor, request.method === "HEAD" ? null : content.bytes);
234
+ }
235
+ if (artifactRoute && request.method === "DELETE") {
236
+ const artifactId = decodeURIComponent(artifactRoute[1]);
237
+ await authorizeArtifact({
238
+ action: "delete",
239
+ kind: "artifact",
240
+ artifactId,
241
+ context: handlerContext.context
242
+ });
243
+ if (!options.artifacts) throw new HttpProtocolError(501, "ARTIFACTS_NOT_CONFIGURED", "Artifact store is not configured");
244
+ if (!options.artifacts.delete) throw new HttpProtocolError(501, "ARTIFACT_DELETE_UNSUPPORTED", "Artifact store does not support deletion");
245
+ if (!await options.artifacts.delete(artifactId, artifactContext(handlerContext, request))) throw notFound("ARTIFACT_NOT_FOUND", "Artifact not found");
246
+ return versioned(new Response(null, { status: 204 }));
247
+ }
119
248
  const run = path.match(/^\/v1\/runs\/([^/]+)$/);
120
249
  if (run && request.method === "GET") {
121
- const value = await options.fevex.getRun(decodeURIComponent(run[1]));
250
+ const runId = decodeURIComponent(run[1]);
251
+ await authorize({
252
+ action: "read",
253
+ runId,
254
+ context: handlerContext.context
255
+ });
256
+ const value = await options.fevex.getRun(runId);
122
257
  if (!value) throw notFound("RUN_NOT_FOUND", "Run not found");
123
258
  return json(value);
124
259
  }
125
260
  if (run && request.method === "DELETE") {
126
261
  const runId = decodeURIComponent(run[1]);
262
+ await authorize({
263
+ action: "cancel",
264
+ runId,
265
+ context: handlerContext.context
266
+ });
127
267
  const value = await options.fevex.getRun(runId);
128
268
  if (!value) throw notFound("RUN_NOT_FOUND", "Run not found");
129
269
  if (value.status === "running" || value.status === "paused") {
@@ -131,7 +271,7 @@ function createFevexHttpHandler(options) {
131
271
  }
132
272
  return versioned(new Response(null, { status: 204 }));
133
273
  }
134
- if (start || startWorkflow || startTeam || events || resume || recover || run) throw new HttpProtocolError(405, "METHOD_NOT_ALLOWED", "Method not allowed");
274
+ if (start || startWorkflow || startTeam || events || resume || recover || runArtifacts || artifactRoute || run) throw new HttpProtocolError(405, "METHOD_NOT_ALLOWED", "Method not allowed");
135
275
  throw notFound("ROUTE_NOT_FOUND", "Route not found");
136
276
  } catch (error) {
137
277
  return problem(error, url.pathname, options.onError);
@@ -192,6 +332,17 @@ function createFevexHttpClient(options) {
192
332
  },
193
333
  async cancelRun(runId) {
194
334
  await assertResponse(await fetcher(`${baseUrl}/v1/runs/${encodeURIComponent(runId)}`, { method: "DELETE" }));
335
+ },
336
+ async getArtifact(artifactId) {
337
+ const response = await fetcher(`${baseUrl}/v1/artifacts/${encodeURIComponent(artifactId)}`);
338
+ await assertResponse(response);
339
+ return response;
340
+ },
341
+ listArtifacts(runId) {
342
+ return request(`/v1/runs/${encodeURIComponent(runId)}/artifacts`);
343
+ },
344
+ async deleteArtifact(artifactId) {
345
+ await assertResponse(await fetcher(`${baseUrl}/v1/artifacts/${encodeURIComponent(artifactId)}`, { method: "DELETE" }));
195
346
  }
196
347
  };
197
348
  }
@@ -220,18 +371,28 @@ async function* observeEvents(fevex, runId, initial, after, pollIntervalMs, sign
220
371
  });
221
372
  }
222
373
  }
223
- function iteratorStream(iterator, cancel) {
374
+ function iteratorStream(iterator, cancel, done) {
375
+ let finished = false;
376
+ const finish = () => {
377
+ if (finished) return;
378
+ finished = true;
379
+ done?.();
380
+ };
224
381
  return new ReadableStream({
225
382
  async pull(controller) {
226
383
  try {
227
384
  const next = await iterator.next();
228
- if (next.done) controller.close();
229
- else controller.enqueue(next.value);
385
+ if (next.done) {
386
+ finish();
387
+ controller.close();
388
+ } else controller.enqueue(next.value);
230
389
  } catch (error) {
390
+ finish();
231
391
  controller.error(error);
232
392
  }
233
393
  },
234
394
  async cancel() {
395
+ finish();
235
396
  cancel();
236
397
  await iterator.return(void 0);
237
398
  }
@@ -280,6 +441,37 @@ function findBoundary(value) {
280
441
  function formatEvent(event) {
281
442
  return `id: ${event.id}\nevent: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`;
282
443
  }
444
+ function artifactContext(handlerContext, request) {
445
+ const tenantId = handlerContext.context?.attributes?.tenantId;
446
+ if (tenantId !== void 0 && typeof tenantId !== "string") throw badRequest("tenantId attribute must be a string");
447
+ const actor = handlerContext.context?.actor;
448
+ return {
449
+ ...typeof tenantId === "string" ? { tenantId } : {},
450
+ ...actor === void 0 ? {} : { actor },
451
+ signal: request.signal
452
+ };
453
+ }
454
+ function optionalLimit(value) {
455
+ if (value === null) return void 0;
456
+ const parsed = Number(value);
457
+ if (!Number.isSafeInteger(parsed) || parsed < 1) throw badRequest("limit must be a positive integer");
458
+ return parsed;
459
+ }
460
+ function artifactResponse(descriptor, bytes) {
461
+ return versioned(new Response(bytes === null ? null : bytes.slice().buffer, { headers: {
462
+ "content-type": descriptor.mimeType,
463
+ "content-length": String(descriptor.size),
464
+ "content-disposition": contentDisposition(descriptor.filename),
465
+ "cache-control": "private, max-age=0, must-revalidate",
466
+ etag: `"${descriptor.sha256}"`,
467
+ "x-content-type-options": "nosniff",
468
+ "fevex-artifact-id": descriptor.artifactId,
469
+ "fevex-artifact-sha256": descriptor.sha256
470
+ } }));
471
+ }
472
+ function contentDisposition(filename) {
473
+ return `attachment; filename="${filename.replace(/[^\x20-\x7e]/gu, "_").replace(/["\\]/gu, "_")}"; filename*=UTF-8''${encodeURIComponent(filename).replace(/'/gu, "%27")}`;
474
+ }
283
475
  function readResolution(body, actor) {
284
476
  if (body.type === "elicitation" && typeof body.requestId === "string" && body.requestId.trim() && hasOwn(body, "value")) return {
285
477
  type: "elicitation",
@@ -334,6 +526,16 @@ function optionalId(value, name) {
334
526
  if (typeof value !== "string" || !value.trim()) throw badRequest(`${name} must be a string`);
335
527
  return value.trim();
336
528
  }
529
+ function optionalPositiveInteger(value, name) {
530
+ if (value === void 0) return void 0;
531
+ if (!Number.isSafeInteger(value) || value < 1) throw badRequest(`${name} must be a positive integer`);
532
+ return value;
533
+ }
534
+ function optionalDeadline(value, name) {
535
+ const deadlineMs = optionalPositiveInteger(value, name);
536
+ if (deadlineMs !== void 0 && deadlineMs > 2147483647) throw badRequest(`${name} must be at most ${MAX_DEADLINE_MS}`);
537
+ return deadlineMs;
538
+ }
337
539
  function optionalReasoning(value) {
338
540
  if (value === void 0) return void 0;
339
541
  if (typeof value !== "string" || ![
@@ -408,6 +610,11 @@ function problem(error, instance, onError) {
408
610
  ({status, code} = error);
409
611
  title = error.message;
410
612
  detail = error.message;
613
+ } else if (error instanceof ArtifactError) {
614
+ code = error.code;
615
+ status = statusForArtifactErrorCode(error.code);
616
+ title = titleForStatus(status);
617
+ detail = error.message;
411
618
  } else {
412
619
  const runError = readFevexRunError(error);
413
620
  if (runError) {
@@ -446,13 +653,26 @@ function statusForRunErrorCode(code) {
446
653
  if (code === "AGENT_NOT_FOUND" || code === "WORKFLOW_NOT_FOUND" || code === "TEAM_NOT_FOUND" || code === "SESSION_NOT_FOUND") return 404;
447
654
  if (code === "APPROVAL_INVALID" || code === "INVALID_CURSOR") return 400;
448
655
  if (code === "POLICY_DENIED") return 403;
656
+ if (code === "ADMISSION_DENIED") return 429;
449
657
  return 409;
450
658
  }
659
+ function statusForArtifactErrorCode(code) {
660
+ if (code === "ARTIFACT_INVALID_ID" || code === "ARTIFACT_INVALID_INPUT" || code === "ARTIFACT_INVALID_CONTEXT") return 400;
661
+ if (code === "ARTIFACT_TOO_LARGE") return 413;
662
+ if (code === "ARTIFACT_MIME_UNSUPPORTED") return 415;
663
+ if (code === "ARTIFACT_CONFLICT") return 409;
664
+ return 500;
665
+ }
451
666
  function titleForStatus(status) {
452
- if (status === 404) return "Not Found";
453
667
  if (status === 400) return "Bad Request";
454
668
  if (status === 403) return "Forbidden";
455
- return "Conflict";
669
+ if (status === 404) return "Not Found";
670
+ if (status === 409) return "Conflict";
671
+ if (status === 413) return "Payload Too Large";
672
+ if (status === 415) return "Unsupported Media Type";
673
+ if (status === 429) return "Too Many Requests";
674
+ if (status === 501) return "Not Implemented";
675
+ return "Internal Server Error";
456
676
  }
457
677
  function wait(ms, signal) {
458
678
  return new Promise((resolve) => {
@@ -1,6 +1,6 @@
1
- import { f as RunId, o as ExecutionContext, s as JsonObject, t as AgentEvent } from "./types-CuoRx12Q.mjs";
2
- import { M as SessionId, a as AgentRun, h as ElicitationRequest } from "./index-CaE864Kz.mjs";
3
- import { t as Fevex } from "./fevex-BoPbZ-Tw.mjs";
1
+ import { f as RunId, o as ExecutionContext, s as JsonObject, t as AgentEvent } from "./types-BDVUoAU6.mjs";
2
+ import { I as SessionId, a as AgentRun, h as ElicitationRequest } from "./index-CGmHXV31.mjs";
3
+ import { t as Fevex } from "./fevex-B5NxFKcV.mjs";
4
4
  //#region src/channels/channel.d.ts
5
5
  interface ChannelMessage {
6
6
  id: string;
@@ -1,4 +1,4 @@
1
- import { f as RunId, l as JsonValue, o as ExecutionContext } from "./types-CuoRx12Q.mjs";
1
+ import { f as RunId, l as JsonValue, o as ExecutionContext } from "./types-BDVUoAU6.mjs";
2
2
  //#region src/sandbox/sandbox.d.ts
3
3
  interface SandboxCapabilities {
4
4
  process?: {
@@ -1,4 +1,4 @@
1
- import { g as ToolSpec, i as AgentMessage, l as JsonValue, m as ToolCall, s as JsonObject } from "./types-CuoRx12Q.mjs";
1
+ import { g as ToolSpec, i as AgentMessage, l as JsonValue, m as ToolCall, s as JsonObject } from "./types-BDVUoAU6.mjs";
2
2
  //#region src/models/model.d.ts
3
3
  /** Provider-neutral reasoning levels accepted by model gateways. */
4
4
  type ReasoningEffort = 'provider-default' | 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
@@ -1,6 +1,6 @@
1
1
  import { t as ExactDefinition } from "./exact-definition-NHBBFlFR.mjs";
2
- import { f as RunId, l as JsonValue, o as ExecutionContext, s as JsonObject } from "./types-CuoRx12Q.mjs";
3
- import { D as RunRequest, M as SessionId, O as RunResult, n as AgentLimits } from "./index-CaE864Kz.mjs";
2
+ import { f as RunId, l as JsonValue, o as ExecutionContext, s as JsonObject } from "./types-BDVUoAU6.mjs";
3
+ import { I as SessionId, M as RunResult, j as RunRequest, n as AgentLimits } from "./index-CGmHXV31.mjs";
4
4
  import { StandardSchemaV1 } from "@standard-schema/spec";
5
5
  //#region src/workflows/workflow.d.ts
6
6
  /** Context shared with workflow steps and compensation handlers. */