@fevex/core 0.1.0-alpha.7 → 0.1.0-alpha.9

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 (34) hide show
  1. package/README.md +49 -0
  2. package/dist/agents/index.d.mts +2 -2
  3. package/dist/artifacts/index.d.mts +1 -1
  4. package/dist/channels/index.d.mts +1 -1
  5. package/dist/evals/index.d.mts +4 -4
  6. package/dist/{fevex-CVMLnZsk.d.mts → fevex-WBvRWSpy.d.mts} +20 -10
  7. package/dist/http/index.d.mts +4 -4
  8. package/dist/{index-CzAi2Y_Z.d.mts → index-3bFNzCiY.d.mts} +154 -4
  9. package/dist/{index-CbACw6xZ.d.mts → index-BLgiXQK4.d.mts} +2 -2
  10. package/dist/{index-5p3q2yIp.d.mts → index-BbwT5XxQ.d.mts} +94 -4
  11. package/dist/{index-Df84VWmN.d.mts → index-CL7mu_x9.d.mts} +1 -1
  12. package/dist/{index-BTD1c4Ys.d.mts → index-CmFBB4Px.d.mts} +3 -3
  13. package/dist/{index-B0AdFbgr.d.mts → index-D4mWV2zG.d.mts} +2 -2
  14. package/dist/{index-CpLDt6Rw.d.mts → index-D7FoZ7aQ.d.mts} +3 -3
  15. package/dist/{index-BgKFd9yw.d.mts → index-D7r0puBi.d.mts} +15 -2
  16. package/dist/{index-1cHr7KB_.d.mts → index-D8lCFkc7.d.mts} +4 -4
  17. package/dist/{index-CseuTR-F.d.mts → index-DgSxJZo4.d.mts} +3 -3
  18. package/dist/index.d.mts +13 -13
  19. package/dist/index.mjs +655 -78
  20. package/dist/knowledge/index.d.mts +1 -1
  21. package/dist/models/index.d.mts +2 -2
  22. package/dist/observability/index.d.mts +1 -1
  23. package/dist/policies/index.d.mts +1 -1
  24. package/dist/runtime/index.d.mts +2 -2
  25. package/dist/runtime/index.mjs +172 -1
  26. package/dist/sandbox/index.d.mts +1 -1
  27. package/dist/teams/index.d.mts +1 -1
  28. package/dist/testing/index.d.mts +6 -6
  29. package/dist/tools/index.d.mts +3 -3
  30. package/dist/tools/index.mjs +221 -1
  31. package/dist/{types-B8laW1hM.d.mts → types-CckDlhYo.d.mts} +15 -1
  32. package/dist/workflows/index.d.mts +1 -1
  33. package/package.json +1 -1
  34. package/dist/exact-definition-NHBBFlFR.d.mts +0 -4
package/dist/index.mjs CHANGED
@@ -4,8 +4,8 @@ import { n as RunPausedError, t as FevexRunError } from "./run-error-Cr17wPGH.mj
4
4
  import { ChannelError, deliverChannelRun, handleChannelInput } from "./channels/index.mjs";
5
5
  import { InMemoryMemoryStore, createAgentsMdContextProvider, createMemoryContextProvider, defineContextProvider, defineSkill, isProgressiveSkillProvider, loadSkillFile, loadSkillFiles } from "./knowledge/index.mjs";
6
6
  import { n as teamAsWorkflow, t as defineTeam } from "./teams-Ctz676G4.mjs";
7
- import { InMemoryRunStore, isDurableRunStore, isRetainableRunStore, validateListEventsOptions, validatePurgeRunsOptions } from "./runtime/index.mjs";
8
- import { FEVEX_JSON_SCHEMA_PROFILE_VERSION, IntegrationError, compileFevexJsonSchema, defineConnection, defineTool, toToolSpec, validateFevexJsonSchemaProfile } from "./tools/index.mjs";
7
+ import { InMemoryRunStore, createDigestContextCompactor, createModelContextCompactor, estimateContextTokens, isDurableRunStore, isRetainableRunStore, resolveContextBudget, validateListEventsOptions, validatePurgeRunsOptions } from "./runtime/index.mjs";
8
+ import { FEVEX_JSON_SCHEMA_PROFILE_VERSION, InMemoryToolEmbeddingCache, IntegrationError, buildToolEmbeddingIndex, compileFevexJsonSchema, cosineSimilarity, defineConnection, defineTool, schemaKeywords, searchToolEmbeddingIndex, toToolSpec, toolEmbeddingKey, toolEmbeddingText, validateFevexJsonSchemaProfile } from "./tools/index.mjs";
9
9
  import { _ as serializeValue, a as assertTokenBudget, c as eventUsage, d as remainingDeadlineMs, f as remainingOutputTokens, g as serializeJsonValue, h as toErrorMessage, i as assertContinuationBudget, l as isTimeoutError, m as runSignal, n as abortable, o as cancellationReason, p as resumeDeadlineSignal, r as addUsage, s as deadlineAtFromMs, u as minDeadline, v as toJsonValue } from "./run-support-CmZ6Xo6H.mjs";
10
10
  import { t as readModelStream } from "./model-stream-JQdHDDcj.mjs";
11
11
  import "./models/index.mjs";
@@ -67,6 +67,139 @@ async function validateSchema(schema, value, errorMessage) {
67
67
  return result.value;
68
68
  }
69
69
  //#endregion
70
+ //#region src/internal/tool-search.ts
71
+ function resolveToolSearchOptions(options, label, defaultCache) {
72
+ if (options !== void 0 && (typeof options !== "object" || options === null || Array.isArray(options))) throw new TypeError(`${label} must be an object`);
73
+ const mode = options?.mode ?? "auto";
74
+ if (mode !== "auto" && mode !== "on" && mode !== "off") throw new TypeError(`${label}.mode must be auto, on or off`);
75
+ const threshold = options?.threshold ?? 30;
76
+ if (!Number.isSafeInteger(threshold) || threshold < 1) throw new TypeError(`${label}.threshold must be a positive safe integer`);
77
+ const tokenThreshold = options?.tokenThreshold;
78
+ if (tokenThreshold !== void 0 && (!Number.isSafeInteger(tokenThreshold) || tokenThreshold < 1)) throw new TypeError(`${label}.tokenThreshold must be a positive safe integer`);
79
+ const limit = options?.limit ?? 5;
80
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 20) throw new TypeError(`${label}.limit must be a safe integer between 1 and 20`);
81
+ const pinned = options?.pinned ?? [];
82
+ if (!Array.isArray(pinned) || pinned.some((name) => typeof name !== "string" || !name.trim())) throw new TypeError(`${label}.pinned must contain non-empty tool names`);
83
+ const normalizedPinned = [...new Set(pinned.map((name) => name.trim()))];
84
+ if (normalizedPinned.length !== pinned.length) throw new TypeError(`${label}.pinned must contain unique tool names`);
85
+ const semantic = options?.semantic;
86
+ return {
87
+ mode,
88
+ threshold,
89
+ ...tokenThreshold === void 0 ? {} : { tokenThreshold },
90
+ limit,
91
+ pinned: normalizedPinned,
92
+ ...semantic === void 0 ? {} : { semantic: resolveSemanticOptions(semantic, `${label}.semantic`, defaultCache) }
93
+ };
94
+ }
95
+ function serializeToolSearchOptions(options) {
96
+ return {
97
+ mode: options.mode,
98
+ threshold: options.threshold,
99
+ ...options.tokenThreshold === void 0 ? {} : { tokenThreshold: options.tokenThreshold },
100
+ limit: options.limit,
101
+ pinned: [...options.pinned]
102
+ };
103
+ }
104
+ function reciprocalRankFusionCandidates(lexical, semantic, limit, rankConstant = 60) {
105
+ if (!Number.isSafeInteger(limit) || limit < 1) throw new TypeError("Tool search fusion limit must be a positive safe integer");
106
+ if (!Number.isSafeInteger(rankConstant) || rankConstant < 1) throw new TypeError("Tool search fusion rankConstant must be a positive safe integer");
107
+ const scores = /* @__PURE__ */ new Map();
108
+ const add = (tools) => {
109
+ for (let index = 0; index < tools.length; index += 1) {
110
+ const tool = tools[index];
111
+ const entry = scores.get(tool.name) ?? {
112
+ tool,
113
+ score: 0
114
+ };
115
+ entry.score += 1 / (rankConstant + index + 1);
116
+ scores.set(tool.name, entry);
117
+ }
118
+ };
119
+ add(lexical);
120
+ add(semantic);
121
+ return [...scores.values()].sort((left, right) => right.score - left.score || left.tool.name.localeCompare(right.tool.name)).slice(0, limit);
122
+ }
123
+ function resolveSemanticOptions(semantic, label, defaultCache) {
124
+ if (typeof semantic !== "object" || semantic === null || Array.isArray(semantic)) throw new TypeError(`${label} must be an object`);
125
+ const embeddings = semantic.embeddings;
126
+ if (typeof embeddings !== "object" || embeddings === null || typeof embeddings.id !== "string" || !embeddings.id.trim() || typeof embeddings.embed !== "function") throw new TypeError(`${label}.embeddings must implement EmbeddingModel`);
127
+ if (embeddings.dimensions !== void 0 && (!Number.isSafeInteger(embeddings.dimensions) || embeddings.dimensions < 1)) throw new TypeError(`${label}.embeddings.dimensions must be a positive safe integer`);
128
+ const cache = semantic.cache ?? defaultCache;
129
+ if (cache === void 0 || typeof cache.get !== "function" || typeof cache.set !== "function") throw new TypeError(`${label}.cache must implement get and set`);
130
+ const topK = semantic.topK ?? 20;
131
+ if (!Number.isSafeInteger(topK) || topK < 1) throw new TypeError(`${label}.topK must be a positive safe integer`);
132
+ const minScore = semantic.minScore ?? 0;
133
+ if (typeof minScore !== "number" || !Number.isFinite(minScore) || minScore < -1 || minScore > 1) throw new TypeError(`${label}.minScore must be a finite number between -1 and 1`);
134
+ const reranker = semantic.reranker;
135
+ if (reranker !== void 0 && (typeof reranker !== "object" || reranker === null || typeof reranker.rerank !== "function")) throw new TypeError(`${label}.reranker must implement rerank`);
136
+ return {
137
+ embeddings,
138
+ cache,
139
+ topK,
140
+ minScore,
141
+ ...reranker === void 0 ? {} : { reranker }
142
+ };
143
+ }
144
+ function shouldUseToolSearch(options, tools) {
145
+ if (options.mode === "off" || tools.length === 0) return false;
146
+ if (options.mode === "on") return true;
147
+ if (tools.length > options.threshold) return true;
148
+ if (options.tokenThreshold === void 0) return false;
149
+ return Math.ceil(JSON.stringify(tools).length / 4) > options.tokenThreshold;
150
+ }
151
+ const searchIndex = /* @__PURE__ */ new WeakMap();
152
+ function indexedTools(tools) {
153
+ const cached = searchIndex.get(tools);
154
+ if (cached !== void 0) return cached;
155
+ const index = tools.map((tool) => {
156
+ const name = tool.name.toLowerCase();
157
+ const description = (tool.description ?? "").toLowerCase();
158
+ return {
159
+ tool,
160
+ name,
161
+ nameTerms: new Set(name.split(/[^a-z0-9]+/).filter(Boolean)),
162
+ description,
163
+ descriptionTerms: new Set(description.split(/[^a-z0-9]+/).filter(Boolean)),
164
+ schema: JSON.stringify(tool.inputSchema ?? {}).toLowerCase()
165
+ };
166
+ });
167
+ searchIndex.set(tools, index);
168
+ return index;
169
+ }
170
+ function searchToolSpecs(tools, query, limit) {
171
+ const terms = [...new Set(query.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 1))];
172
+ if (terms.length === 0) return [];
173
+ return indexedTools(tools).map(({ tool, name, nameTerms, description, descriptionTerms, schema }) => {
174
+ let score = 0;
175
+ for (const term of terms) {
176
+ if (name === term) score += 100;
177
+ else if (nameTerms.has(term)) score += 40;
178
+ else if (name.startsWith(term)) score += 30;
179
+ else if (name.includes(term)) score += 20;
180
+ if (descriptionTerms.has(term)) score += 8;
181
+ else if (description.includes(term)) score += 5;
182
+ if (schema.includes(term)) score += 1;
183
+ }
184
+ return {
185
+ tool,
186
+ score
187
+ };
188
+ }).filter(({ score }) => score > 0).sort((left, right) => right.score - left.score || left.tool.name.localeCompare(right.tool.name)).slice(0, limit).map(({ tool }) => tool);
189
+ }
190
+ function readToolSearchInput(input) {
191
+ if (typeof input !== "object" || input === null || Array.isArray(input)) throw new TypeError("Tool search input must be an object");
192
+ const record = input;
193
+ const query = record.query;
194
+ const limit = record.limit;
195
+ if (typeof query !== "string" || !query.trim()) throw new TypeError("Tool search query must be a non-empty string");
196
+ if (limit !== void 0 && (typeof limit !== "number" || !Number.isSafeInteger(limit) || limit < 1)) throw new TypeError("Tool search limit must be a positive safe integer");
197
+ return {
198
+ query: query.trim(),
199
+ ...limit === void 0 ? {} : { limit }
200
+ };
201
+ }
202
+ //#endregion
70
203
  //#region src/internal/configuration.ts
71
204
  const REASONING_EFFORTS = /* @__PURE__ */ new Set([
72
205
  "provider-default",
@@ -190,6 +323,7 @@ function expandConnection(connection) {
190
323
  };
191
324
  assertConfiguration(policy?.inputSchema === void 0 || isJsonObject(policy.inputSchema), "INVALID_TOOL", `Input schema for connection tool "${remoteName}" must be a JSON object`);
192
325
  assertConfiguration(policy?.outputSchema === void 0 || isJsonObject(policy.outputSchema), "INVALID_TOOL", `Output schema for connection tool "${remoteName}" must be a JSON object`);
326
+ assertConfiguration(policy?.maxResultBytes === void 0 || policy.maxResultBytes === false || Number.isSafeInteger(policy.maxResultBytes) && policy.maxResultBytes > 0, "INVALID_TOOL", `maxResultBytes for connection tool "${remoteName}" must be a positive safe integer or false`);
193
327
  return {
194
328
  name: namespacedToolName(connection.name, remoteName),
195
329
  ...policy?.label === void 0 ? {} : { label: policy.label },
@@ -201,6 +335,7 @@ function expandConnection(connection) {
201
335
  ...policy?.idempotency === void 0 ? {} : { idempotency: policy.idempotency },
202
336
  ...policy?.retry === void 0 ? {} : { retry: policy.retry },
203
337
  ...policy?.credentials === void 0 ? {} : { credentials: policy.credentials },
338
+ ...policy?.maxResultBytes === void 0 ? {} : { maxResultBytes: policy.maxResultBytes },
204
339
  source,
205
340
  async resolve(context) {
206
341
  try {
@@ -300,6 +435,24 @@ function createComposition(config) {
300
435
  assertConfiguration(config.tools === void 0 || Array.isArray(config.tools), "INVALID_CONFIG", "Fevex config \"tools\" must be an array");
301
436
  assertConfiguration(config.connections === void 0 || Array.isArray(config.connections), "INVALID_CONFIG", "Fevex config \"connections\" must be an array");
302
437
  assertConfiguration(config.contextProviders === void 0 || Array.isArray(config.contextProviders), "INVALID_CONFIG", "Fevex config \"contextProviders\" must be an array");
438
+ if (config.contextBudget !== void 0) try {
439
+ resolveContextBudget(config.contextBudget, "Fevex config \"contextBudget\"");
440
+ } catch (error) {
441
+ throw configurationError("INVALID_CONFIG", error.message);
442
+ }
443
+ assertConfiguration(config.toolSearchCache === void 0 || isRecord(config.toolSearchCache) && typeof config.toolSearchCache.get === "function" && typeof config.toolSearchCache.set === "function", "INVALID_CONFIG", "Fevex config \"toolSearchCache\" must implement get and set");
444
+ const defaultToolSearchCache = config.toolSearchCache ?? new InMemoryToolEmbeddingCache();
445
+ if (config.toolSearch !== void 0) try {
446
+ resolveToolSearchOptions(config.toolSearch, "Fevex config \"toolSearch\"", defaultToolSearchCache);
447
+ } catch (error) {
448
+ throw configurationError("INVALID_CONFIG", error.message);
449
+ }
450
+ if (config.compaction !== void 0) {
451
+ assertConfiguration(typeof config.compaction === "object" && config.compaction !== null && !Array.isArray(config.compaction), "INVALID_CONFIG", "Fevex config \"compaction\" must be an object");
452
+ assertConfiguration(config.compaction.compactor === void 0 || isRecord(config.compaction.compactor) && typeof config.compaction.compactor.compact === "function", "INVALID_CONFIG", "Fevex config \"compaction.compactor\" must implement compact");
453
+ assertConfiguration(config.compaction.estimateTokens === void 0 || typeof config.compaction.estimateTokens === "function", "INVALID_CONFIG", "Fevex config \"compaction.estimateTokens\" must be a function");
454
+ assertConfiguration(config.compaction.redactSummary === void 0 || typeof config.compaction.redactSummary === "function", "INVALID_CONFIG", "Fevex config \"compaction.redactSummary\" must be a function");
455
+ }
303
456
  assertConfiguration(config.memoryStore === void 0 || isMemoryStore(config.memoryStore), "INVALID_CONFIG", "Fevex config \"memoryStore\" must implement MemoryStore");
304
457
  assertConfiguration(config.onEvent === void 0 || typeof config.onEvent === "function", "INVALID_CONFIG", "Fevex config \"onEvent\" must be a function");
305
458
  assertConfiguration(config.runStore === void 0 || isRunStore(config.runStore), "INVALID_CONFIG", "Fevex config \"runStore\" must implement RunStore");
@@ -385,6 +538,7 @@ function createComposition(config) {
385
538
  assertConfiguration(definition.credentials === void 0 || Array.isArray(definition.credentials) && definition.credentials.every((value) => typeof value === "string" && value.trim()), "INVALID_TOOL", `Tool "${name}" credentials must be non-empty strings`);
386
539
  assertSandboxCapabilities(definition.sandbox, name);
387
540
  assertConfiguration(definition.sandbox === void 0 || config.sandbox !== void 0, "INVALID_TOOL", `Tool "${name}" requires Fevex config "sandbox"`);
541
+ assertConfiguration(definition.maxResultBytes === void 0 || definition.maxResultBytes === false || Number.isSafeInteger(definition.maxResultBytes) && definition.maxResultBytes > 0, "INVALID_TOOL", `Tool "${name}" maxResultBytes must be a positive safe integer or false`);
388
542
  if (definition.retry !== void 0) assertConfiguration(isRecord(definition.retry) && Number.isInteger(definition.retry.maxAttempts) && definition.retry.maxAttempts >= 1 && Number.isInteger(definition.retry.backoffMs) && definition.retry.backoffMs >= 0 && (definition.retry.maxBackoffMs === void 0 || Number.isInteger(definition.retry.maxBackoffMs) && definition.retry.maxBackoffMs >= 0), "INVALID_TOOL", `Tool "${name}" retry is invalid`);
389
543
  if (definition.inputSchema !== void 0) assertConfiguration(isStandardSchema(definition.inputSchema), "INVALID_TOOL", `Input schema for tool "${name}" must implement Standard Schema`);
390
544
  if (definition.inputSchema !== void 0) assertConfiguration(isStandardSchema(definition.inputSchema), "INVALID_AGENT", `Input schema for agent "${name}" must implement Standard Schema`);
@@ -404,6 +558,7 @@ function createComposition(config) {
404
558
  ...definition.idempotency === void 0 ? {} : { idempotency: definition.idempotency },
405
559
  ...definition.retry === void 0 ? {} : { retry: { ...definition.retry } },
406
560
  ...definition.credentials === void 0 ? {} : { credentials: [...definition.credentials] },
561
+ ...definition.maxResultBytes === void 0 ? {} : { maxResultBytes: definition.maxResultBytes },
407
562
  ...definition.sandbox === void 0 ? {} : { sandbox: copySandboxCapabilities(definition.sandbox) },
408
563
  ...definition.source === void 0 ? {} : { source: { ...definition.source } },
409
564
  ...definition.resolve === void 0 ? {} : { resolve: definition.resolve },
@@ -475,12 +630,23 @@ function createComposition(config) {
475
630
  assertConfiguration(definition.approvalMode === void 0 || definition.approvalMode === "pause" || definition.approvalMode === "deny", "INVALID_AGENT", `Agent "${name}" approvalMode is invalid`);
476
631
  assertConfiguration(definition.toolChoice === void 0 || definition.toolChoice === "auto" || definition.toolChoice === "required" || definition.toolChoice === "none" || isRecord(definition.toolChoice) && typeof definition.toolChoice.name === "string" && Boolean(definition.toolChoice.name.trim()), "INVALID_AGENT", `Agent "${name}" toolChoice is invalid`);
477
632
  assertConfiguration(definition.limits === void 0 || isRecord(definition.limits), "INVALID_AGENT", `Agent "${name}" limits must be an object`);
633
+ if (definition.contextBudget !== void 0) try {
634
+ resolveContextBudget(definition.contextBudget, `Agent "${name}" contextBudget`);
635
+ } catch (error) {
636
+ throw configurationError("INVALID_AGENT", error.message);
637
+ }
638
+ if (definition.toolSearch !== void 0) try {
639
+ resolveToolSearchOptions(definition.toolSearch, `Agent "${name}" toolSearch`, defaultToolSearchCache);
640
+ } catch (error) {
641
+ throw configurationError("INVALID_AGENT", error.message);
642
+ }
478
643
  if (definition.outputSchema !== void 0) assertConfiguration(isStandardSchema(definition.outputSchema), "INVALID_AGENT", `Output schema for agent "${name}" must implement Standard Schema`);
479
644
  const rawLimits = definition.limits;
480
645
  assertLimit(rawLimits?.maxSteps, "maxSteps", 1, name);
481
646
  assertLimit(rawLimits?.maxToolCalls, "maxToolCalls", 0, name);
482
647
  assertLimit(rawLimits?.maxInputTokens, "maxInputTokens", 1, name, true);
483
648
  assertLimit(rawLimits?.maxOutputTokens, "maxOutputTokens", 1, name, true);
649
+ assertLimit(rawLimits?.maxToolResultBytes, "maxToolResultBytes", 1, name, true);
484
650
  agents.set(name, {
485
651
  name,
486
652
  instructions: definition.instructions,
@@ -498,13 +664,19 @@ function createComposition(config) {
498
664
  ...definition.toolChoice === void 0 ? {} : { toolChoice: typeof definition.toolChoice === "string" ? definition.toolChoice : { name: definition.toolChoice.name } },
499
665
  ...definition.elicitation === void 0 ? {} : { elicitation: definition.elicitation },
500
666
  ...definition.approvalMode === void 0 ? {} : { approvalMode: definition.approvalMode },
667
+ ...definition.contextBudget === void 0 ? {} : { contextBudget: { ...definition.contextBudget } },
668
+ ...definition.toolSearch === void 0 ? {} : { toolSearch: {
669
+ ...definition.toolSearch,
670
+ ...definition.toolSearch.pinned === void 0 ? {} : { pinned: [...definition.toolSearch.pinned] }
671
+ } },
501
672
  ...definition.inputSchema === void 0 ? {} : { inputSchema: definition.inputSchema },
502
673
  ...definition.outputSchema === void 0 ? {} : { outputSchema: definition.outputSchema },
503
674
  ...rawLimits === void 0 ? {} : { limits: {
504
675
  ...rawLimits.maxSteps === void 0 ? {} : { maxSteps: rawLimits.maxSteps },
505
676
  ...rawLimits.maxToolCalls === void 0 ? {} : { maxToolCalls: rawLimits.maxToolCalls },
506
677
  ...rawLimits.maxInputTokens === void 0 ? {} : { maxInputTokens: rawLimits.maxInputTokens },
507
- ...rawLimits.maxOutputTokens === void 0 ? {} : { maxOutputTokens: rawLimits.maxOutputTokens }
678
+ ...rawLimits.maxOutputTokens === void 0 ? {} : { maxOutputTokens: rawLimits.maxOutputTokens },
679
+ ...rawLimits.maxToolResultBytes === void 0 ? {} : { maxToolResultBytes: rawLimits.maxToolResultBytes }
508
680
  } }
509
681
  });
510
682
  }
@@ -541,6 +713,7 @@ function createComposition(config) {
541
713
  assertLimit(rawLimits?.maxToolCalls, "maxToolCalls", 0, name, false, "Workflow", "INVALID_WORKFLOW");
542
714
  assertLimit(rawLimits?.maxInputTokens, "maxInputTokens", 1, name, true, "Workflow", "INVALID_WORKFLOW");
543
715
  assertLimit(rawLimits?.maxOutputTokens, "maxOutputTokens", 1, name, true, "Workflow", "INVALID_WORKFLOW");
716
+ assertLimit(rawLimits?.maxToolResultBytes, "maxToolResultBytes", 1, name, false, "Workflow", "INVALID_WORKFLOW");
544
717
  assertConfiguration(!workflows.has(name), "DUPLICATE_WORKFLOW", `Workflow "${name}" is duplicated`);
545
718
  workflows.set(name, {
546
719
  name,
@@ -552,7 +725,8 @@ function createComposition(config) {
552
725
  ...rawLimits.maxSteps === void 0 ? {} : { maxSteps: rawLimits.maxSteps },
553
726
  ...rawLimits.maxToolCalls === void 0 ? {} : { maxToolCalls: rawLimits.maxToolCalls },
554
727
  ...rawLimits.maxInputTokens === void 0 ? {} : { maxInputTokens: rawLimits.maxInputTokens },
555
- ...rawLimits.maxOutputTokens === void 0 ? {} : { maxOutputTokens: rawLimits.maxOutputTokens }
728
+ ...rawLimits.maxOutputTokens === void 0 ? {} : { maxOutputTokens: rawLimits.maxOutputTokens },
729
+ ...rawLimits.maxToolResultBytes === void 0 ? {} : { maxToolResultBytes: rawLimits.maxToolResultBytes }
556
730
  } },
557
731
  run: definition.run
558
732
  });
@@ -591,6 +765,7 @@ function createComposition(config) {
591
765
  assertLimit(rawLimits?.maxToolCalls, "maxToolCalls", 0, name, false, "Team", "INVALID_TEAM");
592
766
  assertLimit(rawLimits?.maxInputTokens, "maxInputTokens", 1, name, true, "Team", "INVALID_TEAM");
593
767
  assertLimit(rawLimits?.maxOutputTokens, "maxOutputTokens", 1, name, true, "Team", "INVALID_TEAM");
768
+ assertLimit(rawLimits?.maxToolResultBytes, "maxToolResultBytes", 1, name, false, "Team", "INVALID_TEAM");
594
769
  assertConfiguration(!teams.has(name), "DUPLICATE_TEAM", `Team "${name}" is duplicated`);
595
770
  assertConfiguration(!workflows.has(name), "INVALID_TEAM", `Team "${name}" conflicts with a workflow of the same name`);
596
771
  const team = {
@@ -606,7 +781,8 @@ function createComposition(config) {
606
781
  ...rawLimits.maxSteps === void 0 ? {} : { maxSteps: rawLimits.maxSteps },
607
782
  ...rawLimits.maxToolCalls === void 0 ? {} : { maxToolCalls: rawLimits.maxToolCalls },
608
783
  ...rawLimits.maxInputTokens === void 0 ? {} : { maxInputTokens: rawLimits.maxInputTokens },
609
- ...rawLimits.maxOutputTokens === void 0 ? {} : { maxOutputTokens: rawLimits.maxOutputTokens }
784
+ ...rawLimits.maxOutputTokens === void 0 ? {} : { maxOutputTokens: rawLimits.maxOutputTokens },
785
+ ...rawLimits.maxToolResultBytes === void 0 ? {} : { maxToolResultBytes: rawLimits.maxToolResultBytes }
610
786
  } },
611
787
  run: definition.run
612
788
  };
@@ -621,6 +797,17 @@ function createComposition(config) {
621
797
  teams,
622
798
  tools,
623
799
  contextProviders,
800
+ contextBudget: config.contextBudget === void 0 ? void 0 : { ...config.contextBudget },
801
+ toolSearch: config.toolSearch === void 0 ? void 0 : {
802
+ ...config.toolSearch,
803
+ ...config.toolSearch.pinned === void 0 ? {} : { pinned: [...config.toolSearch.pinned] }
804
+ },
805
+ toolSearchCache: defaultToolSearchCache,
806
+ compaction: {
807
+ compactor: config.compaction?.compactor ?? createDigestContextCompactor(),
808
+ estimateTokens: config.compaction?.estimateTokens ?? estimateContextTokens,
809
+ ...config.compaction?.redactSummary === void 0 ? {} : { redactSummary: config.compaction.redactSummary }
810
+ },
624
811
  memoryStore: config.memoryStore ?? (usesMemory ? new InMemoryMemoryStore() : void 0),
625
812
  runStore: config.runStore ?? new InMemoryRunStore(),
626
813
  credentialStore: config.credentialStore,
@@ -651,6 +838,10 @@ const LEASE_RENEW_MS = 1e4;
651
838
  const ELICIT_TOOL_NAME = "fevex__elicit";
652
839
  /** Reserved name of the internal progressive skill loader exposed to the model. */
653
840
  const LOAD_SKILL_TOOL_NAME = "fevex__load_skill";
841
+ /** Reserved name of the internal on-demand tool search exposed to the model. */
842
+ const SEARCH_TOOLS_TOOL_NAME = "fevex__search_tools";
843
+ /** Default safety cap for one serialized tool result. */
844
+ const DEFAULT_MAX_TOOL_RESULT_BYTES = 1048576;
654
845
  //#endregion
655
846
  //#region src/internal/run-helpers.ts
656
847
  function redact(message, secrets) {
@@ -778,6 +969,208 @@ const runErrorPayload = (error) => {
778
969
  };
779
970
  };
780
971
  //#endregion
972
+ //#region src/internal/agent-internal-tools.ts
973
+ async function executeInternalTool(input) {
974
+ if (input.call.name === "fevex__search_tools") {
975
+ if (!input.searchEnabled) return void 0;
976
+ const search = readToolSearchInput(input.input);
977
+ const limit = Math.min(search.limit ?? input.toolSearch.limit, input.toolSearch.limit);
978
+ const semantic = input.toolSearch.semantic;
979
+ const candidateLimit = Math.max(limit, semantic?.topK ?? limit);
980
+ const lexical = searchToolSpecs(input.tools, search.query, candidateLimit);
981
+ let matches = lexical;
982
+ if (semantic !== void 0) try {
983
+ if (input.semanticState.index === void 0 && !input.semanticState.attempted) {
984
+ input.semanticState.attempted = true;
985
+ input.semanticState.index = await buildToolEmbeddingIndex({
986
+ tools: input.tools,
987
+ embeddings: semantic.embeddings,
988
+ cache: semantic.cache,
989
+ ...input.signal === void 0 ? {} : { signal: input.signal }
990
+ });
991
+ }
992
+ if (input.semanticState.index !== void 0) {
993
+ const queryVector = (await semantic.embeddings.embed({
994
+ texts: [search.query],
995
+ ...input.signal === void 0 ? {} : { signal: input.signal }
996
+ }))[0];
997
+ if (!queryVector) throw new Error("Embedding model returned no query vector");
998
+ const semanticMatches = searchToolEmbeddingIndex(input.semanticState.index, queryVector, semantic.topK, semantic.minScore);
999
+ if (semanticMatches.length > 0) {
1000
+ const fusionLimit = semantic.reranker === void 0 ? limit : Math.max(limit, semantic.topK);
1001
+ const fused = reciprocalRankFusionCandidates(lexical, semanticMatches.map((match) => match.tool), fusionLimit);
1002
+ if (semantic.reranker !== void 0 && fused.length > 1) matches = (await rerankToolCandidates(semantic.reranker, search.query, fused, limit, input.signal) ?? fused).map((candidate) => candidate.tool);
1003
+ else matches = fused.map((candidate) => candidate.tool);
1004
+ }
1005
+ }
1006
+ } catch (error) {
1007
+ if (input.signal?.aborted) throw error;
1008
+ matches = lexical;
1009
+ }
1010
+ const selected = matches.slice(0, limit);
1011
+ for (const match of selected) input.loadedTools.add(match.name);
1012
+ return { content: serializeValue({
1013
+ tools: selected.map((tool) => ({
1014
+ name: tool.name,
1015
+ ...tool.description === void 0 ? {} : { description: tool.description }
1016
+ })),
1017
+ ...selected.length === 0 ? { message: "No tools matched; try a different query." } : {}
1018
+ }, "Tool search result must be serializable") };
1019
+ }
1020
+ if (input.call.name === "fevex__load_skill") {
1021
+ if (input.progressiveSkills.length === 0) return void 0;
1022
+ const skillInput = readSkillInput(input.input);
1023
+ const skill = input.progressiveSkills.find((item) => item.name === skillInput.name);
1024
+ if (!skill) throw new Error(`Skill "${skillInput.name}" is not available`);
1025
+ try {
1026
+ return { content: (await abortable(() => skill.load(input.knowledgeContext, skillInput.resource), input.signal)).filter((block) => block.content.trim()).map((block) => `[Skill: ${skill.name}/${block.id}]\n${block.content}`).join("\n\n") };
1027
+ } catch (error) {
1028
+ throw new Error(`Skill provider "${skill.name}" failed: ${toErrorMessage(error)}`, { cause: error });
1029
+ }
1030
+ }
1031
+ }
1032
+ async function rerankToolCandidates(reranker, query, candidates, limit, signal) {
1033
+ try {
1034
+ const result = await reranker.rerank({
1035
+ query,
1036
+ tools: candidates,
1037
+ limit,
1038
+ ...signal === void 0 ? {} : { signal }
1039
+ });
1040
+ if (!Array.isArray(result)) return void 0;
1041
+ const byName = new Map(candidates.map((candidate) => [candidate.tool.name, candidate]));
1042
+ const seen = /* @__PURE__ */ new Set();
1043
+ const ordered = [];
1044
+ for (const candidate of result) {
1045
+ const name = candidate?.tool?.name;
1046
+ const original = typeof name === "string" ? byName.get(name) : void 0;
1047
+ if (!original || seen.has(name)) return void 0;
1048
+ seen.add(name);
1049
+ ordered.push(original);
1050
+ }
1051
+ return ordered.slice(0, limit);
1052
+ } catch (error) {
1053
+ if (signal?.aborted) throw error;
1054
+ return;
1055
+ }
1056
+ }
1057
+ function readSkillInput(input) {
1058
+ if (typeof input !== "object" || input === null || Array.isArray(input)) throw new TypeError(`${LOAD_SKILL_TOOL_NAME} input must be an object`);
1059
+ const record = input;
1060
+ const name = record.name;
1061
+ const resource = record.resource;
1062
+ if (typeof name !== "string" || !name.trim()) throw new TypeError(`${LOAD_SKILL_TOOL_NAME} name must be a non-empty string`);
1063
+ if (resource !== void 0 && (typeof resource !== "string" || !resource.trim())) throw new TypeError(`${LOAD_SKILL_TOOL_NAME} resource must be a non-empty string`);
1064
+ return {
1065
+ name: name.trim(),
1066
+ ...resource === void 0 ? {} : { resource: resource.trim() }
1067
+ };
1068
+ }
1069
+ //#endregion
1070
+ //#region src/internal/context-compaction.ts
1071
+ /** Maximum summary characters copied into the context.compacted event. */
1072
+ const MAX_COMPACTION_EVENT_SUMMARY_CHARS = 4e3;
1073
+ /** Keeps the newest summary segments instead of cutting mid-sentence. */
1074
+ function capSummary(summary, maxChars) {
1075
+ if (summary.length <= maxChars) return summary;
1076
+ const tail = summary.slice(summary.length - maxChars + 1);
1077
+ const boundary = tail.indexOf("\n\n");
1078
+ return `…${boundary === -1 ? tail : tail.slice(boundary + 2)}`;
1079
+ }
1080
+ async function compactContext(input) {
1081
+ const estimateWith = (messages, includeTools) => {
1082
+ const value = input.estimateTokens({
1083
+ messages,
1084
+ ...includeTools ? { tools: input.tools } : {}
1085
+ });
1086
+ if (!Number.isFinite(value) || value < 0) throw new TypeError("Context token estimator must return a non-negative finite number");
1087
+ return Math.ceil(value * input.contextBudget.safetyFactor);
1088
+ };
1089
+ const estimate = (messages) => estimateWith(messages, true);
1090
+ const estimated = estimate(input.messages);
1091
+ const usable = input.contextBudget.contextWindow - input.contextBudget.reserveOutputTokens;
1092
+ const threshold = Math.floor(usable * input.contextBudget.compactAt);
1093
+ if (input.allowCompaction === false || estimated <= threshold) {
1094
+ if (estimated > usable) throw new FevexRunError("CONTEXT_BUDGET_EXCEEDED", `Agent "${input.agentName}" context budget exceeded: ${estimated} tokens > ${usable} usable${input.allowCompaction === false ? " after compaction" : ""}.`, input.runId);
1095
+ return;
1096
+ }
1097
+ const split = splitContextForCompaction(input.messages);
1098
+ if (split === void 0 || split.compactable.length === 0) {
1099
+ if (estimated > usable) throw new FevexRunError("CONTEXT_BUDGET_EXCEEDED", `Agent "${input.agentName}" context budget exceeded: ${estimated} tokens > ${usable} usable. The preserved system prefix and current turn cannot be compacted further.`, input.runId);
1100
+ return;
1101
+ }
1102
+ const preservedTokens = estimate([...split.prefix, ...split.currentTurn]);
1103
+ const available = Math.max(1, Math.min(input.contextBudget.targetTokens, usable - 1) - preservedTokens);
1104
+ const compactedMessages = validateCompactedMessages(await abortable(() => input.compactor.compact({
1105
+ agentName: input.agentName,
1106
+ sessionId: input.sessionId,
1107
+ messages: split.compactable,
1108
+ tools: input.tools,
1109
+ estimatedTokens: estimated,
1110
+ targetTokens: available,
1111
+ contextWindow: input.contextBudget.contextWindow,
1112
+ step: input.step,
1113
+ ...input.context === void 0 ? {} : { context: input.context },
1114
+ signal: input.signal,
1115
+ estimateTokens: (messages) => estimateWith(messages, false)
1116
+ }), input.signal));
1117
+ const messages = [
1118
+ ...split.prefix,
1119
+ ...compactedMessages,
1120
+ ...split.currentTurn
1121
+ ];
1122
+ const compactedTokens = estimate(messages);
1123
+ if (compactedTokens > usable) throw new FevexRunError("CONTEXT_BUDGET_EXCEEDED", `Agent "${input.agentName}" context budget exceeded after compaction: ${compactedTokens} tokens > ${usable} usable. The preserved system prefix and current turn cannot be compacted further.`, input.runId);
1124
+ return {
1125
+ messages,
1126
+ removedMessages: split.compactable.length,
1127
+ estimatedTokens: estimated,
1128
+ compactedTokens,
1129
+ summary: compactedMessages.map((message) => message.content).join("\n\n")
1130
+ };
1131
+ }
1132
+ function splitContextForCompaction(messages) {
1133
+ let lastUser = -1;
1134
+ for (let index = messages.length - 1; index >= 0; index -= 1) if (messages[index].role === "user") {
1135
+ lastUser = index;
1136
+ break;
1137
+ }
1138
+ if (lastUser < 0) return void 0;
1139
+ let lastToolBatch = -1;
1140
+ for (let index = messages.length - 1; index > lastUser; index -= 1) {
1141
+ const message = messages[index];
1142
+ if (message.role === "assistant" && (message.toolCalls?.length ?? 0) > 0) {
1143
+ lastToolBatch = index;
1144
+ break;
1145
+ }
1146
+ }
1147
+ const prefix = messages.filter((message) => message.role === "system");
1148
+ const currentTurn = (lastToolBatch === -1 ? messages.slice(lastUser) : [messages[lastUser], ...messages.slice(lastToolBatch)]).filter((message) => message.role !== "system");
1149
+ const preserved = /* @__PURE__ */ new Set([...prefix, ...currentTurn]);
1150
+ return {
1151
+ prefix,
1152
+ currentTurn,
1153
+ compactable: messages.filter((message) => message.role !== "system" && !preserved.has(message))
1154
+ };
1155
+ }
1156
+ function validateCompactedMessages(value) {
1157
+ if (!Array.isArray(value) || value.length === 0) throw new TypeError("Context compactor must return at least one message");
1158
+ const roles = /* @__PURE__ */ new Set([
1159
+ "system",
1160
+ "user",
1161
+ "assistant",
1162
+ "tool"
1163
+ ]);
1164
+ for (const [index, message] of value.entries()) {
1165
+ if (typeof message !== "object" || message === null || Array.isArray(message)) throw new TypeError(`Context compactor message at index ${index} must be an object`);
1166
+ const record = message;
1167
+ if (typeof record.role !== "string" || !roles.has(record.role)) throw new TypeError(`Context compactor message at index ${index} has an invalid role`);
1168
+ if (typeof record.content !== "string") throw new TypeError(`Context compactor message at index ${index} must have string content`);
1169
+ if (record.toolCalls !== void 0 && !Array.isArray(record.toolCalls)) throw new TypeError(`Context compactor message at index ${index} has invalid toolCalls`);
1170
+ }
1171
+ return value;
1172
+ }
1173
+ //#endregion
781
1174
  //#region src/internal/definition-hash.ts
782
1175
  function canonicalize(value) {
783
1176
  if (Array.isArray(value)) return value.map(canonicalize);
@@ -796,6 +1189,7 @@ async function transportableTool(tool) {
796
1189
  retry: tool.retry,
797
1190
  credentials: tool.credentials ?? [],
798
1191
  sandbox: tool.sandbox,
1192
+ maxResultBytes: tool.maxResultBytes,
799
1193
  source: tool.source
800
1194
  };
801
1195
  }
@@ -804,7 +1198,7 @@ async function sha256(value) {
804
1198
  const digest = await crypto.subtle.digest("SHA-256", encoded);
805
1199
  return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
806
1200
  }
807
- async function definitionHash(name, modelName, agent, tools) {
1201
+ async function definitionHash(name, modelName, agent, tools, contextBudget) {
808
1202
  const toolDefinitions = await Promise.all((agent.tools ?? []).map((toolName) => transportableTool(tools.get(toolName))));
809
1203
  return sha256({
810
1204
  name,
@@ -820,6 +1214,7 @@ async function definitionHash(name, modelName, agent, tools) {
820
1214
  elicitation: agent.elicitation,
821
1215
  approvalMode: agent.approvalMode,
822
1216
  limits: agent.limits,
1217
+ contextBudget,
823
1218
  inputSchema: agent.inputSchema === void 0 ? void 0 : toTransportableSchema(agent.inputSchema, "input", `Input schema for agent "${name}" is not transportable`),
824
1219
  outputSchema: agent.outputSchema === void 0 ? void 0 : toTransportableSchema(agent.outputSchema, "output", `Output schema for agent "${name}" is not transportable`)
825
1220
  });
@@ -864,7 +1259,8 @@ function combineLimits(first, second) {
864
1259
  ...minLimit(first.maxSteps, second.maxSteps) === void 0 ? {} : { maxSteps: minLimit(first.maxSteps, second.maxSteps) },
865
1260
  ...minLimit(first.maxToolCalls, second.maxToolCalls) === void 0 ? {} : { maxToolCalls: minLimit(first.maxToolCalls, second.maxToolCalls) },
866
1261
  ...minLimit(first.maxInputTokens, second.maxInputTokens) === void 0 ? {} : { maxInputTokens: minLimit(first.maxInputTokens, second.maxInputTokens) },
867
- ...minLimit(first.maxOutputTokens, second.maxOutputTokens) === void 0 ? {} : { maxOutputTokens: minLimit(first.maxOutputTokens, second.maxOutputTokens) }
1262
+ ...minLimit(first.maxOutputTokens, second.maxOutputTokens) === void 0 ? {} : { maxOutputTokens: minLimit(first.maxOutputTokens, second.maxOutputTokens) },
1263
+ ...minLimit(first.maxToolResultBytes, second.maxToolResultBytes) === void 0 ? {} : { maxToolResultBytes: minLimit(first.maxToolResultBytes, second.maxToolResultBytes) }
868
1264
  };
869
1265
  }
870
1266
  function subtractLimit(limit, used) {
@@ -877,7 +1273,8 @@ function remainingWorkflowLimits(limits, budget) {
877
1273
  ...limits.maxSteps === void 0 ? {} : { maxSteps: subtractLimit(limits.maxSteps, budget?.steps) },
878
1274
  ...limits.maxToolCalls === void 0 ? {} : { maxToolCalls: subtractLimit(limits.maxToolCalls, budget?.toolCalls) },
879
1275
  ...limits.maxInputTokens === void 0 ? {} : { maxInputTokens: subtractLimit(limits.maxInputTokens, budget?.usage?.inputTokens) },
880
- ...limits.maxOutputTokens === void 0 ? {} : { maxOutputTokens: subtractLimit(limits.maxOutputTokens, budget?.usage?.outputTokens) }
1276
+ ...limits.maxOutputTokens === void 0 ? {} : { maxOutputTokens: subtractLimit(limits.maxOutputTokens, budget?.usage?.outputTokens) },
1277
+ ...limits.maxToolResultBytes === void 0 ? {} : { maxToolResultBytes: limits.maxToolResultBytes }
881
1278
  };
882
1279
  }
883
1280
  function assertWorkflowBudget(workflowName, limits, budget) {
@@ -904,12 +1301,24 @@ function addWorkflowBudget(first, second) {
904
1301
  * engine — that direction is what keeps the two separable.
905
1302
  */
906
1303
  function createAgentEngine(composition, core) {
907
- const { models, agents, tools, contextProviders, memoryStore, runStore, credentialStore, sandbox, artifacts, policies } = composition;
1304
+ const { models, agents, tools, contextProviders, contextBudget: defaultContextBudget, compaction, toolSearch: defaultToolSearch, toolSearchCache: defaultToolSearchCache, memoryStore, runStore, credentialStore, sandbox, artifacts, policies } = composition;
1305
+ const compactionStats = {
1306
+ compactions: 0,
1307
+ removedMessages: 0,
1308
+ estimatedTokens: 0,
1309
+ compactedTokens: 0,
1310
+ savedTokens: 0
1311
+ };
908
1312
  const { activeRuns, activeSessions, runtimeOwner, isClosed, admitStart, markInternalRun, resolveModel, resolveCheckpointModel, notifyObserver, createEvent, emitEvent, releaseExecution, commit, startLease, cancelExecution, authorize } = core;
909
1313
  const prepareExecutionBody = async (name, request, internal = false, correlation = {}) => {
910
1314
  if (!internal && isClosed()) throw new FevexRunError("RUNTIME_CLOSED", "Runtime is not accepting new runs");
911
1315
  const agent = agents.get(name);
912
1316
  if (!agent) throw new FevexRunError("AGENT_NOT_FOUND", `Agent "${name}" is not registered`);
1317
+ const contextBudget = resolveContextBudget(request.contextBudget ?? agent.contextBudget ?? defaultContextBudget, `Agent "${name}" contextBudget`);
1318
+ const toolSearch = resolveToolSearchOptions(request.toolSearch ?? agent.toolSearch ?? defaultToolSearch, `Agent "${name}" toolSearch`, defaultToolSearchCache);
1319
+ const toolChoice = effectiveToolChoice(agent, request);
1320
+ const initialLoadedTools = new Set(toolSearch.pinned.filter((toolName) => agent.tools?.includes(toolName)));
1321
+ if (typeof toolChoice === "object" && agent.tools?.includes(toolChoice.name)) initialLoadedTools.add(toolChoice.name);
913
1322
  const { modelName, model } = resolveModel(agent, request.model);
914
1323
  const now = (/* @__PURE__ */ new Date()).toISOString();
915
1324
  let session;
@@ -967,7 +1376,7 @@ function createAgentEngine(composition, core) {
967
1376
  content: agent.instructions
968
1377
  },
969
1378
  ...knowledge.skills,
970
- ...session.history,
1379
+ ...sessionHistoryMessages(session),
971
1380
  ...knowledge.context,
972
1381
  ...knowledge.memory,
973
1382
  {
@@ -980,8 +1389,11 @@ function createAgentEngine(composition, core) {
980
1389
  runId: run.id,
981
1390
  ...request.model !== void 0 || typeof agent.model === "string" ? { modelName } : {},
982
1391
  ...request.reasoning === void 0 ? {} : { reasoning: request.reasoning },
983
- definitionHash: await definitionHash(name, modelName, agent, tools),
1392
+ definitionHash: await definitionHash(name, modelName, agent, tools, contextBudget),
984
1393
  limits: combineLimits(agent.limits, request.limits),
1394
+ ...contextBudget === void 0 ? {} : { contextBudget },
1395
+ toolSearch: serializeToolSearchOptions(toolSearch),
1396
+ loadedTools: [...initialLoadedTools],
985
1397
  messages,
986
1398
  inputContent,
987
1399
  context: request.context,
@@ -992,7 +1404,7 @@ function createAgentEngine(composition, core) {
992
1404
  pendingIndex: 0,
993
1405
  effectiveElicitationMode: effectiveElicitationMode(agent, request),
994
1406
  effectiveApprovalMode: effectiveApprovalMode(agent, request),
995
- effectiveToolChoice: effectiveToolChoice(agent, request)
1407
+ effectiveToolChoice: toolChoice
996
1408
  };
997
1409
  const ownerId = `${runtimeOwner}:${crypto.randomUUID()}`;
998
1410
  const lease = {
@@ -1039,6 +1451,8 @@ function createAgentEngine(composition, core) {
1039
1451
  const durableCheckpoint = async (state, model, modelName, messages, inputContent, usage, providerState, step, toolCallCount, seenToolCallIds, pendingTools, pendingIndex) => {
1040
1452
  const agent = agents.get(state.run.agentName);
1041
1453
  if (agent.model !== void 0 && typeof agent.model !== "string") throw new FevexRunError("RUN_NOT_RESUMABLE", "Durable continuation requires a named model", state.run.id);
1454
+ const contextBudget = resolveContextBudget(state.checkpoint?.contextBudget ?? state.request.contextBudget ?? agent.contextBudget ?? defaultContextBudget, `Agent "${state.run.agentName}" contextBudget`);
1455
+ const toolSearch = state.toolSearch ?? resolveToolSearchOptions(state.checkpoint?.toolSearch ?? state.request.toolSearch ?? agent.toolSearch ?? defaultToolSearch, `Agent "${state.run.agentName}" toolSearch`, defaultToolSearchCache);
1042
1456
  let serializedState;
1043
1457
  if (providerState !== void 0) {
1044
1458
  if (!model.stateCodec) throw new FevexRunError("RUN_NOT_RESUMABLE", "ModelGateway must provide stateCodec for durable continuation", state.run.id);
@@ -1049,8 +1463,11 @@ function createAgentEngine(composition, core) {
1049
1463
  runId: state.run.id,
1050
1464
  modelName,
1051
1465
  ...state.request.reasoning === void 0 ? {} : { reasoning: state.request.reasoning },
1052
- definitionHash: await definitionHash(state.run.agentName, modelName, agent, tools),
1466
+ definitionHash: await definitionHash(state.run.agentName, modelName, agent, tools, contextBudget),
1053
1467
  limits: state.checkpoint?.limits ?? combineLimits(agent.limits, state.request.limits),
1468
+ ...contextBudget === void 0 ? {} : { contextBudget },
1469
+ toolSearch: serializeToolSearchOptions(toolSearch),
1470
+ loadedTools: [...state.loadedTools ?? []],
1054
1471
  messages: structuredClone(messages),
1055
1472
  inputContent,
1056
1473
  context: state.request.context,
@@ -1109,6 +1526,15 @@ function createAgentEngine(composition, core) {
1109
1526
  const { request } = state;
1110
1527
  const agent = agents.get(name);
1111
1528
  const { modelName, model } = resolveModel(agent, state.checkpoint?.modelName ?? request.model);
1529
+ const contextBudget = resolveContextBudget(state.checkpoint?.contextBudget ?? request.contextBudget ?? agent.contextBudget ?? defaultContextBudget, `Agent "${name}" contextBudget`);
1530
+ const semantic = request.toolSearch?.semantic ?? agent.toolSearch?.semantic ?? defaultToolSearch?.semantic;
1531
+ const toolSearch = resolveToolSearchOptions(state.checkpoint === void 0 ? request.toolSearch ?? agent.toolSearch ?? defaultToolSearch : state.checkpoint.toolSearch === void 0 ? {
1532
+ mode: "off",
1533
+ ...semantic === void 0 ? {} : { semantic }
1534
+ } : {
1535
+ ...state.checkpoint.toolSearch,
1536
+ ...semantic === void 0 ? {} : { semantic }
1537
+ }, `Agent "${name}" toolSearch`, defaultToolSearchCache);
1112
1538
  const events = [...state.initialEvents ?? []];
1113
1539
  const firstEvents = state.initialEvents ?? [];
1114
1540
  state.initialEvents = void 0;
@@ -1126,12 +1552,14 @@ function createAgentEngine(composition, core) {
1126
1552
  let seenToolCallIds;
1127
1553
  let pendingTools;
1128
1554
  let pendingIndex;
1555
+ let compactionApplied = false;
1556
+ const semanticState = { attempted: false };
1129
1557
  try {
1130
1558
  for (const event of firstEvents) yield event;
1131
1559
  if (state.checkpoint) {
1132
1560
  const checkpoint = state.checkpoint;
1133
1561
  if (checkpoint.version !== 2) throw new FevexRunError("CHECKPOINT_UNSUPPORTED", `Checkpoint for run "${state.run.id}" is unsupported`, state.run.id);
1134
- const hash = await definitionHash(name, modelName, agent, tools);
1562
+ const hash = await definitionHash(name, modelName, agent, tools, contextBudget);
1135
1563
  if (checkpoint.definitionHash !== hash) throw new FevexRunError("RUN_DEFINITION_CHANGED", `Definition for agent "${name}" changed`, state.run.id);
1136
1564
  inputContent = checkpoint.inputContent;
1137
1565
  messages = checkpoint.messages;
@@ -1153,7 +1581,7 @@ function createAgentEngine(composition, core) {
1153
1581
  content: agent.instructions
1154
1582
  },
1155
1583
  ...knowledge.skills,
1156
- ...state.session.history,
1584
+ ...sessionHistoryMessages(state.session),
1157
1585
  ...knowledge.context,
1158
1586
  ...knowledge.memory,
1159
1587
  {
@@ -1201,30 +1629,86 @@ function createAgentEngine(composition, core) {
1201
1629
  additionalProperties: false
1202
1630
  }
1203
1631
  };
1632
+ const searchEnabled = shouldUseToolSearch(toolSearch, agentTools);
1633
+ const searchTool = searchEnabled ? {
1634
+ name: SEARCH_TOOLS_TOOL_NAME,
1635
+ description: `Search ${agentTools.length} tools available to this agent by capability. Use it when you need a tool that is not currently exposed. Returns matching tools and makes their schemas available on the next turn.`,
1636
+ inputSchema: {
1637
+ type: "object",
1638
+ properties: {
1639
+ query: {
1640
+ type: "string",
1641
+ description: "Capability to search for, such as \"send email\" or \"query database\"."
1642
+ },
1643
+ limit: {
1644
+ type: "integer",
1645
+ minimum: 1,
1646
+ maximum: toolSearch.limit
1647
+ }
1648
+ },
1649
+ required: ["query"],
1650
+ additionalProperties: false
1651
+ }
1652
+ } : void 0;
1653
+ const elicitationTool = currentElicitationMode === "pause" ? {
1654
+ name: ELICIT_TOOL_NAME,
1655
+ description: "Request external information required to continue this run. Use prompt for explanatory text shown outside the form. Put short field labels in responseSchema property titles and optional field help in property descriptions.",
1656
+ inputSchema: {
1657
+ type: "object",
1658
+ properties: {
1659
+ prompt: {
1660
+ type: "string",
1661
+ description: "Explanatory text for the user; do not duplicate field labels here."
1662
+ },
1663
+ responseSchema: {
1664
+ type: "object",
1665
+ description: "JSON Schema for the expected response. Each property should include a short title for the form label and may include a description for field help."
1666
+ },
1667
+ expiresAt: { type: "string" }
1668
+ },
1669
+ required: ["prompt", "responseSchema"],
1670
+ additionalProperties: false
1671
+ }
1672
+ } : void 0;
1204
1673
  const allTools = [
1205
1674
  ...agentTools,
1206
1675
  ...skillTool === void 0 ? [] : [skillTool],
1207
- ...currentElicitationMode === "pause" ? [{
1208
- name: ELICIT_TOOL_NAME,
1209
- description: "Request external information required to continue this run. Use prompt for explanatory text shown outside the form. Put short field labels in responseSchema property titles and optional field help in property descriptions.",
1210
- inputSchema: {
1211
- type: "object",
1212
- properties: {
1213
- prompt: {
1214
- type: "string",
1215
- description: "Explanatory text for the user; do not duplicate field labels here."
1216
- },
1217
- responseSchema: {
1218
- type: "object",
1219
- description: "JSON Schema for the expected response. Each property should include a short title for the form label and may include a description for field help."
1220
- },
1221
- expiresAt: { type: "string" }
1222
- },
1223
- required: ["prompt", "responseSchema"],
1224
- additionalProperties: false
1225
- }
1226
- }] : []
1676
+ ...searchTool === void 0 ? [] : [searchTool],
1677
+ ...elicitationTool === void 0 ? [] : [elicitationTool]
1227
1678
  ];
1679
+ const loadedTools = new Set(state.loadedTools ?? state.checkpoint?.loadedTools ?? []);
1680
+ for (const toolName of toolSearch.pinned) if (agent.tools?.includes(toolName)) loadedTools.add(toolName);
1681
+ if (typeof currentToolChoice === "object" && agent.tools?.includes(currentToolChoice.name)) loadedTools.add(currentToolChoice.name);
1682
+ state.loadedTools = loadedTools;
1683
+ state.toolSearch = toolSearch;
1684
+ let loadedToolsVersion = 0;
1685
+ let cachedModelTools;
1686
+ let cachedModelToolsVersion = -1;
1687
+ const modelTools = () => {
1688
+ if (!searchEnabled) return allTools;
1689
+ if (cachedModelTools !== void 0 && cachedModelToolsVersion === loadedToolsVersion) return cachedModelTools;
1690
+ cachedModelTools = [
1691
+ ...agentTools.filter((tool) => loadedTools.has(tool.name)),
1692
+ ...skillTool === void 0 ? [] : [skillTool],
1693
+ ...searchTool === void 0 ? [] : [searchTool],
1694
+ ...elicitationTool === void 0 ? [] : [elicitationTool]
1695
+ ];
1696
+ cachedModelToolsVersion = loadedToolsVersion;
1697
+ return cachedModelTools;
1698
+ };
1699
+ const toolsTokenCache = /* @__PURE__ */ new WeakMap();
1700
+ const estimateContext = (input) => {
1701
+ if (!input.tools?.length) return compaction.estimateTokens(input);
1702
+ let toolTokens = toolsTokenCache.get(input.tools);
1703
+ if (toolTokens === void 0) {
1704
+ toolTokens = compaction.estimateTokens({
1705
+ messages: [],
1706
+ tools: input.tools
1707
+ });
1708
+ toolsTokenCache.set(input.tools, toolTokens);
1709
+ }
1710
+ return compaction.estimateTokens({ messages: input.messages }) + toolTokens;
1711
+ };
1228
1712
  const effectiveLimits = state.checkpoint?.limits ?? combineLimits(agent.limits, request.limits);
1229
1713
  const maxSteps = effectiveLimits?.maxSteps ?? 8;
1230
1714
  const maxToolCalls = effectiveLimits?.maxToolCalls ?? 16;
@@ -1232,12 +1716,73 @@ function createAgentEngine(composition, core) {
1232
1716
  while (step <= maxSteps) {
1233
1717
  request.signal.throwIfAborted();
1234
1718
  if (pendingTools.length === 0) {
1719
+ const currentTools = modelTools();
1720
+ if (contextBudget !== void 0) {
1721
+ const compacted = await compactContext({
1722
+ runId: state.run.id,
1723
+ agentName: name,
1724
+ sessionId: state.session.id,
1725
+ messages,
1726
+ tools: currentTools,
1727
+ contextBudget,
1728
+ compactor: compaction.compactor,
1729
+ estimateTokens: estimateContext,
1730
+ step,
1731
+ ...request.context === void 0 ? {} : { context: request.context },
1732
+ signal: request.signal,
1733
+ allowCompaction: !compactionApplied
1734
+ });
1735
+ if (compacted !== void 0) {
1736
+ messages = compacted.messages;
1737
+ compactionApplied = true;
1738
+ compactionStats.compactions += 1;
1739
+ compactionStats.removedMessages += compacted.removedMessages;
1740
+ compactionStats.estimatedTokens += compacted.estimatedTokens;
1741
+ compactionStats.compactedTokens += compacted.compactedTokens;
1742
+ compactionStats.savedTokens += Math.max(0, compacted.estimatedTokens - compacted.compactedTokens);
1743
+ const previous = state.session.compaction;
1744
+ const combined = previous === void 0 ? compacted.summary : `${previous.summary}\n\n${compacted.summary}`;
1745
+ const maxSummaryChars = Math.max(200, contextBudget.targetTokens * 4);
1746
+ state.session.compaction = {
1747
+ summary: capSummary(combined, maxSummaryChars),
1748
+ coveredMessages: state.session.history.length,
1749
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1750
+ };
1751
+ const redactedSummary = compaction.redactSummary?.(compacted.summary) ?? compacted.summary;
1752
+ if (typeof redactedSummary !== "string") throw new TypeError("compaction.redactSummary must return a string");
1753
+ const summaryTruncated = redactedSummary.length > MAX_COMPACTION_EVENT_SUMMARY_CHARS;
1754
+ const event = createEvent(state, "context.compacted", {
1755
+ step,
1756
+ removedMessages: compacted.removedMessages,
1757
+ estimatedTokens: compacted.estimatedTokens,
1758
+ compactedTokens: compacted.compactedTokens,
1759
+ targetTokens: contextBudget.targetTokens,
1760
+ contextWindow: contextBudget.contextWindow,
1761
+ summary: summaryTruncated ? capSummary(redactedSummary, MAX_COMPACTION_EVENT_SUMMARY_CHARS) : redactedSummary,
1762
+ ...summaryTruncated ? { summaryTruncated: true } : {}
1763
+ });
1764
+ if (isDurableRunStore(runStore)) {
1765
+ const checkpoint = await durableCheckpoint(state, model, modelName, messages, inputContent, usage, providerState, step, toolCallCount, seenToolCallIds, pendingTools, pendingIndex);
1766
+ await commit(state, {
1767
+ checkpoint,
1768
+ session: state.session,
1769
+ events: [event]
1770
+ });
1771
+ state.checkpoint = checkpoint;
1772
+ } else {
1773
+ await runStore.appendEvent(event);
1774
+ notifyObserver(event);
1775
+ }
1776
+ events.push(event);
1777
+ yield event;
1778
+ }
1779
+ }
1235
1780
  const hasToolBudget = step < maxSteps && toolCallCount < maxToolCalls;
1236
- const modelToolChoice = hasToolBudget && allTools.length ? currentToolChoice === "auto" ? void 0 : currentToolChoice : void 0;
1781
+ const modelToolChoice = hasToolBudget && currentTools.length ? currentToolChoice === "auto" ? void 0 : currentToolChoice : void 0;
1237
1782
  yield await emit("model.started", { step });
1238
1783
  const modelStream = readModelStream(model, {
1239
1784
  messages: [...messages],
1240
- tools: hasToolBudget && allTools.length ? allTools : void 0,
1785
+ tools: hasToolBudget && currentTools.length ? currentTools : void 0,
1241
1786
  ...modelToolChoice === void 0 ? {} : { toolChoice: modelToolChoice },
1242
1787
  reasoning: state.request.reasoning ?? agent.reasoning,
1243
1788
  modelOptions: agent.modelOptions,
@@ -1313,52 +1858,63 @@ function createAgentEngine(composition, core) {
1313
1858
  assertContinuationBudget(name, "maxInputTokens", "inputTokens", effectiveLimits?.maxInputTokens, usage);
1314
1859
  assertContinuationBudget(name, "maxOutputTokens", "outputTokens", effectiveLimits?.maxOutputTokens, usage);
1315
1860
  if (result.toolCalls.length > maxToolCalls - toolCallCount) throw new Error(`Agent "${name}" exceeded maxToolCalls limit of ${maxToolCalls}`);
1316
- const loadSkillCall = progressiveSkills.length === 0 ? void 0 : result.toolCalls.find((call) => call.name === LOAD_SKILL_TOOL_NAME);
1317
- if (loadSkillCall) {
1318
- if (result.toolCalls.length !== 1) throw new Error(`${LOAD_SKILL_TOOL_NAME} must be the only tool call in a model turn`);
1319
- if (!loadSkillCall.id?.trim()) throw new Error("Tool call id cannot be empty");
1320
- if (seenToolCallIds.has(loadSkillCall.id)) throw new Error(`Tool call id "${loadSkillCall.id}" is duplicated in run "${state.run.id}"`);
1861
+ const internalCall = result.toolCalls.find((call) => searchEnabled && call.name === "fevex__search_tools" || progressiveSkills.length > 0 && call.name === "fevex__load_skill");
1862
+ if (internalCall) {
1863
+ if (result.toolCalls.length !== 1) throw new Error(`${internalCall.name} must be the only tool call in a model turn`);
1864
+ if (!internalCall.id?.trim()) throw new Error("Tool call id cannot be empty");
1865
+ if (seenToolCallIds.has(internalCall.id)) throw new Error(`Tool call id "${internalCall.id}" is duplicated in run "${state.run.id}"`);
1321
1866
  if (toolCallCount + 1 > maxToolCalls) throw new Error(`Agent "${name}" exceeded maxToolCalls limit of ${maxToolCalls}`);
1322
- const rawInput = toJsonValue(loadSkillCall.input, `Input for tool "${LOAD_SKILL_TOOL_NAME}" must be JSON-serializable`);
1323
- const skillInput = readSkillInput(rawInput);
1324
- const skill = progressiveSkills.find((item) => item.name === skillInput.name);
1325
- if (!skill) throw new Error(`Skill "${skillInput.name}" is not available to agent "${name}"`);
1326
- seenToolCallIds.add(loadSkillCall.id);
1867
+ const rawInput = toJsonValue(internalCall.input, `Input for tool "${internalCall.name}" must be JSON-serializable`);
1868
+ const loadedBefore = loadedTools.size;
1869
+ seenToolCallIds.add(internalCall.id);
1327
1870
  messages.push({
1328
1871
  role: "assistant",
1329
1872
  content: result.output === void 0 ? "" : serializeValue(result.output, `Model output for agent "${name}" must be serializable`),
1330
1873
  toolCalls: [{
1331
- ...loadSkillCall,
1874
+ ...internalCall,
1332
1875
  input: rawInput
1333
1876
  }]
1334
1877
  });
1335
1878
  yield await emit("tool.started", {
1336
1879
  step,
1337
- toolCallId: loadSkillCall.id,
1338
- toolName: LOAD_SKILL_TOOL_NAME
1880
+ toolCallId: internalCall.id,
1881
+ toolName: internalCall.name
1339
1882
  });
1340
- let blocks;
1883
+ let execution;
1341
1884
  try {
1342
- blocks = await abortable(() => skill.load(knowledgeContext(state, inputContent), skillInput.resource), request.signal);
1885
+ execution = await executeInternalTool({
1886
+ call: internalCall,
1887
+ input: rawInput,
1888
+ tools: agentTools,
1889
+ searchEnabled,
1890
+ toolSearch,
1891
+ loadedTools,
1892
+ progressiveSkills,
1893
+ knowledgeContext: knowledgeContext(state, inputContent),
1894
+ semanticState,
1895
+ signal: request.signal
1896
+ });
1343
1897
  } catch (error) {
1344
1898
  yield await emit("tool.failed", {
1345
1899
  step,
1346
- toolCallId: loadSkillCall.id,
1347
- toolName: LOAD_SKILL_TOOL_NAME,
1900
+ toolCallId: internalCall.id,
1901
+ toolName: internalCall.name,
1348
1902
  error: toErrorMessage(error)
1349
1903
  });
1350
- throw new Error(`Skill provider "${skill.name}" failed: ${toErrorMessage(error)}`, { cause: error });
1904
+ throw error;
1351
1905
  }
1906
+ if (!execution) throw new Error(`Tool "${internalCall.name}" is not available to agent "${name}"`);
1907
+ if (loadedTools.size > loadedBefore) loadedToolsVersion += 1;
1352
1908
  messages.push({
1353
1909
  role: "tool",
1354
- name: LOAD_SKILL_TOOL_NAME,
1355
- toolCallId: loadSkillCall.id,
1356
- content: blocks.filter((block) => block.content.trim()).map((block) => `[Skill: ${skill.name}/${block.id}]\n${block.content}`).join("\n\n")
1910
+ name: internalCall.name,
1911
+ toolCallId: internalCall.id,
1912
+ content: execution.content
1357
1913
  });
1358
1914
  yield await emit("tool.completed", {
1359
1915
  step,
1360
- toolCallId: loadSkillCall.id,
1361
- toolName: LOAD_SKILL_TOOL_NAME
1916
+ toolCallId: internalCall.id,
1917
+ toolName: internalCall.name
1362
1918
  });
1363
1919
  toolCallCount += 1;
1364
1920
  step += 1;
@@ -1420,6 +1976,7 @@ function createAgentEngine(composition, core) {
1420
1976
  batch.add(call.id);
1421
1977
  const tool = agent.tools?.includes(call.name) ? tools.get(call.name) : void 0;
1422
1978
  if (!tool) throw new Error(`Tool "${call.name}" is not available to agent "${name}"`);
1979
+ if (searchEnabled && !loadedTools.has(call.name)) throw new Error(`Tool "${call.name}" is not loaded; use ${SEARCH_TOOLS_TOOL_NAME} first`);
1423
1980
  const rawInput = toJsonValue(call.input, `Input for tool "${call.name}" must be JSON-serializable`);
1424
1981
  let validated;
1425
1982
  try {
@@ -1676,7 +2233,13 @@ function createAgentEngine(composition, core) {
1676
2233
  signal: request.signal
1677
2234
  }), request.signal);
1678
2235
  for (const event of createdArtifacts.splice(0)) yield event;
1679
- output = toJsonValue(tool.outputSchema === void 0 ? raw : await validateSchema(tool.outputSchema, raw, `Output from tool "${tool.name}" does not match outputSchema`), `Output from tool "${tool.name}" must be JSON-serializable`);
2236
+ const validated = tool.outputSchema === void 0 ? raw : await validateSchema(tool.outputSchema, raw, `Output from tool "${tool.name}" does not match outputSchema`);
2237
+ const serialized = serializeValue(validated, `Output from tool "${tool.name}" must be JSON-serializable`);
2238
+ const configuredMaxResultBytes = tool.maxResultBytes !== void 0 ? tool.maxResultBytes : effectiveLimits?.maxToolResultBytes;
2239
+ const maxResultBytes = configuredMaxResultBytes === void 0 ? DEFAULT_MAX_TOOL_RESULT_BYTES : configuredMaxResultBytes;
2240
+ const resultBytes = new TextEncoder().encode(serialized).byteLength;
2241
+ if (maxResultBytes !== false && resultBytes > maxResultBytes) throw new FevexRunError("TOOL_RESULT_TOO_LARGE", `Tool "${tool.name}" returned ${resultBytes} bytes; limit is ${maxResultBytes}. Store large outputs as artifacts and return an artifact descriptor.`, state.run.id);
2242
+ output = toJsonValue(validated, `Output from tool "${tool.name}" must be JSON-serializable`);
1680
2243
  if (containsSecret(output, secrets)) throw new Error(`Output from tool "${tool.name}" contains a credential`);
1681
2244
  record = {
1682
2245
  ...record,
@@ -1713,7 +2276,7 @@ function createAgentEngine(composition, core) {
1713
2276
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1714
2277
  };
1715
2278
  if (durable) await commit(state, { toolExecution: record });
1716
- if (attempt >= maxAttempts || tool.idempotency !== "keyed") {
2279
+ if (attempt >= maxAttempts || tool.idempotency !== "keyed" || error instanceof FevexRunError && error.code === "TOOL_RESULT_TOO_LARGE") {
1717
2280
  yield await emit("tool.failed", {
1718
2281
  step,
1719
2282
  toolCallId: pending.call.id,
@@ -2110,20 +2673,19 @@ function createAgentEngine(composition, core) {
2110
2673
  nextEvent,
2111
2674
  drainExecution,
2112
2675
  resumeAgent,
2113
- cancelStoredAgent
2676
+ cancelStoredAgent,
2677
+ getCompactionStats() {
2678
+ return { ...compactionStats };
2679
+ }
2114
2680
  };
2115
2681
  }
2116
- function readSkillInput(input) {
2117
- if (typeof input !== "object" || input === null || Array.isArray(input)) throw new TypeError(`${LOAD_SKILL_TOOL_NAME} input must be an object`);
2118
- const record = input;
2119
- const name = record.name;
2120
- const resource = record.resource;
2121
- if (typeof name !== "string" || !name.trim()) throw new TypeError(`${LOAD_SKILL_TOOL_NAME} name must be a non-empty string`);
2122
- if (resource !== void 0 && (typeof resource !== "string" || !resource.trim())) throw new TypeError(`${LOAD_SKILL_TOOL_NAME} resource must be a non-empty string`);
2123
- return {
2124
- name: name.trim(),
2125
- ...resource === void 0 ? {} : { resource: resource.trim() }
2126
- };
2682
+ function sessionHistoryMessages(session) {
2683
+ const compaction = session.compaction;
2684
+ if (compaction === void 0 || compaction.coveredMessages <= 0 || compaction.coveredMessages > session.history.length) return session.history;
2685
+ return [{
2686
+ role: "system",
2687
+ content: `[Compacted conversation]\n${compaction.summary}`
2688
+ }, ...session.history.slice(compaction.coveredMessages)];
2127
2689
  }
2128
2690
  function scopedArtifacts(store, options) {
2129
2691
  const scopedContext = (value) => {
@@ -2284,6 +2846,17 @@ function annotation(event) {
2284
2846
  delayMs: event.payload.delayMs
2285
2847
  }
2286
2848
  };
2849
+ case "context.compacted": return {
2850
+ name: event.type,
2851
+ timestamp: event.timestamp,
2852
+ attributes: {
2853
+ removedMessages: event.payload.removedMessages,
2854
+ estimatedTokens: event.payload.estimatedTokens,
2855
+ compactedTokens: event.payload.compactedTokens,
2856
+ targetTokens: event.payload.targetTokens,
2857
+ contextWindow: event.payload.contextWindow
2858
+ }
2859
+ };
2287
2860
  case "tool.execution_unknown": return {
2288
2861
  name: event.type,
2289
2862
  timestamp: event.timestamp,
@@ -2882,12 +3455,12 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
2882
3455
  };
2883
3456
  const resolveCheckpointModel = async (agentName, agent, checkpoint) => {
2884
3457
  const preferred = resolveModel(agent, checkpoint.modelName);
2885
- if (checkpoint.definitionHash === await definitionHash(agentName, preferred.modelName, agent, tools)) return preferred;
3458
+ if (checkpoint.definitionHash === await definitionHash(agentName, preferred.modelName, agent, tools, checkpoint.contextBudget)) return preferred;
2886
3459
  if (checkpoint.modelName !== void 0) return void 0;
2887
3460
  for (const candidateName of models.keys()) {
2888
3461
  if (candidateName === preferred.modelName) continue;
2889
3462
  const candidate = resolveModel(agent, candidateName);
2890
- if (checkpoint.definitionHash === await definitionHash(agentName, candidate.modelName, agent, tools)) return candidate;
3463
+ if (checkpoint.definitionHash === await definitionHash(agentName, candidate.modelName, agent, tools, checkpoint.contextBudget)) return candidate;
2891
3464
  }
2892
3465
  };
2893
3466
  const scheduleTraceExport = (event) => {
@@ -4218,7 +4791,7 @@ function createRuntime(composition) {
4218
4791
  const core = createRunCore(composition);
4219
4792
  const { activeRuns, activeWorkflowRuns, activeSessions, isIdle, waitForIdle, closeAdmission, isClosed, traceExports, runtimeOwner, createEvent, createCoordinatorEvent, notifyObserver, commit, startLease, cancelExecution, releaseWorkflowExecution, commitWorkflow } = core;
4220
4793
  const agentEngine = createAgentEngine(composition, core);
4221
- const { prepareExecution, executeAgent, nextEvent, drainExecution, resumeAgent, cancelStoredAgent } = agentEngine;
4794
+ const { prepareExecution, executeAgent, nextEvent, drainExecution, resumeAgent, cancelStoredAgent, getCompactionStats } = agentEngine;
4222
4795
  const { prepareWorkflowExecution, executeWorkflow, nextWorkflowEvent, drainWorkflowExecution, resumeWorkflow, cancelWorkflowExecution, cancelStoredWorkflow } = createWorkflowEngine(composition, core, agentEngine);
4223
4796
  const resumeRun = async (runId, resolution) => {
4224
4797
  if (isClosed()) throw new FevexRunError("RUNTIME_CLOSED", "Runtime is not accepting new runs", runId);
@@ -4408,6 +4981,7 @@ function createRuntime(composition) {
4408
4981
  role: "system",
4409
4982
  content: summary
4410
4983
  }];
4984
+ session.compaction = void 0;
4411
4985
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4412
4986
  await runStore.saveSession(session);
4413
4987
  return structuredClone(session);
@@ -4418,6 +4992,9 @@ function createRuntime(composition) {
4418
4992
  getObservabilityStats() {
4419
4993
  return traceExports.stats();
4420
4994
  },
4995
+ getCompactionStats() {
4996
+ return getCompactionStats();
4997
+ },
4421
4998
  shutdown
4422
4999
  };
4423
5000
  }
@@ -4433,4 +5010,4 @@ function createFevex(config) {
4433
5010
  return createRuntime(createComposition(config));
4434
5011
  }
4435
5012
  //#endregion
4436
- export { ArtifactError, CatalogedArtifactStore, ChannelError, FEVEX_JSON_SCHEMA_PROFILE_VERSION, FevexConfigurationError, FevexRunError, FileSystemArtifactStore, InMemoryArtifactCatalog, InMemoryArtifactStore, InMemoryMemoryStore, InMemoryRunStore, IntegrationError, PROVIDER_REASONING_UNSUPPORTED, PROVIDER_SCHEMA_UNSUPPORTED, RunPausedError, S3ArtifactStore, SCHEMA_NOT_TRANSPORTABLE, SandboxError, artifactTenantId, assertArtifactId, compileFevexJsonSchema, createAgentsMdContextProvider, createFevex, createLocalSandbox, createMemoryContextProvider, defineAgent, defineConnection, defineContextProvider, definePolicy, defineSkill, defineTeam, defineTool, defineWorkflow, deliverChannelRun, handleChannelInput, isDurableRunStore, isProgressiveSkillProvider, isRetainableRunStore, loadSkillFile, loadSkillFiles, sandboxResultToJson, toToolSpec, validateFevexJsonSchemaProfile, validateListEventsOptions, validatePurgeRunsOptions };
5013
+ export { ArtifactError, CatalogedArtifactStore, ChannelError, FEVEX_JSON_SCHEMA_PROFILE_VERSION, FevexConfigurationError, FevexRunError, FileSystemArtifactStore, InMemoryArtifactCatalog, InMemoryArtifactStore, InMemoryMemoryStore, InMemoryRunStore, InMemoryToolEmbeddingCache, IntegrationError, PROVIDER_REASONING_UNSUPPORTED, PROVIDER_SCHEMA_UNSUPPORTED, RunPausedError, S3ArtifactStore, SCHEMA_NOT_TRANSPORTABLE, SandboxError, artifactTenantId, assertArtifactId, buildToolEmbeddingIndex, compileFevexJsonSchema, cosineSimilarity, createAgentsMdContextProvider, createDigestContextCompactor, createFevex, createLocalSandbox, createMemoryContextProvider, createModelContextCompactor, defineAgent, defineConnection, defineContextProvider, definePolicy, defineSkill, defineTeam, defineTool, defineWorkflow, deliverChannelRun, estimateContextTokens, handleChannelInput, isDurableRunStore, isProgressiveSkillProvider, isRetainableRunStore, loadSkillFile, loadSkillFiles, resolveContextBudget, sandboxResultToJson, schemaKeywords, searchToolEmbeddingIndex, toToolSpec, toolEmbeddingKey, toolEmbeddingText, validateFevexJsonSchemaProfile, validateListEventsOptions, validatePurgeRunsOptions };