@fevex/core 0.1.0-alpha.1 → 0.1.0-alpha.11

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 (56) hide show
  1. package/README.md +786 -13
  2. package/dist/agents/index.d.mts +2 -2
  3. package/dist/agents/index.mjs +1 -1
  4. package/dist/artifacts/index.d.mts +2 -0
  5. package/dist/artifacts/index.mjs +2 -0
  6. package/dist/artifacts-gUzemET7.mjs +440 -0
  7. package/dist/channels/index.d.mts +2 -0
  8. package/dist/channels/index.mjs +192 -0
  9. package/dist/evals/index.d.mts +19 -11
  10. package/dist/evals/index.mjs +125 -122
  11. package/dist/fevex-_KOsOsB8.d.mts +129 -0
  12. package/dist/http/index.d.mts +150 -0
  13. package/dist/http/index.mjs +692 -0
  14. package/dist/index-26ilbzAs.d.mts +50 -0
  15. package/dist/index-B8EcvWeN.d.mts +103 -0
  16. package/dist/index-BEqYWean.d.mts +118 -0
  17. package/dist/index-Btth7rdL.d.mts +58 -0
  18. package/dist/{index-QTkd6a7C.d.mts → index-CFzrvDQ0.d.mts} +4 -3
  19. package/dist/index-CIHWc5V1.d.mts +116 -0
  20. package/dist/index-D7r0puBi.d.mts +64 -0
  21. package/dist/index-DHcfIfh-.d.mts +224 -0
  22. package/dist/index-DUhNa7IR.d.mts +73 -0
  23. package/dist/index-lCCFokc6.d.mts +617 -0
  24. package/dist/index.d.mts +22 -10
  25. package/dist/index.mjs +4178 -671
  26. package/dist/knowledge/index.d.mts +2 -0
  27. package/dist/knowledge/index.mjs +289 -0
  28. package/dist/model-stream-JQdHDDcj.mjs +131 -0
  29. package/dist/models/index.d.mts +2 -46
  30. package/dist/observability/index.d.mts +2 -2
  31. package/dist/policies/index.d.mts +1 -1
  32. package/dist/policies/index.mjs +1 -1
  33. package/dist/run-error-Cr17wPGH.mjs +23 -0
  34. package/dist/{model-stream-u2xMsEaf.mjs → run-support-CmZ6Xo6H.mjs} +41 -130
  35. package/dist/runtime/index.d.mts +2 -2
  36. package/dist/runtime/index.mjs +271 -9
  37. package/dist/sandbox/index.d.mts +2 -0
  38. package/dist/sandbox/index.mjs +147 -0
  39. package/dist/teams/index.d.mts +2 -0
  40. package/dist/teams/index.mjs +2 -0
  41. package/dist/teams-Ctz676G4.mjs +82 -0
  42. package/dist/testing/index.d.mts +39 -9
  43. package/dist/testing/index.mjs +512 -28
  44. package/dist/tools/index.d.mts +3 -3
  45. package/dist/tools/index.mjs +442 -2
  46. package/dist/types-CckDlhYo.d.mts +575 -0
  47. package/dist/workflows/index.d.mts +2 -0
  48. package/dist/workflows/index.mjs +6 -0
  49. package/package.json +31 -3
  50. package/dist/exact-definition-NHBBFlFR.d.mts +0 -4
  51. package/dist/fevex-Dz6GBz9o.d.mts +0 -32
  52. package/dist/index-BzxHfyYV.d.mts +0 -23
  53. package/dist/index-CZGyhNIQ.d.mts +0 -47
  54. package/dist/index-C_Zpr13f.d.mts +0 -124
  55. package/dist/index-DGWOGXzc.d.mts +0 -181
  56. package/dist/index-ojCY3lwm.d.mts +0 -79
@@ -1,3 +1,4 @@
1
+ import { t as FevexRunError } from "./run-error-Cr17wPGH.mjs";
1
2
  //#region src/internal/json.ts
2
3
  const invalidJsonValue = Symbol("invalid-json-value");
3
4
  function normalizeJsonValue(value, seen = /* @__PURE__ */ new WeakSet()) {
@@ -50,6 +51,45 @@ function serializeValue(value, errorMessage) {
50
51
  }
51
52
  //#endregion
52
53
  //#region src/internal/run-support.ts
54
+ /** Largest delay accepted by Node and Bun timers. */
55
+ const MAX_DEADLINE_MS = 2147483647;
56
+ function deadlineAtFromMs(deadlineMs) {
57
+ if (deadlineMs === void 0) return void 0;
58
+ if (!Number.isSafeInteger(deadlineMs) || deadlineMs <= 0 || deadlineMs > 2147483647) throw new TypeError(`deadlineMs must be a positive safe integer up to ${MAX_DEADLINE_MS}`);
59
+ return new Date(Date.now() + deadlineMs).toISOString();
60
+ }
61
+ function runSignal(controller, request) {
62
+ const signals = [controller.signal];
63
+ if (request.signal) signals.push(request.signal);
64
+ if (request.deadlineMs !== void 0) {
65
+ deadlineAtFromMs(request.deadlineMs);
66
+ signals.push(AbortSignal.timeout(request.deadlineMs));
67
+ }
68
+ return AbortSignal.any(signals);
69
+ }
70
+ function resumeDeadlineSignal(deadlineAt, runId, code) {
71
+ if (deadlineAt === void 0) return void 0;
72
+ const expiresAt = Date.parse(deadlineAt);
73
+ if (!Number.isFinite(expiresAt)) throw new FevexRunError(code, `Run "${runId}" has an invalid deadline`, runId);
74
+ if (expiresAt <= Date.now()) throw new FevexRunError(code, `Run "${runId}" deadline has passed`, runId);
75
+ return AbortSignal.timeout(expiresAt - Date.now());
76
+ }
77
+ function remainingDeadlineMs(deadlineAt, runId) {
78
+ if (deadlineAt === void 0) return void 0;
79
+ const expiresAt = Date.parse(deadlineAt);
80
+ if (!Number.isFinite(expiresAt)) throw new FevexRunError("RUN_NOT_RESUMABLE", `Run "${runId}" has an invalid deadline`, runId);
81
+ const remaining = expiresAt - Date.now();
82
+ if (remaining <= 0) throw new FevexRunError("RUN_NOT_RESUMABLE", `Run "${runId}" deadline has passed`, runId);
83
+ return remaining;
84
+ }
85
+ function isTimeoutError(error) {
86
+ return typeof error === "object" && error !== null && error.name === "TimeoutError";
87
+ }
88
+ function minDeadline(first, second) {
89
+ if (first === void 0) return second;
90
+ if (second === void 0) return first;
91
+ return Math.min(first, second);
92
+ }
53
93
  function toErrorMessage(error) {
54
94
  if (error instanceof Error) return error.message;
55
95
  if (typeof error === "string") return error;
@@ -119,133 +159,4 @@ function eventUsage(usage) {
119
159
  return value;
120
160
  }
121
161
  //#endregion
122
- //#region src/internal/model-stream.ts
123
- const MAX_DELTA_CHARS = 512;
124
- const MAX_DELTA_DELAY_MS = 32;
125
- function isRecord(value) {
126
- return typeof value === "object" && value !== null && !Array.isArray(value);
127
- }
128
- function canonicalJson(value) {
129
- if (Array.isArray(value)) return value.map(canonicalJson);
130
- if (!isRecord(value)) return value;
131
- const prototype = Object.getPrototypeOf(value);
132
- if (prototype !== Object.prototype && prototype !== null) return value;
133
- return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonicalJson(item)]));
134
- }
135
- function assertConsistentOutput(result, outputText) {
136
- if (result.output === void 0) {
137
- if (outputText) throw new TypeError("Model stream emitted output without a final output");
138
- return;
139
- }
140
- if (typeof result.output === "string") {
141
- if (result.output !== outputText) throw new TypeError("Model stream deltas do not match the final output");
142
- return;
143
- }
144
- if (!outputText) throw new TypeError("Model stream returned JSON output without output deltas");
145
- let parsed;
146
- try {
147
- parsed = JSON.parse(outputText);
148
- } catch (error) {
149
- throw new TypeError("Model stream output deltas are not valid JSON", { cause: error });
150
- }
151
- if (JSON.stringify(canonicalJson(parsed)) !== JSON.stringify(canonicalJson(result.output))) throw new TypeError("Model stream deltas do not match the final JSON output");
152
- }
153
- function assertStreamEvent(value) {
154
- if (!isRecord(value)) throw new TypeError("ModelGateway stream event must be an object");
155
- if (value.type === "output.delta") {
156
- if (typeof value.delta !== "string") throw new TypeError("ModelGateway output.delta must contain a string delta");
157
- return;
158
- }
159
- if (value.type === "completed") {
160
- if (!isRecord(value.result)) throw new TypeError("ModelGateway completed event must contain a result");
161
- return;
162
- }
163
- throw new TypeError("ModelGateway returned an unknown stream event");
164
- }
165
- function takeCharacters(value, count) {
166
- const characters = Array.from(value);
167
- return [characters.slice(0, count).join(""), characters.slice(count).join("")];
168
- }
169
- async function* readModelStream(model, input) {
170
- input.signal?.throwIfAborted();
171
- const stream = model.stream(input);
172
- if (!stream || typeof stream[Symbol.asyncIterator] !== "function") throw new TypeError("ModelGateway.stream must return an AsyncIterable");
173
- const iterator = stream[Symbol.asyncIterator]();
174
- let pending;
175
- let completed;
176
- let outputText = "";
177
- let buffer = "";
178
- let firstDelta = true;
179
- let bufferedAt = 0;
180
- const next = () => {
181
- const promise = abortable(() => iterator.next(), input.signal);
182
- promise.catch(() => {});
183
- return promise;
184
- };
185
- try {
186
- while (true) {
187
- if (buffer && Array.from(buffer).length >= MAX_DELTA_CHARS) {
188
- const [chunk, rest] = takeCharacters(buffer, MAX_DELTA_CHARS);
189
- buffer = rest;
190
- bufferedAt = performance.now();
191
- yield chunk;
192
- continue;
193
- }
194
- pending ??= next();
195
- let item;
196
- if (buffer) {
197
- const remaining = Math.max(0, MAX_DELTA_DELAY_MS - (performance.now() - bufferedAt));
198
- const raced = await Promise.race([pending.then((value) => ({
199
- type: "item",
200
- value
201
- })), new Promise((resolve) => {
202
- setTimeout(() => resolve({ type: "flush" }), remaining);
203
- })]);
204
- if (raced.type === "flush") {
205
- const chunk = buffer;
206
- buffer = "";
207
- bufferedAt = 0;
208
- yield chunk;
209
- continue;
210
- }
211
- item = raced.value;
212
- } else item = await pending;
213
- pending = void 0;
214
- if (item.done) {
215
- if (!completed) throw new TypeError("ModelGateway stream ended without completed");
216
- assertConsistentOutput(completed, outputText);
217
- return completed;
218
- }
219
- assertStreamEvent(item.value);
220
- if (completed) throw new TypeError("ModelGateway stream emitted an event after completed");
221
- if (item.value.type === "completed") {
222
- completed = item.value.result;
223
- if (buffer) {
224
- const chunk = buffer;
225
- buffer = "";
226
- bufferedAt = 0;
227
- yield chunk;
228
- }
229
- continue;
230
- }
231
- if (!item.value.delta) continue;
232
- outputText += item.value.delta;
233
- buffer += item.value.delta;
234
- if (firstDelta) {
235
- const [chunk, rest] = takeCharacters(buffer, MAX_DELTA_CHARS);
236
- firstDelta = false;
237
- buffer = rest;
238
- bufferedAt = performance.now();
239
- yield chunk;
240
- } else if (!bufferedAt) bufferedAt = performance.now();
241
- }
242
- } catch (error) {
243
- if (buffer) yield buffer;
244
- throw error;
245
- } finally {
246
- if (pending) pending.catch(() => {});
247
- if (iterator.return) Promise.resolve(iterator.return()).catch(() => {});
248
- }
249
- }
250
- //#endregion
251
- export { assertTokenBudget as a, remainingOutputTokens as c, serializeValue as d, toJsonValue as f, assertContinuationBudget as i, toErrorMessage as l, abortable as n, cancellationReason as o, addUsage as r, eventUsage as s, readModelStream as t, serializeJsonValue as u };
162
+ export { serializeValue as _, assertTokenBudget as a, eventUsage as c, remainingDeadlineMs as d, remainingOutputTokens as f, serializeJsonValue as g, toErrorMessage as h, assertContinuationBudget as i, isTimeoutError as l, runSignal as m, abortable as n, cancellationReason as o, resumeDeadlineSignal as p, addUsage as r, deadlineAtFromMs as s, MAX_DEADLINE_MS as t, minDeadline as u, toJsonValue as v };
@@ -1,2 +1,2 @@
1
- import { C as isDurableRunStore, S as ToolExecutionStatus, _ as RunStore, a as DurableRunStore, b as ToolExecutionRecord, c as ListEventsOptions, d as RunCheckpoint, f as RunLease, g as RunStatus, h as RunResult, i as CredentialStore, l as PendingToolExecution, m as RunRequest, n as ApprovalRequest, o as ExecutionCommit, p as RunPause, r as ApprovalResolution, s as InMemoryRunStore, t as AgentRun, u as ResumeRunResolution, v as Session, x as ToolExecutionResolution, y as SessionId } from "../index-DGWOGXzc.mjs";
2
- export { AgentRun, ApprovalRequest, ApprovalResolution, CredentialStore, DurableRunStore, ExecutionCommit, InMemoryRunStore, ListEventsOptions, PendingToolExecution, ResumeRunResolution, RunCheckpoint, RunLease, RunPause, RunRequest, RunResult, RunStatus, RunStore, Session, SessionId, ToolExecutionRecord, ToolExecutionResolution, ToolExecutionStatus, isDurableRunStore };
1
+ import { $ as SessionCompaction, A as ElicitationMode, B as ResumeRunResolution, C as ApprovalMode, D as CoordinatorRun, E as CoordinatorCheckpoint, F as ExecutionCreate, G as RunLease, H as RetainableRunStore, I as ListEventsOptions, J as RunRequest, K as RunPause, L as PendingToolExecution, M as ElicitationResolution, N as EventResolution, O as CredentialStore, P as ExecutionCommit, Q as Session, R as PurgeRunsOptions, S as AgentRunPause, T as ApprovalResolution, U as RunCheckpoint, V as RetainableRunStatus, W as RunCorrelation, X as RunStatus, Y as RunResult, Z as RunStore, _ as createDigestContextCompactor, _t as validatePurgeRunsOptions, at as ToolExecutionRecord, b as resolveContextBudget, c as InMemoryRunStore, ct as WorkflowBudgetUsage, d as ContextCompactionStats, dt as WorkflowRun, et as SessionId, f as ContextCompactor, ft as WorkflowStepRecord, g as ResolvedContextBudget, gt as validateListEventsOptions, h as ModelContextCompactorOptions, ht as isRetainableRunStore, it as TimerResolution, j as ElicitationRequest, k as DurableRunStore, l as ContextCompactionInput, lt as WorkflowCheckpoint, m as DigestContextCompactorOptions, mt as isDurableRunStore, nt as TeamCheckpoint, ot as ToolExecutionResolution, p as ContextTokenEstimator, pt as WorkflowWaitRecord, q as RunRecord, rt as TeamRun, st as ToolExecutionStatus, tt as StoredRunCheckpoint, u as ContextCompactionOptions, ut as WorkflowCompensationRecord, v as createModelContextCompactor, w as ApprovalRequest, x as AgentRun, y as estimateContextTokens, z as PurgeRunsResult } from "../index-lCCFokc6.mjs";
2
+ export { AgentRun, AgentRunPause, ApprovalMode, ApprovalRequest, ApprovalResolution, ContextCompactionInput, ContextCompactionOptions, ContextCompactionStats, ContextCompactor, ContextTokenEstimator, CoordinatorCheckpoint, CoordinatorRun, CredentialStore, DigestContextCompactorOptions, DurableRunStore, ElicitationMode, ElicitationRequest, ElicitationResolution, EventResolution, ExecutionCommit, ExecutionCreate, InMemoryRunStore, ListEventsOptions, ModelContextCompactorOptions, PendingToolExecution, PurgeRunsOptions, PurgeRunsResult, ResolvedContextBudget, ResumeRunResolution, RetainableRunStatus, RetainableRunStore, RunCheckpoint, RunCorrelation, RunLease, RunPause, RunRecord, RunRequest, RunResult, RunStatus, RunStore, Session, SessionCompaction, SessionId, StoredRunCheckpoint, TeamCheckpoint, TeamRun, TimerResolution, ToolExecutionRecord, ToolExecutionResolution, ToolExecutionStatus, WorkflowBudgetUsage, WorkflowCheckpoint, WorkflowCompensationRecord, WorkflowRun, WorkflowStepRecord, WorkflowWaitRecord, createDigestContextCompactor, createModelContextCompactor, estimateContextTokens, isDurableRunStore, isRetainableRunStore, resolveContextBudget, validateListEventsOptions, validatePurgeRunsOptions };
@@ -1,15 +1,218 @@
1
- //#region src/runtime/index.ts
1
+ import { t as FevexRunError } from "../run-error-Cr17wPGH.mjs";
2
+ //#region src/runtime/compaction.ts
3
+ /** Validates a context budget and fills in defaults. */
4
+ function resolveContextBudget(budget, label) {
5
+ if (budget === void 0) return void 0;
6
+ if (!isRecord(budget)) throw new TypeError(`${label} must be an object`);
7
+ const contextWindow = budget.contextWindow;
8
+ if (!Number.isSafeInteger(contextWindow) || contextWindow <= 0) throw new TypeError(`${label}.contextWindow must be a positive safe integer`);
9
+ const reserveOutputTokens = budget.reserveOutputTokens ?? 0;
10
+ if (!Number.isSafeInteger(reserveOutputTokens) || reserveOutputTokens < 0 || reserveOutputTokens >= contextWindow) throw new TypeError(`${label}.reserveOutputTokens must be a non-negative safe integer below contextWindow`);
11
+ const usable = contextWindow - reserveOutputTokens;
12
+ const compactAt = budget.compactAt ?? .8;
13
+ if (typeof compactAt !== "number" || !Number.isFinite(compactAt) || compactAt <= 0 || compactAt > 1) throw new TypeError(`${label}.compactAt must be greater than 0 and at most 1`);
14
+ const targetTokens = budget.targetTokens ?? Math.max(1, Math.floor(usable * Math.min(.5, compactAt * .9)));
15
+ if (!Number.isSafeInteger(targetTokens) || targetTokens <= 0 || targetTokens >= usable) throw new TypeError(`${label}.targetTokens must be a positive safe integer below the usable window`);
16
+ if (targetTokens > Math.floor(usable * compactAt)) throw new TypeError(`${label}.targetTokens must not exceed the compaction threshold`);
17
+ const safetyFactor = budget.safetyFactor ?? 1.1;
18
+ if (typeof safetyFactor !== "number" || !Number.isFinite(safetyFactor) || safetyFactor < 1 || safetyFactor > 2) throw new TypeError(`${label}.safetyFactor must be between 1 and 2`);
19
+ return {
20
+ contextWindow,
21
+ reserveOutputTokens,
22
+ compactAt,
23
+ targetTokens,
24
+ safetyFactor
25
+ };
26
+ }
27
+ /**
28
+ * Rough provider-neutral estimate: about four characters per token plus
29
+ * message and tool framing. Hosts can inject a real tokenizer instead.
30
+ */
31
+ function estimateContextTokens(input) {
32
+ let characters = 0;
33
+ for (const message of input.messages) {
34
+ characters += message.role.length + message.content.length + 16;
35
+ if (message.name !== void 0) characters += message.name.length;
36
+ if (message.toolCallId !== void 0) characters += message.toolCallId.length;
37
+ if (message.toolCalls !== void 0) characters += JSON.stringify(message.toolCalls).length;
38
+ }
39
+ if (input.tools?.length) characters += JSON.stringify(input.tools).length;
40
+ return Math.max(1, Math.ceil(characters / 4));
41
+ }
42
+ const DEFAULT_COMPACTOR_PROMPT = "You compact conversation history for another agent. Preserve decisions, approvals, constraints, identifiers, tool results and open tasks. Return only the summary.";
43
+ /**
44
+ * Creates a semantic compactor backed by a model. Use a cheap, fast model and
45
+ * keep it separate from the agent model when possible. Large transcripts are
46
+ * summarized in chunks and then merged.
47
+ */
48
+ function createModelContextCompactor(options) {
49
+ const maxOutputTokens = options.maxOutputTokens ?? 1024;
50
+ if (!Number.isSafeInteger(maxOutputTokens) || maxOutputTokens <= 0) throw new TypeError("Model compactor maxOutputTokens must be a positive safe integer");
51
+ const maxInputChars = options.maxInputChars ?? 12e4;
52
+ if (!Number.isSafeInteger(maxInputChars) || maxInputChars < 1e3) throw new TypeError("Model compactor maxInputChars must be a safe integer of at least 1,000");
53
+ const systemPrompt = options.systemPrompt ?? DEFAULT_COMPACTOR_PROMPT;
54
+ const summarize = async (text, instruction, signal) => {
55
+ const stream = options.model.stream({
56
+ messages: [{
57
+ role: "system",
58
+ content: systemPrompt
59
+ }, {
60
+ role: "user",
61
+ content: `${instruction}\n\n${text || "(empty)"}`
62
+ }],
63
+ maxOutputTokens,
64
+ ...signal === void 0 ? {} : { signal }
65
+ });
66
+ let deltas = "";
67
+ let output;
68
+ for await (const event of stream) if (event.type === "output.delta") deltas += event.delta;
69
+ else output = event.result.output;
70
+ const summary = typeof output === "string" ? output : deltas;
71
+ if (!summary.trim()) throw new Error("Model context compactor returned an empty summary");
72
+ return summary.trim();
73
+ };
74
+ return { async compact(input) {
75
+ let current = formatTranscript(input.messages);
76
+ let depth = 0;
77
+ while (current.length > maxInputChars && depth < 4) {
78
+ const summaries = [];
79
+ for (const chunk of splitText(current, maxInputChars)) summaries.push(await summarize(chunk, "Summarize this part of a longer conversation. Preserve decisions, constraints, identifiers, tool results and open tasks.", input.signal));
80
+ current = summaries.join("\n\n");
81
+ depth += 1;
82
+ }
83
+ if (current.length > maxInputChars) current = `${current.slice(0, maxInputChars - 1)}…`;
84
+ return [{
85
+ role: "system",
86
+ content: await summarize(current, `Merge the following into one summary in at most ${input.targetTokens} tokens.`, input.signal)
87
+ }];
88
+ } };
89
+ }
90
+ function formatTranscript(messages) {
91
+ return messages.map((message) => {
92
+ const toolCalls = message.toolCalls?.length ? `\nTool calls: ${message.toolCalls.map((call) => `${call.name}(${JSON.stringify(call.input)})`).join(", ")}` : "";
93
+ const name = message.name === void 0 ? "" : ` (${message.name})`;
94
+ return `${message.role}${name}: ${message.content}${toolCalls}`;
95
+ }).join("\n\n");
96
+ }
97
+ function splitText(text, maxChars) {
98
+ const chunks = [];
99
+ let rest = text;
100
+ while (rest.length > maxChars) {
101
+ let cut = rest.lastIndexOf("\n", maxChars);
102
+ if (cut < Math.floor(maxChars / 2)) cut = maxChars;
103
+ chunks.push(rest.slice(0, cut));
104
+ rest = rest.slice(cut).trimStart();
105
+ }
106
+ if (rest) chunks.push(rest);
107
+ return chunks;
108
+ }
109
+ /**
110
+ * Deterministic compactor used when the host does not inject one. It keeps
111
+ * role-aware excerpts of older messages and drops the oldest excerpts first.
112
+ * It never calls a model.
113
+ */
114
+ function createDigestContextCompactor(options = {}) {
115
+ const maxChars = options.maxCharsPerMessage ?? 400;
116
+ if (!Number.isSafeInteger(maxChars) || maxChars < 40) throw new TypeError("Digest compactor maxCharsPerMessage must be a safe integer of at least 40");
117
+ return { compact(input) {
118
+ if (input.messages.length === 0) return [{
119
+ role: "system",
120
+ content: "[Compacted earlier conversation]"
121
+ }];
122
+ let perMessage = maxChars;
123
+ let lines = input.messages.flatMap((message) => digestMessage(message, perMessage));
124
+ while (input.estimateTokens([{
125
+ role: "system",
126
+ content: digestContent(lines, input.messages.length)
127
+ }]) > input.targetTokens && perMessage > 40) {
128
+ perMessage = Math.max(40, Math.floor(perMessage * .7));
129
+ lines = input.messages.flatMap((message) => digestMessage(message, perMessage));
130
+ }
131
+ let selected = lines;
132
+ let content = digestContent(selected, input.messages.length);
133
+ while (input.estimateTokens([{
134
+ role: "system",
135
+ content
136
+ }]) > input.targetTokens && selected.length > 1) {
137
+ selected = selected.slice(1);
138
+ content = digestContent(selected, input.messages.length);
139
+ }
140
+ const limit = Math.max(80, input.targetTokens * 4);
141
+ if (content.length > limit) content = `${content.slice(0, Math.max(0, limit - 1))}…`;
142
+ return [{
143
+ role: "system",
144
+ content
145
+ }];
146
+ } };
147
+ }
148
+ function digestContent(lines, totalMessages) {
149
+ const omitted = Math.max(0, totalMessages - lines.length);
150
+ return [`[Compacted conversation: ${totalMessages} earlier messages${omitted ? `, ${omitted} omitted` : ""}]`, ...lines].join("\n");
151
+ }
152
+ function digestMessage(message, maxChars) {
153
+ const content = truncate(message.content, maxChars);
154
+ if (message.role === "user") return content ? [`User: ${content}`] : [];
155
+ if (message.role === "assistant") {
156
+ const lines = [];
157
+ if (content) lines.push(`Assistant: ${content}`);
158
+ for (const call of message.toolCalls ?? []) lines.push(`Tool call ${call.name}: ${truncate(JSON.stringify(call.input), Math.min(maxChars, 200))}`);
159
+ return lines;
160
+ }
161
+ if (message.role === "tool") return content ? [`Tool result${message.name === void 0 ? "" : ` (${message.name})`}: ${content}`] : [];
162
+ return content ? [`System: ${content}`] : [];
163
+ }
164
+ function truncate(value, maxChars) {
165
+ const normalized = value.replace(/\s+/g, " ").trim();
166
+ if (normalized.length <= maxChars) return normalized;
167
+ return `${normalized.slice(0, Math.max(0, maxChars - 1))}…`;
168
+ }
169
+ function isRecord(value) {
170
+ return typeof value === "object" && value !== null && !Array.isArray(value);
171
+ }
172
+ //#endregion
173
+ //#region src/runtime/run-store.ts
174
+ function validateListEventsOptions(options) {
175
+ if (options.limit !== void 0 && (!Number.isSafeInteger(options.limit) || options.limit < 1)) throw new TypeError("Event limit must be a positive safe integer");
176
+ if (options.order !== void 0 && options.order !== "asc" && options.order !== "desc") throw new TypeError("Event order must be asc or desc");
177
+ }
178
+ function isRetainableRunStore(store) {
179
+ return typeof store.purgeRuns === "function";
180
+ }
181
+ function validatePurgeRunsOptions(options = {}) {
182
+ const statuses = options.statuses ?? [
183
+ "completed",
184
+ "failed",
185
+ "cancelled"
186
+ ];
187
+ if (statuses.length === 0 || statuses.some((status) => status !== "completed" && status !== "failed" && status !== "cancelled")) throw new TypeError("Purge statuses must contain only completed, failed or cancelled");
188
+ if (options.before !== void 0 && Number.isNaN(Date.parse(options.before))) throw new TypeError("Purge before must be an ISO timestamp");
189
+ if (options.limit !== void 0 && (!Number.isSafeInteger(options.limit) || options.limit < 1)) throw new TypeError("Purge limit must be a positive safe integer");
190
+ return {
191
+ statuses,
192
+ ...options.before === void 0 ? {} : { before: new Date(options.before).toISOString() },
193
+ ...options.limit === void 0 ? {} : { limit: options.limit }
194
+ };
195
+ }
2
196
  function isDurableRunStore(store) {
3
197
  const value = store;
4
198
  return [
5
199
  value.getCheckpoint,
6
200
  value.getToolExecution,
201
+ value.createExecution,
7
202
  value.commitExecution,
8
203
  value.acquireLease,
9
204
  value.renewLease,
10
205
  value.releaseLease
11
206
  ].every((method) => typeof method === "function");
12
207
  }
208
+ //#endregion
209
+ //#region src/runtime/in-memory-run-store.ts
210
+ /**
211
+ * In-process durable store intended for development and tests.
212
+ *
213
+ * Values are cloned at the boundary so callers cannot mutate persisted state.
214
+ * Data is lost when the process exits.
215
+ */
13
216
  var InMemoryRunStore = class {
14
217
  #runs = /* @__PURE__ */ new Map();
15
218
  #sessions = /* @__PURE__ */ new Map();
@@ -30,6 +233,9 @@ var InMemoryRunStore = class {
30
233
  return session === void 0 ? void 0 : structuredClone(session);
31
234
  }
32
235
  async saveSession(session) {
236
+ const current = this.#sessions.get(session.id);
237
+ if (this.#sessionBusy(session.id) || (current?.revision ?? 0) !== (session.revision ?? 0)) throw new FevexRunError("RUN_CONFLICT", `Session "${session.id}" is active or was modified`);
238
+ session.revision = (session.revision ?? 0) + 1;
33
239
  this.#sessions.set(session.id, structuredClone(session));
34
240
  }
35
241
  async appendEvent(event) {
@@ -38,15 +244,18 @@ var InMemoryRunStore = class {
38
244
  events.push(structuredClone(event));
39
245
  }
40
246
  async listEvents(runId, options = {}) {
247
+ validateListEventsOptions(options);
41
248
  const events = this.#events.get(runId);
42
249
  if (!events) throw new Error(`Run "${runId}" does not exist`);
43
250
  let start = 0;
44
251
  if (options.after !== void 0) {
45
252
  const cursor = events.findIndex(({ id }) => id === options.after);
46
- if (cursor < 0) throw new Error(`Event cursor "${options.after}" does not exist in run "${runId}"`);
253
+ if (cursor < 0) throw new FevexRunError("INVALID_CURSOR", `Event cursor "${options.after}" does not exist in run "${runId}"`, runId);
47
254
  start = cursor + 1;
48
255
  }
49
- return structuredClone(events.slice(start).sort((a, b) => a.sequence - b.sequence));
256
+ const sequence = start ? events[start - 1].sequence : -1;
257
+ const ordered = events.filter((event) => event.sequence > sequence).sort((a, b) => options.order === "desc" ? b.sequence - a.sequence : a.sequence - b.sequence);
258
+ return structuredClone(ordered.slice(0, options.limit));
50
259
  }
51
260
  async getCheckpoint(runId) {
52
261
  const checkpoint = this.#checkpoints.get(runId);
@@ -56,14 +265,37 @@ var InMemoryRunStore = class {
56
265
  const execution = this.#toolExecutions.get(`${runId}:${toolCallId}`);
57
266
  return execution === void 0 ? void 0 : structuredClone(execution);
58
267
  }
268
+ async createExecution(create) {
269
+ if (this.#runs.has(create.run.id)) return false;
270
+ const session = create.session;
271
+ const currentSession = this.#sessions.get(create.run.sessionId);
272
+ if (!session || session.id !== create.run.sessionId || this.#sessionBusy(session.id) || (currentSession?.revision ?? 0) !== (session.revision ?? 0)) return false;
273
+ const run = structuredClone(create.run);
274
+ run.revision = 1;
275
+ this.#runs.set(run.id, run);
276
+ create.run.revision = 1;
277
+ session.revision = (session.revision ?? 0) + 1;
278
+ this.#sessions.set(session.id, structuredClone(session));
279
+ this.#events.set(run.id, structuredClone(create.events));
280
+ this.#checkpoints.set(run.id, structuredClone(create.checkpoint));
281
+ create.lease.generation = 1;
282
+ this.#leases.set(run.id, structuredClone(create.lease));
283
+ return true;
284
+ }
59
285
  async commitExecution(commit) {
60
286
  const current = this.#runs.get(commit.run.id);
61
287
  if (!current || current.revision !== commit.expectedRevision) return false;
288
+ const lease = this.#leases.get(commit.run.id);
289
+ if (!lease || !commit.lease || !(commit.lease.generation > 0) || lease.ownerId !== commit.lease.ownerId || lease.generation !== commit.lease.generation || !(Date.parse(lease.expiresAt) > Date.now())) return false;
290
+ if (commit.session && (commit.session.id !== current.sessionId || (this.#sessions.get(current.sessionId)?.revision ?? 0) !== (commit.session.revision ?? 0) || this.#sessionBusy(current.sessionId, current.id))) return false;
62
291
  const run = structuredClone(commit.run);
63
292
  run.revision = commit.expectedRevision + 1;
64
293
  this.#runs.set(run.id, run);
65
294
  commit.run.revision = run.revision;
66
- if (commit.session) this.#sessions.set(commit.session.id, structuredClone(commit.session));
295
+ if (commit.session) {
296
+ commit.session.revision = (commit.session.revision ?? 0) + 1;
297
+ this.#sessions.set(commit.session.id, structuredClone(commit.session));
298
+ }
67
299
  if (commit.checkpoint === null) this.#checkpoints.delete(run.id);
68
300
  else if (commit.checkpoint) this.#checkpoints.set(run.id, structuredClone(commit.checkpoint));
69
301
  if (commit.toolExecution) this.#toolExecutions.set(`${run.id}:${commit.toolExecution.toolCallId}`, structuredClone(commit.toolExecution));
@@ -72,21 +304,51 @@ var InMemoryRunStore = class {
72
304
  for (const event of commit.events ?? []) events.push(structuredClone(event));
73
305
  return true;
74
306
  }
307
+ #sessionBusy(sessionId, exceptRunId) {
308
+ return [...this.#runs.values()].some((run) => run.sessionId === sessionId && run.id !== exceptRunId && (run.status === "running" || run.status === "paused"));
309
+ }
310
+ async purgeRuns(options = {}) {
311
+ const { statuses, before, limit } = validatePurgeRunsOptions(options);
312
+ const allowed = new Set(statuses);
313
+ const beforeTime = before === void 0 ? void 0 : Date.parse(before);
314
+ const candidates = [...this.#runs.entries()].filter(([, run]) => allowed.has(run.status) && (beforeTime === void 0 || Date.parse(run.createdAt) < beforeTime)).sort(([, first], [, second]) => Date.parse(first.createdAt) - Date.parse(second.createdAt)).slice(0, limit);
315
+ for (const [runId] of candidates) {
316
+ this.#runs.delete(runId);
317
+ this.#events.delete(runId);
318
+ this.#checkpoints.delete(runId);
319
+ this.#leases.delete(runId);
320
+ for (const key of this.#toolExecutions.keys()) if (key.startsWith(`${runId}:`)) this.#toolExecutions.delete(key);
321
+ }
322
+ const affectedSessions = new Set(candidates.map(([, run]) => run.sessionId));
323
+ const usedSessions = new Set([...this.#runs.values()].map(({ sessionId }) => sessionId));
324
+ let sessions = 0;
325
+ for (const sessionId of affectedSessions) {
326
+ if (usedSessions.has(sessionId)) continue;
327
+ if (this.#sessions.delete(sessionId)) sessions += 1;
328
+ }
329
+ return {
330
+ runs: candidates.length,
331
+ sessions
332
+ };
333
+ }
75
334
  async acquireLease(lease) {
76
335
  const current = this.#leases.get(lease.runId);
77
- if (current && current.ownerId !== lease.ownerId && Date.parse(current.expiresAt) > Date.now()) return false;
336
+ if (current && Date.parse(current.expiresAt) > Date.now()) return false;
337
+ lease.generation = (current?.generation ?? 0) + 1;
78
338
  this.#leases.set(lease.runId, structuredClone(lease));
79
339
  return true;
80
340
  }
81
341
  async renewLease(lease) {
82
342
  const current = this.#leases.get(lease.runId);
83
- if (!current || current.ownerId !== lease.ownerId) return false;
343
+ if (!current || current.ownerId !== lease.ownerId || current.generation !== lease.generation || !(Date.parse(current.expiresAt) > Date.now())) return false;
84
344
  this.#leases.set(lease.runId, structuredClone(lease));
85
345
  return true;
86
346
  }
87
- async releaseLease(runId, ownerId) {
88
- if (this.#leases.get(runId)?.ownerId === ownerId) this.#leases.delete(runId);
347
+ async releaseLease(runId, ownerId, generation) {
348
+ if (!(generation > 0)) return;
349
+ const current = this.#leases.get(runId);
350
+ if (current?.ownerId === ownerId && current.generation === generation) current.expiresAt = (/* @__PURE__ */ new Date(0)).toISOString();
89
351
  }
90
352
  };
91
353
  //#endregion
92
- export { InMemoryRunStore, isDurableRunStore };
354
+ export { InMemoryRunStore, createDigestContextCompactor, createModelContextCompactor, estimateContextTokens, isDurableRunStore, isRetainableRunStore, resolveContextBudget, validateListEventsOptions, validatePurgeRunsOptions };
@@ -0,0 +1,2 @@
1
+ import { a as SandboxError, c as sandboxResultToJson, i as SandboxCapabilities, n as createLocalSandbox, o as SandboxRunRequest, r as Sandbox, s as SandboxRunResult, t as LocalSandboxOptions } from "../index-DUhNa7IR.mjs";
2
+ export { LocalSandboxOptions, Sandbox, SandboxCapabilities, SandboxError, SandboxRunRequest, SandboxRunResult, createLocalSandbox, sandboxResultToJson };