@falai/agent 1.1.3 → 1.2.1

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 (193) hide show
  1. package/README.md +9 -0
  2. package/dist/cjs/core/Agent.d.ts +17 -1
  3. package/dist/cjs/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/core/Agent.js +47 -0
  5. package/dist/cjs/core/Agent.js.map +1 -1
  6. package/dist/cjs/core/BatchPromptBuilder.d.ts +3 -0
  7. package/dist/cjs/core/BatchPromptBuilder.d.ts.map +1 -1
  8. package/dist/cjs/core/BatchPromptBuilder.js +4 -1
  9. package/dist/cjs/core/BatchPromptBuilder.js.map +1 -1
  10. package/dist/cjs/core/CompactionEngine.d.ts +65 -0
  11. package/dist/cjs/core/CompactionEngine.d.ts.map +1 -0
  12. package/dist/cjs/core/CompactionEngine.js +251 -0
  13. package/dist/cjs/core/CompactionEngine.js.map +1 -0
  14. package/dist/cjs/core/PromptComposer.d.ts +8 -1
  15. package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
  16. package/dist/cjs/core/PromptComposer.js +238 -126
  17. package/dist/cjs/core/PromptComposer.js.map +1 -1
  18. package/dist/cjs/core/PromptSectionCache.d.ts +57 -0
  19. package/dist/cjs/core/PromptSectionCache.d.ts.map +1 -0
  20. package/dist/cjs/core/PromptSectionCache.js +108 -0
  21. package/dist/cjs/core/PromptSectionCache.js.map +1 -0
  22. package/dist/cjs/core/ResponseEngine.d.ts +3 -2
  23. package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
  24. package/dist/cjs/core/ResponseEngine.js +8 -8
  25. package/dist/cjs/core/ResponseEngine.js.map +1 -1
  26. package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
  27. package/dist/cjs/core/ResponseModal.js +120 -70
  28. package/dist/cjs/core/ResponseModal.js.map +1 -1
  29. package/dist/cjs/core/ResponsePipeline.d.ts +2 -1
  30. package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
  31. package/dist/cjs/core/ResponsePipeline.js +17 -19
  32. package/dist/cjs/core/ResponsePipeline.js.map +1 -1
  33. package/dist/cjs/core/RoutingEngine.d.ts +10 -0
  34. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
  35. package/dist/cjs/core/RoutingEngine.js +5 -4
  36. package/dist/cjs/core/RoutingEngine.js.map +1 -1
  37. package/dist/cjs/core/SessionManager.d.ts.map +1 -1
  38. package/dist/cjs/core/SessionManager.js +20 -0
  39. package/dist/cjs/core/SessionManager.js.map +1 -1
  40. package/dist/cjs/core/StreamingToolExecutor.d.ts +142 -0
  41. package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -0
  42. package/dist/cjs/core/StreamingToolExecutor.js +455 -0
  43. package/dist/cjs/core/StreamingToolExecutor.js.map +1 -0
  44. package/dist/cjs/core/ToolManager.d.ts +18 -1
  45. package/dist/cjs/core/ToolManager.d.ts.map +1 -1
  46. package/dist/cjs/core/ToolManager.js +91 -0
  47. package/dist/cjs/core/ToolManager.js.map +1 -1
  48. package/dist/cjs/index.d.ts +5 -1
  49. package/dist/cjs/index.d.ts.map +1 -1
  50. package/dist/cjs/index.js +8 -2
  51. package/dist/cjs/index.js.map +1 -1
  52. package/dist/cjs/providers/AnthropicProvider.d.ts +7 -0
  53. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
  54. package/dist/cjs/providers/AnthropicProvider.js +109 -19
  55. package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
  56. package/dist/cjs/providers/GeminiProvider.d.ts +32 -0
  57. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  58. package/dist/cjs/providers/GeminiProvider.js +160 -53
  59. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  60. package/dist/cjs/providers/OpenAIProvider.d.ts +5 -0
  61. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  62. package/dist/cjs/providers/OpenAIProvider.js +65 -18
  63. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  64. package/dist/cjs/providers/OpenRouterProvider.d.ts +5 -0
  65. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
  66. package/dist/cjs/providers/OpenRouterProvider.js +57 -18
  67. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
  68. package/dist/cjs/types/agent.d.ts +44 -0
  69. package/dist/cjs/types/agent.d.ts.map +1 -1
  70. package/dist/cjs/types/agent.js.map +1 -1
  71. package/dist/cjs/types/ai.d.ts +2 -2
  72. package/dist/cjs/types/ai.d.ts.map +1 -1
  73. package/dist/cjs/types/compaction.d.ts +50 -0
  74. package/dist/cjs/types/compaction.d.ts.map +1 -0
  75. package/dist/cjs/types/compaction.js +6 -0
  76. package/dist/cjs/types/compaction.js.map +1 -0
  77. package/dist/cjs/types/index.d.ts +4 -2
  78. package/dist/cjs/types/index.d.ts.map +1 -1
  79. package/dist/cjs/types/index.js.map +1 -1
  80. package/dist/cjs/types/tool.d.ts +84 -0
  81. package/dist/cjs/types/tool.d.ts.map +1 -1
  82. package/dist/core/Agent.d.ts +17 -1
  83. package/dist/core/Agent.d.ts.map +1 -1
  84. package/dist/core/Agent.js +47 -0
  85. package/dist/core/Agent.js.map +1 -1
  86. package/dist/core/BatchPromptBuilder.d.ts +3 -0
  87. package/dist/core/BatchPromptBuilder.d.ts.map +1 -1
  88. package/dist/core/BatchPromptBuilder.js +4 -1
  89. package/dist/core/BatchPromptBuilder.js.map +1 -1
  90. package/dist/core/CompactionEngine.d.ts +65 -0
  91. package/dist/core/CompactionEngine.d.ts.map +1 -0
  92. package/dist/core/CompactionEngine.js +244 -0
  93. package/dist/core/CompactionEngine.js.map +1 -0
  94. package/dist/core/PromptComposer.d.ts +8 -1
  95. package/dist/core/PromptComposer.d.ts.map +1 -1
  96. package/dist/core/PromptComposer.js +238 -126
  97. package/dist/core/PromptComposer.js.map +1 -1
  98. package/dist/core/PromptSectionCache.d.ts +57 -0
  99. package/dist/core/PromptSectionCache.d.ts.map +1 -0
  100. package/dist/core/PromptSectionCache.js +104 -0
  101. package/dist/core/PromptSectionCache.js.map +1 -0
  102. package/dist/core/ResponseEngine.d.ts +3 -2
  103. package/dist/core/ResponseEngine.d.ts.map +1 -1
  104. package/dist/core/ResponseEngine.js +8 -8
  105. package/dist/core/ResponseEngine.js.map +1 -1
  106. package/dist/core/ResponseModal.d.ts.map +1 -1
  107. package/dist/core/ResponseModal.js +121 -71
  108. package/dist/core/ResponseModal.js.map +1 -1
  109. package/dist/core/ResponsePipeline.d.ts +2 -1
  110. package/dist/core/ResponsePipeline.d.ts.map +1 -1
  111. package/dist/core/ResponsePipeline.js +18 -20
  112. package/dist/core/ResponsePipeline.js.map +1 -1
  113. package/dist/core/RoutingEngine.d.ts +10 -0
  114. package/dist/core/RoutingEngine.d.ts.map +1 -1
  115. package/dist/core/RoutingEngine.js +6 -5
  116. package/dist/core/RoutingEngine.js.map +1 -1
  117. package/dist/core/SessionManager.d.ts.map +1 -1
  118. package/dist/core/SessionManager.js +17 -0
  119. package/dist/core/SessionManager.js.map +1 -1
  120. package/dist/core/StreamingToolExecutor.d.ts +142 -0
  121. package/dist/core/StreamingToolExecutor.d.ts.map +1 -0
  122. package/dist/core/StreamingToolExecutor.js +448 -0
  123. package/dist/core/StreamingToolExecutor.js.map +1 -0
  124. package/dist/core/ToolManager.d.ts +18 -1
  125. package/dist/core/ToolManager.d.ts.map +1 -1
  126. package/dist/core/ToolManager.js +91 -0
  127. package/dist/core/ToolManager.js.map +1 -1
  128. package/dist/index.d.ts +5 -1
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +3 -0
  131. package/dist/index.js.map +1 -1
  132. package/dist/providers/AnthropicProvider.d.ts +7 -0
  133. package/dist/providers/AnthropicProvider.d.ts.map +1 -1
  134. package/dist/providers/AnthropicProvider.js +109 -19
  135. package/dist/providers/AnthropicProvider.js.map +1 -1
  136. package/dist/providers/GeminiProvider.d.ts +32 -0
  137. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  138. package/dist/providers/GeminiProvider.js +160 -53
  139. package/dist/providers/GeminiProvider.js.map +1 -1
  140. package/dist/providers/OpenAIProvider.d.ts +5 -0
  141. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  142. package/dist/providers/OpenAIProvider.js +65 -18
  143. package/dist/providers/OpenAIProvider.js.map +1 -1
  144. package/dist/providers/OpenRouterProvider.d.ts +5 -0
  145. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  146. package/dist/providers/OpenRouterProvider.js +57 -18
  147. package/dist/providers/OpenRouterProvider.js.map +1 -1
  148. package/dist/types/agent.d.ts +44 -0
  149. package/dist/types/agent.d.ts.map +1 -1
  150. package/dist/types/agent.js.map +1 -1
  151. package/dist/types/ai.d.ts +2 -2
  152. package/dist/types/ai.d.ts.map +1 -1
  153. package/dist/types/compaction.d.ts +50 -0
  154. package/dist/types/compaction.d.ts.map +1 -0
  155. package/dist/types/compaction.js +5 -0
  156. package/dist/types/compaction.js.map +1 -0
  157. package/dist/types/index.d.ts +4 -2
  158. package/dist/types/index.d.ts.map +1 -1
  159. package/dist/types/index.js.map +1 -1
  160. package/dist/types/tool.d.ts +84 -0
  161. package/dist/types/tool.d.ts.map +1 -1
  162. package/docs/api/overview.md +140 -0
  163. package/docs/core/tools/enhanced-tool.md +186 -0
  164. package/docs/core/tools/streaming-execution.md +161 -0
  165. package/docs/guides/context-compaction.md +96 -0
  166. package/docs/guides/prompt-optimization.md +164 -0
  167. package/examples/advanced-patterns/context-compaction.ts +223 -0
  168. package/examples/advanced-patterns/streaming-responses.ts +85 -7
  169. package/examples/tools/enhanced-tool-metadata.ts +268 -0
  170. package/examples/tools/streaming-tool-execution.ts +283 -0
  171. package/package.json +1 -1
  172. package/src/core/Agent.ts +58 -2
  173. package/src/core/BatchPromptBuilder.ts +4 -1
  174. package/src/core/CompactionEngine.ts +318 -0
  175. package/src/core/PromptComposer.ts +259 -156
  176. package/src/core/PromptSectionCache.ts +136 -0
  177. package/src/core/ResponseEngine.ts +7 -11
  178. package/src/core/ResponseModal.ts +133 -83
  179. package/src/core/ResponsePipeline.ts +22 -22
  180. package/src/core/RoutingEngine.ts +16 -5
  181. package/src/core/SessionManager.ts +19 -0
  182. package/src/core/StreamingToolExecutor.ts +572 -0
  183. package/src/core/ToolManager.ts +151 -41
  184. package/src/index.ts +14 -0
  185. package/src/providers/AnthropicProvider.ts +121 -24
  186. package/src/providers/GeminiProvider.ts +174 -54
  187. package/src/providers/OpenAIProvider.ts +77 -25
  188. package/src/providers/OpenRouterProvider.ts +68 -25
  189. package/src/types/agent.ts +45 -0
  190. package/src/types/ai.ts +2 -2
  191. package/src/types/compaction.ts +52 -0
  192. package/src/types/index.ts +35 -14
  193. package/src/types/tool.ts +108 -0
@@ -13,6 +13,7 @@ import type {
13
13
  AgentStructuredResponse,
14
14
  StructuredSchema,
15
15
  } from "../types";
16
+ import type { HistoryItem } from "../types/history";
16
17
  import { withTimeoutAndRetry, logger } from "../utils";
17
18
  import { FunctionParameters } from "openai/resources/shared.mjs";
18
19
 
@@ -161,6 +162,52 @@ export class OpenAIProvider implements AiProvider {
161
162
  };
162
163
  }
163
164
 
165
+ /**
166
+ * Build OpenAI-formatted messages from HistoryItem[] array.
167
+ * Maps directly to ChatCompletionMessageParam format.
168
+ */
169
+ private buildOpenAIMessages(history: HistoryItem[]): Array<unknown> {
170
+ const messages: Array<unknown> = [];
171
+
172
+ for (const item of history) {
173
+ switch (item.role) {
174
+ case "system":
175
+ messages.push({ role: "system", content: item.content });
176
+ break;
177
+ case "user":
178
+ messages.push({ role: "user", content: item.content });
179
+ break;
180
+ case "assistant":
181
+ if (item.tool_calls && item.tool_calls.length > 0) {
182
+ messages.push({
183
+ role: "assistant",
184
+ content: item.content || null,
185
+ tool_calls: item.tool_calls.map(tc => ({
186
+ id: tc.id,
187
+ type: "function",
188
+ function: {
189
+ name: tc.name,
190
+ arguments: JSON.stringify(tc.arguments),
191
+ },
192
+ })),
193
+ });
194
+ } else {
195
+ messages.push({ role: "assistant", content: item.content || "" });
196
+ }
197
+ break;
198
+ case "tool":
199
+ messages.push({
200
+ role: "tool",
201
+ tool_call_id: item.tool_call_id,
202
+ content: typeof item.content === "string" ? item.content : JSON.stringify(item.content),
203
+ });
204
+ break;
205
+ }
206
+ }
207
+
208
+ return messages;
209
+ }
210
+
164
211
  /**
165
212
  * Adapt common schema format to OpenAI's format.
166
213
  * OpenAI uses standard JSON Schema, so this is mostly a passthrough.
@@ -222,8 +269,7 @@ export class OpenAIProvider implements AiProvider {
222
269
  for (let i = 0; i < this.backupModels.length; i++) {
223
270
  const backupModel = this.backupModels[i];
224
271
  logger.debug(
225
- `[OPENAI] Trying backup model ${i + 1}/${
226
- this.backupModels.length
272
+ `[OPENAI] Trying backup model ${i + 1}/${this.backupModels.length
227
273
  }: ${backupModel}`
228
274
  );
229
275
 
@@ -266,14 +312,12 @@ export class OpenAIProvider implements AiProvider {
266
312
  input: GenerateMessageInput<TContext>
267
313
  ): Promise<GenerateMessageOutput<TStructured>> {
268
314
  const operation = async (): Promise<GenerateMessageOutput> => {
315
+ const historyMessages = this.buildOpenAIMessages(input.history);
316
+ historyMessages.push({ role: "user", content: input.prompt });
317
+
269
318
  const params: ChatCompletionCreateParamsNonStreaming = {
270
319
  model,
271
- messages: [
272
- {
273
- role: "user",
274
- content: input.prompt,
275
- },
276
- ],
320
+ messages: historyMessages as ChatCompletionCreateParamsNonStreaming["messages"],
277
321
  ...this.config,
278
322
  };
279
323
 
@@ -336,10 +380,7 @@ export class OpenAIProvider implements AiProvider {
336
380
  // Fall back to regular chat completions API if no schema provided
337
381
  const response = await this.client.chat.completions.create(params);
338
382
 
339
- const message = response.choices[0]?.message?.content;
340
- if (!message) {
341
- throw new Error("No response from OpenAI");
342
- }
383
+ const message = response.choices[0]?.message?.content || "";
343
384
 
344
385
  let toolCalls: Array<{
345
386
  toolName: string;
@@ -368,7 +409,11 @@ export class OpenAIProvider implements AiProvider {
368
409
  };
369
410
  });
370
411
  }
371
- // Extract tool calls from response
412
+
413
+ // Only throw error if we have no text AND no function calls
414
+ if (!message && toolCalls.length === 0) {
415
+ throw new Error("No response from OpenAI");
416
+ }
372
417
 
373
418
  return {
374
419
  message,
@@ -423,8 +468,7 @@ export class OpenAIProvider implements AiProvider {
423
468
  for (let i = 0; i < this.backupModels.length; i++) {
424
469
  const backupModel = this.backupModels[i];
425
470
  logger.debug(
426
- `[OPENAI] Trying backup model ${i + 1}/${
427
- this.backupModels.length
471
+ `[OPENAI] Trying backup model ${i + 1}/${this.backupModels.length
428
472
  }: ${backupModel}`
429
473
  );
430
474
 
@@ -469,15 +513,14 @@ export class OpenAIProvider implements AiProvider {
469
513
  model: string,
470
514
  input: GenerateMessageInput<TContext>
471
515
  ): AsyncGenerator<GenerateMessageStreamChunk<TStructured>> {
516
+ // Build messages from history and append prompt as final user message
517
+ const historyMessages = this.buildOpenAIMessages(input.history);
518
+ historyMessages.push({ role: "user" as const, content: input.prompt });
519
+
472
520
  const params = {
473
521
  ...this.config,
474
522
  model,
475
- messages: [
476
- {
477
- role: "user" as const,
478
- content: input.prompt,
479
- },
480
- ],
523
+ messages: historyMessages as ChatCompletionCreateParamsNonStreaming["messages"],
481
524
  stream: true as const,
482
525
  };
483
526
 
@@ -530,9 +573,9 @@ export class OpenAIProvider implements AiProvider {
530
573
  try {
531
574
  toolCallArguments = toolCall.function.arguments
532
575
  ? (JSON.parse(toolCall.function.arguments) as Record<
533
- string,
534
- unknown
535
- >)
576
+ string,
577
+ unknown
578
+ >)
536
579
  : {};
537
580
  } catch (error) {
538
581
  logger.warn(
@@ -584,6 +627,15 @@ export class OpenAIProvider implements AiProvider {
584
627
  }
585
628
  }
586
629
 
630
+ // Include tool calls in structured response (even without JSON schema)
631
+ if (toolCalls.length > 0) {
632
+ structured = {
633
+ ...(structured || {}),
634
+ message: (structured as AgentStructuredResponse | undefined)?.message || accumulated,
635
+ toolCalls,
636
+ } as TStructured;
637
+ }
638
+
587
639
  // Yield final chunk
588
640
  yield {
589
641
  delta: "",
@@ -596,7 +648,7 @@ export class OpenAIProvider implements AiProvider {
596
648
  promptTokens,
597
649
  completionTokens,
598
650
  },
599
- structured: structured ? { ...structured, toolCalls } : undefined,
651
+ structured,
600
652
  };
601
653
  }
602
654
  }
@@ -15,6 +15,7 @@ import type {
15
15
  GenerateMessageStreamChunk,
16
16
  StructuredSchema,
17
17
  } from "../types";
18
+ import type { HistoryItem } from "../types/history";
18
19
  import { withTimeoutAndRetry, logger } from "../utils";
19
20
 
20
21
  const DEFAULT_RETRY_CONFIG = {
@@ -171,6 +172,52 @@ export class OpenRouterProvider implements AiProvider {
171
172
  };
172
173
  }
173
174
 
175
+ /**
176
+ * Build OpenRouter-formatted messages from HistoryItem[] array.
177
+ * OpenRouter uses OpenAI-compatible format.
178
+ */
179
+ private buildOpenRouterMessages(history: HistoryItem[]): Array<unknown> {
180
+ const messages: Array<unknown> = [];
181
+
182
+ for (const item of history) {
183
+ switch (item.role) {
184
+ case "system":
185
+ messages.push({ role: "system", content: item.content });
186
+ break;
187
+ case "user":
188
+ messages.push({ role: "user", content: item.content });
189
+ break;
190
+ case "assistant":
191
+ if (item.tool_calls && item.tool_calls.length > 0) {
192
+ messages.push({
193
+ role: "assistant",
194
+ content: item.content || null,
195
+ tool_calls: item.tool_calls.map(tc => ({
196
+ id: tc.id,
197
+ type: "function",
198
+ function: {
199
+ name: tc.name,
200
+ arguments: JSON.stringify(tc.arguments),
201
+ },
202
+ })),
203
+ });
204
+ } else {
205
+ messages.push({ role: "assistant", content: item.content || "" });
206
+ }
207
+ break;
208
+ case "tool":
209
+ messages.push({
210
+ role: "tool",
211
+ tool_call_id: item.tool_call_id,
212
+ content: typeof item.content === "string" ? item.content : JSON.stringify(item.content),
213
+ });
214
+ break;
215
+ }
216
+ }
217
+
218
+ return messages;
219
+ }
220
+
174
221
  /**
175
222
  * Adapt common schema format to OpenRouter's format.
176
223
  * OpenRouter is OpenAI-compatible and uses standard JSON Schema.
@@ -229,8 +276,7 @@ export class OpenRouterProvider implements AiProvider {
229
276
  for (let i = 0; i < this.backupModels.length; i++) {
230
277
  const backupModel = this.backupModels[i];
231
278
  logger.debug(
232
- `[OPENROUTER] Trying backup model ${i + 1}/${
233
- this.backupModels.length
279
+ `[OPENROUTER] Trying backup model ${i + 1}/${this.backupModels.length
234
280
  }: ${backupModel}`
235
281
  );
236
282
 
@@ -273,14 +319,12 @@ export class OpenRouterProvider implements AiProvider {
273
319
  input: GenerateMessageInput<TContext>
274
320
  ): Promise<GenerateMessageOutput<TStructured>> {
275
321
  const operation = async (): Promise<GenerateMessageOutput> => {
322
+ const historyMessages = this.buildOpenRouterMessages(input.history);
323
+ historyMessages.push({ role: "user", content: input.prompt });
324
+
276
325
  const params: ChatCompletionCreateParamsNonStreaming = {
277
326
  model,
278
- messages: [
279
- {
280
- role: "user",
281
- content: input.prompt,
282
- },
283
- ],
327
+ messages: historyMessages as ChatCompletionCreateParamsNonStreaming["messages"],
284
328
  ...this.config,
285
329
  };
286
330
 
@@ -345,10 +389,7 @@ export class OpenRouterProvider implements AiProvider {
345
389
  // Fall back to regular chat completions API if no schema provided
346
390
  const response = await this.client.chat.completions.create(params);
347
391
 
348
- const message = response.choices[0]?.message?.content;
349
- if (!message) {
350
- throw new Error("No response from OpenRouter");
351
- }
392
+ const message = response.choices[0]?.message?.content || "";
352
393
 
353
394
  let toolCalls: Array<{
354
395
  toolName: string;
@@ -378,6 +419,11 @@ export class OpenRouterProvider implements AiProvider {
378
419
  });
379
420
  }
380
421
 
422
+ // Only throw error if we have no text AND no function calls
423
+ if (!message && toolCalls.length === 0) {
424
+ throw new Error("No response from OpenRouter");
425
+ }
426
+
381
427
  return {
382
428
  message,
383
429
  metadata: {
@@ -428,8 +474,7 @@ export class OpenRouterProvider implements AiProvider {
428
474
  for (let i = 0; i < this.backupModels.length; i++) {
429
475
  const backupModel = this.backupModels[i];
430
476
  logger.debug(
431
- `[OPENROUTER] Trying backup model ${i + 1}/${
432
- this.backupModels.length
477
+ `[OPENROUTER] Trying backup model ${i + 1}/${this.backupModels.length
433
478
  }: ${backupModel}`
434
479
  );
435
480
 
@@ -471,15 +516,13 @@ export class OpenRouterProvider implements AiProvider {
471
516
  model: string,
472
517
  input: GenerateMessageInput<TContext>
473
518
  ): AsyncGenerator<GenerateMessageStreamChunk<TStructured>> {
519
+ const historyMessages = this.buildOpenRouterMessages(input.history);
520
+ historyMessages.push({ role: "user" as const, content: input.prompt });
521
+
474
522
  const params = {
475
523
  ...this.config,
476
524
  model,
477
- messages: [
478
- {
479
- role: "user" as const,
480
- content: input.prompt,
481
- },
482
- ],
525
+ messages: historyMessages as ChatCompletionCreateParamsNonStreaming["messages"],
483
526
  stream: true as const,
484
527
  };
485
528
 
@@ -532,9 +575,9 @@ export class OpenRouterProvider implements AiProvider {
532
575
  try {
533
576
  toolCallArguments = toolCall.function.arguments
534
577
  ? (JSON.parse(toolCall.function.arguments) as Record<
535
- string,
536
- unknown
537
- >)
578
+ string,
579
+ unknown
580
+ >)
538
581
  : {};
539
582
  } catch (error) {
540
583
  logger.warn(
@@ -589,9 +632,9 @@ export class OpenRouterProvider implements AiProvider {
589
632
  // If tools were used, include them in structured response
590
633
  if (toolCalls.length > 0) {
591
634
  structured = {
592
- message: accumulated,
635
+ ...(structured || {}),
636
+ message: (structured as AgentStructuredResponse | undefined)?.message || accumulated,
593
637
  toolCalls,
594
- ...structured,
595
638
  } as TStructured;
596
639
  }
597
640
 
@@ -9,6 +9,39 @@ import type { PersistenceConfig } from "./persistence";
9
9
  import type { SessionState } from "./session";
10
10
  import type { StructuredSchema } from "./schema";
11
11
  import { Template, ConditionTemplate } from "./template";
12
+ import type { PromptCacheConfig } from "../core/PromptSectionCache";
13
+
14
+ /**
15
+ * Agent-level compaction configuration.
16
+ * Unlike CompactionOptions, this does not require a `provider` since the agent already has one.
17
+ */
18
+ export interface AgentCompactionConfig {
19
+ /** Maximum token budget for the conversation */
20
+ maxTokens: number;
21
+ /**
22
+ * Threshold ratio (0–1) at which to trigger compaction.
23
+ * Must be between 0.5 and 0.95.
24
+ * @default 0.8
25
+ */
26
+ compactionThreshold?: number;
27
+ /**
28
+ * Number of recent messages to always preserve unchanged.
29
+ * Must be >= 2.
30
+ * @default 4
31
+ */
32
+ preserveRecentCount?: number;
33
+ /**
34
+ * Maximum characters per tool result before truncation.
35
+ * Must be > 0.
36
+ * @default 5000
37
+ */
38
+ maxToolResultChars?: number;
39
+ /**
40
+ * Whether compaction is enabled.
41
+ * @default true when config is provided
42
+ */
43
+ enabled?: boolean;
44
+ }
12
45
 
13
46
  /**
14
47
  * Composition mode determines how the agent processes and structures responses
@@ -132,6 +165,18 @@ export interface AgentOptions<TContext = unknown, TData = unknown> {
132
165
  * @default 1
133
166
  */
134
167
  maxStepsPerBatch?: number;
168
+ /**
169
+ * Optional compaction configuration for managing conversation history size.
170
+ * When provided, the agent will validate the options and make them available
171
+ * for use by the SessionManager/CompactionEngine.
172
+ */
173
+ compaction?: AgentCompactionConfig;
174
+ /**
175
+ * Optional prompt cache configuration for controlling section memoization behavior.
176
+ * When provided, controls whether prompt sections are cached across turns.
177
+ * @default { enabled: true }
178
+ */
179
+ promptCache?: PromptCacheConfig;
135
180
  }
136
181
 
137
182
  /**
package/src/types/ai.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * AI provider strategy types
3
3
  */
4
4
 
5
- import type { Event } from "./history";
5
+ import type { HistoryItem } from "./history";
6
6
 
7
7
  /**
8
8
  * Reasoning/thinking configuration for AI models
@@ -36,7 +36,7 @@ export interface GenerateMessageInput<TContext = unknown> {
36
36
  /** The constructed prompt */
37
37
  prompt: string;
38
38
  /** Interaction history */
39
- history: Event[];
39
+ history: HistoryItem[];
40
40
  /** Context data */
41
41
  context: TContext;
42
42
  /** Tools available for AI to call during this interaction */
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Context compaction types for managing conversation history size
3
+ */
4
+
5
+ import type { AiProvider } from "./ai";
6
+ import type { HistoryItem } from "./history";
7
+
8
+ /**
9
+ * Configuration for the compaction engine.
10
+ *
11
+ * Validation constraints:
12
+ * - `compactionThreshold` must be between 0.5 and 0.95
13
+ * - `preserveRecentCount` must be >= 2
14
+ * - `maxToolResultChars` must be > 0
15
+ */
16
+ export interface CompactionOptions {
17
+ /** Maximum token budget for the conversation */
18
+ maxTokens: number;
19
+ /**
20
+ * Threshold ratio (0–1) at which to trigger compaction.
21
+ * Must be between 0.5 and 0.95.
22
+ */
23
+ compactionThreshold: number;
24
+ /**
25
+ * Number of recent messages to always preserve unchanged.
26
+ * Must be >= 2.
27
+ */
28
+ preserveRecentCount: number;
29
+ /**
30
+ * Maximum characters per tool result before truncation.
31
+ * Must be > 0.
32
+ */
33
+ maxToolResultChars: number;
34
+ /** Provider to use for LLM summarization during auto-compact */
35
+ provider: AiProvider;
36
+ }
37
+
38
+ /**
39
+ * Result of a compaction operation
40
+ */
41
+ export interface CompactionResult {
42
+ /** The compacted history */
43
+ history: HistoryItem[];
44
+ /** Strategy that was applied */
45
+ strategy: 'none' | 'tool_result_budget' | 'micro_compact' | 'auto_compact';
46
+ /** Estimated tokens after compaction */
47
+ estimatedTokens: number;
48
+ /** Number of messages removed/compacted */
49
+ messagesCompacted: number;
50
+ /** Summary text (if auto-compact was used) */
51
+ summary?: string;
52
+ }
@@ -5,6 +5,7 @@
5
5
  // Agent types
6
6
  export type {
7
7
  AgentOptions,
8
+ AgentCompactionConfig,
8
9
  Term,
9
10
  Guideline,
10
11
  GuidelineMatch,
@@ -58,12 +59,19 @@ export * from "./route";
58
59
  export type { SessionState, PendingTransition } from "./session";
59
60
 
60
61
  // Tool types
61
- export type {
62
- Tool,
63
- ToolContext,
64
- ToolResult,
62
+ export type {
63
+ Tool,
64
+ ToolContext,
65
+ ToolResult,
65
66
  ToolHandler,
66
67
  ToolExecutionResult,
68
+ EnhancedTool,
69
+ ToolValidationResult,
70
+ ToolPermissionResult,
71
+ ToolCallRequest,
72
+ ToolExecutionUpdate,
73
+ TrackedTool,
74
+ ToolStatus,
67
75
  DataEnrichmentConfig,
68
76
  ValidationConfig,
69
77
  ApiCallConfig,
@@ -71,6 +79,19 @@ export type {
71
79
  } from "./tool";
72
80
  export { ToolScope } from "./tool";
73
81
 
82
+ // Compaction types
83
+ export type {
84
+ CompactionOptions,
85
+ CompactionResult,
86
+ } from "./compaction";
87
+
88
+ // Prompt cache types (re-exported from core)
89
+ export type {
90
+ PromptSectionType,
91
+ PromptCacheConfig,
92
+ SectionCompute,
93
+ } from "../core/PromptSectionCache";
94
+
74
95
  // AI provider types
75
96
  export type {
76
97
  AiProvider,
@@ -105,15 +126,15 @@ export type {
105
126
  export * from "./persistence";
106
127
 
107
128
  // Template types
108
- export type {
109
- Template,
110
- TemplateContext,
111
- ConditionTemplate,
112
- ConditionEvaluationResult
129
+ export type {
130
+ Template,
131
+ TemplateContext,
132
+ ConditionTemplate,
133
+ ConditionEvaluationResult
113
134
  } from "./template";
114
- export {
115
- ConditionEvaluator,
116
- createConditionEvaluator,
117
- extractAIContextStrings,
118
- hasProgrammaticConditions
135
+ export {
136
+ ConditionEvaluator,
137
+ createConditionEvaluator,
138
+ extractAIContextStrings,
139
+ hasProgrammaticConditions
119
140
  } from "../utils/condition";
package/src/types/tool.ts CHANGED
@@ -116,6 +116,114 @@ export enum ToolScope {
116
116
 
117
117
 
118
118
 
119
+ // --- EnhancedTool and supporting types ---
120
+
121
+ /**
122
+ * Result of input validation on a tool call
123
+ */
124
+ export interface ToolValidationResult {
125
+ valid: boolean;
126
+ error?: string;
127
+ /** Suggested corrected input */
128
+ correctedInput?: Record<string, unknown>;
129
+ }
130
+
131
+ /**
132
+ * Result of a permission check on a tool call
133
+ */
134
+ export interface ToolPermissionResult {
135
+ allowed: boolean;
136
+ reason?: string;
137
+ /** If not allowed, can the user override? */
138
+ canOverride?: boolean;
139
+ }
140
+
141
+ /**
142
+ * A single tool invocation request from the LLM
143
+ */
144
+ export interface ToolCallRequest {
145
+ /** Unique ID for this tool call instance */
146
+ id: string;
147
+ /** Tool name/ID to execute */
148
+ toolName: string;
149
+ /** Arguments passed to the tool */
150
+ arguments: Record<string, unknown>;
151
+ }
152
+
153
+ /**
154
+ * Result or progress update from a tool execution
155
+ */
156
+ export interface ToolExecutionUpdate<TData = unknown> {
157
+ /** The tool call this update relates to */
158
+ toolCallId: string;
159
+ /** Result message (undefined for progress updates) */
160
+ result?: ToolExecutionResult;
161
+ /** Progress message for long-running tools */
162
+ progress?: string;
163
+ /** Updated context after tool execution */
164
+ contextUpdate?: Record<string, unknown>;
165
+ /** Updated data after tool execution */
166
+ dataUpdate?: Partial<TData>;
167
+ }
168
+
169
+ /**
170
+ * Internal status of a tracked tool in the executor queue
171
+ */
172
+ export type ToolStatus = 'queued' | 'executing' | 'completed' | 'yielded';
173
+
174
+ /**
175
+ * Internal type tracking the state of a queued or executing tool
176
+ */
177
+ export interface TrackedTool<TContext = unknown, TData = unknown> {
178
+ id: string;
179
+ toolCall: ToolCallRequest;
180
+ tool: EnhancedTool<TContext, TData>;
181
+ status: ToolStatus;
182
+ isConcurrencySafe: boolean;
183
+ promise?: Promise<void>;
184
+ results: ToolExecutionResult[];
185
+ pendingProgress: string[];
186
+ }
187
+
188
+ /**
189
+ * Extended tool interface with rich metadata for concurrency control,
190
+ * permission gating, input validation, and result size management.
191
+ *
192
+ * All additional methods/properties are optional — plain `Tool` objects
193
+ * remain fully compatible.
194
+ */
195
+ export interface EnhancedTool<
196
+ TContext = any,
197
+ TData = any,
198
+ TResult = any
199
+ > extends Tool<TContext, TData, TResult> {
200
+ /** Whether this tool is safe to run concurrently with other concurrent-safe tools */
201
+ isConcurrencySafe?(input?: Record<string, unknown>): boolean;
202
+ /** Whether this tool only reads data without side effects */
203
+ isReadOnly?(input?: Record<string, unknown>): boolean;
204
+ /** Whether this tool performs destructive/irreversible operations */
205
+ isDestructive?(input?: Record<string, unknown>): boolean;
206
+
207
+ /** How the tool responds to abort signals: 'cancel' = immediate abort, 'block' = allow completion */
208
+ interruptBehavior?(): 'cancel' | 'block';
209
+ /** Maximum characters for the tool result before truncation */
210
+ maxResultSizeChars?: number;
211
+
212
+ /** Validate input before execution */
213
+ validateInput?(
214
+ input: Record<string, unknown>,
215
+ context: ToolContext<TContext, TData>
216
+ ): Promise<ToolValidationResult> | ToolValidationResult;
217
+
218
+ /** Check permissions before execution */
219
+ checkPermissions?(
220
+ input: Record<string, unknown>,
221
+ context: ToolContext<TContext, TData>
222
+ ): Promise<ToolPermissionResult> | ToolPermissionResult;
223
+ }
224
+
225
+ // --- Existing tool configuration types ---
226
+
119
227
  /**
120
228
  * Configuration for data enrichment tools
121
229
  */