@falai/agent 1.1.2 → 1.2.0

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 (173) 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 +14 -11
  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 -118
  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 -0
  23. package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
  24. package/dist/cjs/core/ResponseEngine.js +10 -6
  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 +79 -20
  28. package/dist/cjs/core/ResponseModal.js.map +1 -1
  29. package/dist/cjs/core/RoutingEngine.d.ts +10 -0
  30. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
  31. package/dist/cjs/core/RoutingEngine.js +3 -2
  32. package/dist/cjs/core/RoutingEngine.js.map +1 -1
  33. package/dist/cjs/core/SessionManager.d.ts.map +1 -1
  34. package/dist/cjs/core/SessionManager.js +20 -0
  35. package/dist/cjs/core/SessionManager.js.map +1 -1
  36. package/dist/cjs/core/StreamingToolExecutor.d.ts +142 -0
  37. package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -0
  38. package/dist/cjs/core/StreamingToolExecutor.js +455 -0
  39. package/dist/cjs/core/StreamingToolExecutor.js.map +1 -0
  40. package/dist/cjs/core/ToolManager.d.ts +18 -1
  41. package/dist/cjs/core/ToolManager.d.ts.map +1 -1
  42. package/dist/cjs/core/ToolManager.js +91 -0
  43. package/dist/cjs/core/ToolManager.js.map +1 -1
  44. package/dist/cjs/index.d.ts +5 -1
  45. package/dist/cjs/index.d.ts.map +1 -1
  46. package/dist/cjs/index.js +8 -2
  47. package/dist/cjs/index.js.map +1 -1
  48. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
  49. package/dist/cjs/providers/AnthropicProvider.js +8 -7
  50. package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
  51. package/dist/cjs/providers/GeminiProvider.d.ts +25 -0
  52. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  53. package/dist/cjs/providers/GeminiProvider.js +79 -51
  54. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  55. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  56. package/dist/cjs/providers/OpenAIProvider.js +14 -6
  57. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  58. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
  59. package/dist/cjs/providers/OpenRouterProvider.js +7 -6
  60. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
  61. package/dist/cjs/types/agent.d.ts +44 -0
  62. package/dist/cjs/types/agent.d.ts.map +1 -1
  63. package/dist/cjs/types/agent.js.map +1 -1
  64. package/dist/cjs/types/compaction.d.ts +50 -0
  65. package/dist/cjs/types/compaction.d.ts.map +1 -0
  66. package/dist/cjs/types/compaction.js +6 -0
  67. package/dist/cjs/types/compaction.js.map +1 -0
  68. package/dist/cjs/types/index.d.ts +4 -2
  69. package/dist/cjs/types/index.d.ts.map +1 -1
  70. package/dist/cjs/types/index.js.map +1 -1
  71. package/dist/cjs/types/tool.d.ts +84 -0
  72. package/dist/cjs/types/tool.d.ts.map +1 -1
  73. package/dist/core/Agent.d.ts +17 -1
  74. package/dist/core/Agent.d.ts.map +1 -1
  75. package/dist/core/Agent.js +47 -0
  76. package/dist/core/Agent.js.map +1 -1
  77. package/dist/core/BatchPromptBuilder.d.ts +3 -0
  78. package/dist/core/BatchPromptBuilder.d.ts.map +1 -1
  79. package/dist/core/BatchPromptBuilder.js +14 -11
  80. package/dist/core/BatchPromptBuilder.js.map +1 -1
  81. package/dist/core/CompactionEngine.d.ts +65 -0
  82. package/dist/core/CompactionEngine.d.ts.map +1 -0
  83. package/dist/core/CompactionEngine.js +244 -0
  84. package/dist/core/CompactionEngine.js.map +1 -0
  85. package/dist/core/PromptComposer.d.ts +8 -1
  86. package/dist/core/PromptComposer.d.ts.map +1 -1
  87. package/dist/core/PromptComposer.js +238 -118
  88. package/dist/core/PromptComposer.js.map +1 -1
  89. package/dist/core/PromptSectionCache.d.ts +57 -0
  90. package/dist/core/PromptSectionCache.d.ts.map +1 -0
  91. package/dist/core/PromptSectionCache.js +104 -0
  92. package/dist/core/PromptSectionCache.js.map +1 -0
  93. package/dist/core/ResponseEngine.d.ts +3 -0
  94. package/dist/core/ResponseEngine.d.ts.map +1 -1
  95. package/dist/core/ResponseEngine.js +10 -6
  96. package/dist/core/ResponseEngine.js.map +1 -1
  97. package/dist/core/ResponseModal.d.ts.map +1 -1
  98. package/dist/core/ResponseModal.js +79 -20
  99. package/dist/core/ResponseModal.js.map +1 -1
  100. package/dist/core/RoutingEngine.d.ts +10 -0
  101. package/dist/core/RoutingEngine.d.ts.map +1 -1
  102. package/dist/core/RoutingEngine.js +3 -2
  103. package/dist/core/RoutingEngine.js.map +1 -1
  104. package/dist/core/SessionManager.d.ts.map +1 -1
  105. package/dist/core/SessionManager.js +17 -0
  106. package/dist/core/SessionManager.js.map +1 -1
  107. package/dist/core/StreamingToolExecutor.d.ts +142 -0
  108. package/dist/core/StreamingToolExecutor.d.ts.map +1 -0
  109. package/dist/core/StreamingToolExecutor.js +448 -0
  110. package/dist/core/StreamingToolExecutor.js.map +1 -0
  111. package/dist/core/ToolManager.d.ts +18 -1
  112. package/dist/core/ToolManager.d.ts.map +1 -1
  113. package/dist/core/ToolManager.js +91 -0
  114. package/dist/core/ToolManager.js.map +1 -1
  115. package/dist/index.d.ts +5 -1
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +3 -0
  118. package/dist/index.js.map +1 -1
  119. package/dist/providers/AnthropicProvider.d.ts.map +1 -1
  120. package/dist/providers/AnthropicProvider.js +8 -7
  121. package/dist/providers/AnthropicProvider.js.map +1 -1
  122. package/dist/providers/GeminiProvider.d.ts +25 -0
  123. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  124. package/dist/providers/GeminiProvider.js +79 -51
  125. package/dist/providers/GeminiProvider.js.map +1 -1
  126. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  127. package/dist/providers/OpenAIProvider.js +14 -6
  128. package/dist/providers/OpenAIProvider.js.map +1 -1
  129. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  130. package/dist/providers/OpenRouterProvider.js +7 -6
  131. package/dist/providers/OpenRouterProvider.js.map +1 -1
  132. package/dist/types/agent.d.ts +44 -0
  133. package/dist/types/agent.d.ts.map +1 -1
  134. package/dist/types/agent.js.map +1 -1
  135. package/dist/types/compaction.d.ts +50 -0
  136. package/dist/types/compaction.d.ts.map +1 -0
  137. package/dist/types/compaction.js +5 -0
  138. package/dist/types/compaction.js.map +1 -0
  139. package/dist/types/index.d.ts +4 -2
  140. package/dist/types/index.d.ts.map +1 -1
  141. package/dist/types/index.js.map +1 -1
  142. package/dist/types/tool.d.ts +84 -0
  143. package/dist/types/tool.d.ts.map +1 -1
  144. package/docs/api/overview.md +140 -0
  145. package/docs/core/tools/enhanced-tool.md +186 -0
  146. package/docs/core/tools/streaming-execution.md +161 -0
  147. package/docs/guides/context-compaction.md +96 -0
  148. package/docs/guides/prompt-optimization.md +164 -0
  149. package/examples/advanced-patterns/context-compaction.ts +223 -0
  150. package/examples/advanced-patterns/streaming-responses.ts +85 -7
  151. package/examples/tools/enhanced-tool-metadata.ts +268 -0
  152. package/examples/tools/streaming-tool-execution.ts +283 -0
  153. package/package.json +1 -1
  154. package/src/core/Agent.ts +58 -2
  155. package/src/core/BatchPromptBuilder.ts +14 -11
  156. package/src/core/CompactionEngine.ts +318 -0
  157. package/src/core/PromptComposer.ts +261 -141
  158. package/src/core/PromptSectionCache.ts +136 -0
  159. package/src/core/ResponseEngine.ts +9 -6
  160. package/src/core/ResponseModal.ts +81 -20
  161. package/src/core/RoutingEngine.ts +13 -2
  162. package/src/core/SessionManager.ts +19 -0
  163. package/src/core/StreamingToolExecutor.ts +572 -0
  164. package/src/core/ToolManager.ts +151 -41
  165. package/src/index.ts +14 -0
  166. package/src/providers/AnthropicProvider.ts +11 -12
  167. package/src/providers/GeminiProvider.ts +83 -52
  168. package/src/providers/OpenAIProvider.ts +21 -13
  169. package/src/providers/OpenRouterProvider.ts +13 -13
  170. package/src/types/agent.ts +45 -0
  171. package/src/types/compaction.ts +52 -0
  172. package/src/types/index.ts +35 -14
  173. package/src/types/tool.ts +108 -0
@@ -12,10 +12,14 @@ Complete API documentation for `@falai/agent`. This framework provides a strongl
12
12
  - [RoutingEngine](#routingengine)
13
13
  - [ResponseEngine](#responseengine)
14
14
  - [PromptComposer](#promptcomposer)
15
+ - [StreamingToolExecutor](#streamingtoolexecutor)
16
+ - [CompactionEngine](#compactionengine)
17
+ - [PromptSectionCache](#promptsectioncache)
15
18
 
16
19
  - [AI Providers](#ai-providers)
17
20
  - [Persistence Adapters](#persistence-adapters)
18
21
  - [Types & Interfaces](#types--interfaces)
22
+ - [EnhancedTool](#enhancedtool)
19
23
  - [Utilities](#utilities)
20
24
 
21
25
  ---
@@ -602,6 +606,119 @@ build(): Promise<string>
602
606
 
603
607
 
604
608
 
609
+ ---
610
+
611
+ ### StreamingToolExecutor
612
+
613
+ Executes tools as they arrive from the LLM stream with concurrency control, abort handling, and ordered result yielding.
614
+
615
+ #### Constructor
616
+
617
+ ```typescript
618
+ new StreamingToolExecutor<TContext, TData>(
619
+ toolContext: ToolContext<TContext, TData>,
620
+ options?: {
621
+ maxParallel?: number; // default: 10
622
+ signal?: AbortSignal;
623
+ }
624
+ )
625
+ ```
626
+
627
+ #### Methods
628
+
629
+ ```typescript
630
+ addTool(toolCall: ToolCallRequest, tool: EnhancedTool<TContext, TData>): void
631
+ ```
632
+
633
+ Queue a tool for execution. Concurrency safety is evaluated once at queue time.
634
+
635
+ ```typescript
636
+ getCompletedResults(): Generator<ToolExecutionUpdate<TData>>
637
+ ```
638
+
639
+ Synchronous generator yielding available results in request order.
640
+
641
+ ```typescript
642
+ getRemainingResults(): AsyncGenerator<ToolExecutionUpdate<TData>>
643
+ ```
644
+
645
+ Async generator yielding all results, waiting for pending tools.
646
+
647
+ ```typescript
648
+ discard(): void
649
+ getUpdatedContext(): TContext
650
+ hasUnfinishedTools(): boolean
651
+ ```
652
+
653
+ See [Streaming Execution Guide](../core/tools/streaming-execution.md) for detailed usage.
654
+
655
+ ---
656
+
657
+ ### CompactionEngine
658
+
659
+ Manages conversation history size through multi-layered compaction strategies.
660
+
661
+ #### Static Methods
662
+
663
+ ```typescript
664
+ CompactionEngine.estimateTokens(history: HistoryItem[]): number
665
+ CompactionEngine.applyToolResultBudget(history: HistoryItem[], maxCharsPerResult: number): HistoryItem[]
666
+ CompactionEngine.validateOptions(options: CompactionOptions): void
667
+ CompactionEngine.checkAndCompact(history: HistoryItem[], options: CompactionOptions): Promise<CompactionResult>
668
+ ```
669
+
670
+ See [Context Compaction Guide](../guides/context-compaction.md) for detailed usage.
671
+
672
+ ---
673
+
674
+ ### PromptSectionCache
675
+
676
+ Memoizes static prompt sections across turns, recomputing only dynamic sections per-turn. Integrates with `PromptComposer` for optimized prompt generation.
677
+
678
+ #### Constructor
679
+
680
+ ```typescript
681
+ new PromptSectionCache(config?: PromptCacheConfig)
682
+ ```
683
+
684
+ #### Configuration
685
+
686
+ ```typescript
687
+ interface PromptCacheConfig {
688
+ enabled?: boolean; // default: true
689
+ volatileKeys?: string[]; // keys that always recompute
690
+ }
691
+ ```
692
+
693
+ #### Methods
694
+
695
+ ```typescript
696
+ register(key: string, type: PromptSectionType, compute: () => string | null | Promise<string | null>): void
697
+ ```
698
+
699
+ Register a section as `'static'` (cached) or `'dynamic'` (recomputed every turn).
700
+
701
+ ```typescript
702
+ get(key: string): Promise<string | null>
703
+ ```
704
+
705
+ Get a section's value, using cache for static sections.
706
+
707
+ ```typescript
708
+ resolveAll(): Promise<(string | null)[]>
709
+ ```
710
+
711
+ Resolve all sections in registration order.
712
+
713
+ ```typescript
714
+ invalidate(key: string): void
715
+ invalidateAll(): void
716
+ ```
717
+
718
+ Invalidate a specific section or all sections.
719
+
720
+ See [Prompt Optimization Guide](../guides/prompt-optimization.md) for detailed usage.
721
+
605
722
  ---
606
723
 
607
724
  ## AI Providers
@@ -1077,6 +1194,29 @@ type ToolHandler<TContext, TArgs extends unknown[], TResult, TData> = (
1077
1194
  }>;
1078
1195
  ```
1079
1196
 
1197
+ ### EnhancedTool
1198
+
1199
+ Extends `Tool` with optional metadata for concurrency, permissions, validation, and result budgeting. See [EnhancedTool Reference](../core/tools/enhanced-tool.md) for full documentation.
1200
+
1201
+ ```typescript
1202
+ interface EnhancedTool<TContext, TData, TResult> extends Tool<TContext, TData, TResult> {
1203
+ isConcurrencySafe?(input?: Record<string, unknown>): boolean;
1204
+ isReadOnly?(input?: Record<string, unknown>): boolean;
1205
+ isDestructive?(input?: Record<string, unknown>): boolean;
1206
+ interruptBehavior?(): 'cancel' | 'block';
1207
+ maxResultSizeChars?: number;
1208
+ validateInput?(input: Record<string, unknown>, context: ToolContext<TContext, TData>): Promise<ToolValidationResult> | ToolValidationResult;
1209
+ checkPermissions?(input: Record<string, unknown>, context: ToolContext<TContext, TData>): Promise<ToolPermissionResult> | ToolPermissionResult;
1210
+ }
1211
+
1212
+ interface ToolValidationResult { valid: boolean; error?: string; correctedInput?: Record<string, unknown>; }
1213
+ interface ToolPermissionResult { allowed: boolean; reason?: string; canOverride?: boolean; }
1214
+ interface ToolCallRequest { id: string; toolName: string; arguments: Record<string, unknown>; }
1215
+ interface ToolExecutionUpdate<TData> { toolCallId: string; result?: ToolExecutionResult; progress?: string; contextUpdate?: Record<string, unknown>; dataUpdate?: Partial<TData>; }
1216
+ interface CompactionOptions { maxTokens: number; compactionThreshold: number; preserveRecentCount: number; maxToolResultChars: number; provider: AiProvider; }
1217
+ interface CompactionResult<TData> { history: HistoryItem[]; strategy: 'none' | 'tool_result_budget' | 'micro_compact' | 'auto_compact'; estimatedTokens: number; messagesCompacted: number; summary?: string; }
1218
+ ```
1219
+
1080
1220
  ### AI Provider Types
1081
1221
 
1082
1222
  ```typescript
@@ -0,0 +1,186 @@
1
+ # EnhancedTool Interface
2
+
3
+ `EnhancedTool` extends the existing `Tool` interface with optional metadata for concurrency control, permission gating, input validation, and result size management. All additional methods are optional — plain `Tool` objects remain fully compatible.
4
+
5
+ ## Interface
6
+
7
+ ```typescript
8
+ interface EnhancedTool<TContext = any, TData = any, TResult = any>
9
+ extends Tool<TContext, TData, TResult> {
10
+
11
+ // Concurrency & safety
12
+ isConcurrencySafe?(input?: Record<string, unknown>): boolean;
13
+ isReadOnly?(input?: Record<string, unknown>): boolean;
14
+ isDestructive?(input?: Record<string, unknown>): boolean;
15
+
16
+ // Execution control
17
+ interruptBehavior?(): 'cancel' | 'block';
18
+ maxResultSizeChars?: number;
19
+
20
+ // Validation & permissions
21
+ validateInput?(
22
+ input: Record<string, unknown>,
23
+ context: ToolContext<TContext, TData>
24
+ ): Promise<ToolValidationResult> | ToolValidationResult;
25
+
26
+ checkPermissions?(
27
+ input: Record<string, unknown>,
28
+ context: ToolContext<TContext, TData>
29
+ ): Promise<ToolPermissionResult> | ToolPermissionResult;
30
+ }
31
+ ```
32
+
33
+ ## Methods & Properties
34
+
35
+ ### isConcurrencySafe
36
+
37
+ Returns `true` if this tool can safely run in parallel with other concurrency-safe tools. The `StreamingToolExecutor` evaluates this once at queue time and caches the result.
38
+
39
+ Default (when absent): `false` — the tool runs serially.
40
+
41
+ ```typescript
42
+ const listFiles: EnhancedTool = {
43
+ id: "list-files",
44
+ name: "list_files",
45
+ description: "List files in a directory",
46
+ handler: async (ctx, args) => { /* ... */ },
47
+ isConcurrencySafe: () => true,
48
+ };
49
+ ```
50
+
51
+ The method receives the tool's input arguments, so concurrency safety can be input-dependent:
52
+
53
+ ```typescript
54
+ isConcurrencySafe: (input) => {
55
+ // Safe for read paths, not safe for write paths
56
+ return input?.mode === "read";
57
+ },
58
+ ```
59
+
60
+ ### isReadOnly / isDestructive
61
+
62
+ Informational metadata. `isReadOnly` indicates the tool has no side effects; `isDestructive` indicates irreversible operations. Both default to `false` when absent.
63
+
64
+ ```typescript
65
+ isReadOnly: () => true,
66
+ isDestructive: () => false,
67
+ ```
68
+
69
+ ### interruptBehavior
70
+
71
+ Controls how the tool responds to abort signals (sibling failure or parent cancellation):
72
+
73
+ - `'cancel'` — immediately abort the tool
74
+ - `'block'` — allow the tool to finish (default when absent)
75
+
76
+ ```typescript
77
+ interruptBehavior: () => "cancel",
78
+ ```
79
+
80
+ ### maxResultSizeChars
81
+
82
+ Maximum characters for the tool result. Results exceeding this limit are truncated with a notice like `[Truncated: 12000 chars total, showing first 5000]`.
83
+
84
+ ```typescript
85
+ maxResultSizeChars: 50_000,
86
+ ```
87
+
88
+ ### validateInput
89
+
90
+ Called before the tool handler. If it returns `{ valid: false }`, the handler is never invoked and a validation error is returned instead.
91
+
92
+ ```typescript
93
+ validateInput: async (input, ctx) => {
94
+ if (!input.resourceId || typeof input.resourceId !== "string") {
95
+ return { valid: false, error: "resourceId must be a non-empty string" };
96
+ }
97
+ return { valid: true };
98
+ },
99
+ ```
100
+
101
+ The return type:
102
+
103
+ ```typescript
104
+ interface ToolValidationResult {
105
+ valid: boolean;
106
+ error?: string;
107
+ correctedInput?: Record<string, unknown>;
108
+ }
109
+ ```
110
+
111
+ ### checkPermissions
112
+
113
+ Called before the tool handler (after validation). If it returns `{ allowed: false }`, the handler is never invoked and a permission error is returned.
114
+
115
+ ```typescript
116
+ checkPermissions: async (input, ctx) => {
117
+ const role = (ctx.context as any)?.userRole;
118
+ if (role !== "admin") {
119
+ return { allowed: false, reason: "Only admins can delete resources", canOverride: false };
120
+ }
121
+ return { allowed: true };
122
+ },
123
+ ```
124
+
125
+ The return type:
126
+
127
+ ```typescript
128
+ interface ToolPermissionResult {
129
+ allowed: boolean;
130
+ reason?: string;
131
+ canOverride?: boolean;
132
+ }
133
+ ```
134
+
135
+ ## Full Example
136
+
137
+ ```typescript
138
+ const deleteTool: EnhancedTool = {
139
+ id: "delete-resource",
140
+ name: "delete_resource",
141
+ description: "Delete a resource permanently",
142
+ parameters: {
143
+ type: "object",
144
+ properties: { resourceId: { type: "string" } },
145
+ required: ["resourceId"],
146
+ },
147
+ handler: async (ctx, args) => {
148
+ await deleteResource(args?.resourceId as string);
149
+ return { success: true };
150
+ },
151
+
152
+ isConcurrencySafe: () => false,
153
+ isReadOnly: () => false,
154
+ isDestructive: () => true,
155
+ interruptBehavior: () => "block",
156
+ maxResultSizeChars: 500,
157
+
158
+ validateInput: async (input) => {
159
+ if (!input.resourceId || typeof input.resourceId !== "string") {
160
+ return { valid: false, error: "resourceId must be a non-empty string" };
161
+ }
162
+ return { valid: true };
163
+ },
164
+
165
+ checkPermissions: async (input, ctx) => {
166
+ const role = (ctx.context as any)?.userRole;
167
+ if (role !== "admin") {
168
+ return { allowed: false, reason: "Only admins can delete resources" };
169
+ }
170
+ return { allowed: true };
171
+ },
172
+ };
173
+ ```
174
+
175
+ ## Backward Compatibility
176
+
177
+ Plain `Tool` objects without any `EnhancedTool` methods work exactly as before. The framework applies these defaults:
178
+
179
+ | Property | Default |
180
+ |---|---|
181
+ | `isConcurrencySafe` | `false` |
182
+ | `isReadOnly` | `false` |
183
+ | `isDestructive` | `false` |
184
+ | `interruptBehavior` | `'block'` |
185
+ | `validateInput` | skipped |
186
+ | `checkPermissions` | skipped |
@@ -0,0 +1,161 @@
1
+ # Streaming Tool Execution
2
+
3
+ The `StreamingToolExecutor` executes tools as they arrive from the LLM stream rather than waiting for the full response. It provides concurrency control, abort handling, and ordered result yielding.
4
+
5
+ ## Overview
6
+
7
+ - Tools begin executing immediately as they are parsed from the LLM stream
8
+ - Read-only (concurrency-safe) tools run in parallel
9
+ - Write (non-concurrency-safe) tools run serially with exclusive access
10
+ - Results are always yielded in the original request order
11
+ - Progress messages bypass ordering and are delivered immediately
12
+
13
+ ## Concurrency Control
14
+
15
+ The executor enforces a strict invariant at all times:
16
+
17
+ > Either **all** executing tools have `isConcurrencySafe === true`, **or** exactly **one** tool is executing with `isConcurrencySafe === false`.
18
+
19
+ Tools without the `isConcurrencySafe` method default to `false` (serial execution), preserving backward compatibility with plain `Tool` objects.
20
+
21
+ A configurable `maxParallel` limit (default: 10) caps the number of concurrently executing tools regardless of concurrency safety.
22
+
23
+ ### Example: Mixed Read/Write Tools
24
+
25
+ ```typescript
26
+ import { Agent, EnhancedTool } from "@falai/agent";
27
+
28
+ const readFile: EnhancedTool = {
29
+ id: "read-file",
30
+ name: "read_file",
31
+ description: "Read a file from disk",
32
+ parameters: {
33
+ type: "object",
34
+ properties: { path: { type: "string" } },
35
+ required: ["path"],
36
+ },
37
+ handler: async (ctx, args) => {
38
+ const content = await fs.readFile(args?.path as string, "utf-8");
39
+ return { data: content, success: true };
40
+ },
41
+ isConcurrencySafe: () => true, // safe to run in parallel
42
+ isReadOnly: () => true,
43
+ maxResultSizeChars: 50_000,
44
+ };
45
+
46
+ const writeFile: EnhancedTool = {
47
+ id: "write-file",
48
+ name: "write_file",
49
+ description: "Write content to a file",
50
+ parameters: {
51
+ type: "object",
52
+ properties: {
53
+ path: { type: "string" },
54
+ content: { type: "string" },
55
+ },
56
+ required: ["path", "content"],
57
+ },
58
+ handler: async (ctx, args) => {
59
+ await fs.writeFile(args?.path as string, args?.content as string);
60
+ return { success: true };
61
+ },
62
+ isConcurrencySafe: () => false, // must run exclusively
63
+ interruptBehavior: () => "block",
64
+ };
65
+
66
+ const agent = new Agent({
67
+ name: "CodeAssistant",
68
+ provider: anthropicProvider,
69
+ tools: [readFile, writeFile],
70
+ });
71
+ ```
72
+
73
+ When the LLM requests `read_file` three times followed by `write_file`, the three reads execute in parallel. Once all reads complete, the write executes alone.
74
+
75
+ ## Abort Behavior
76
+
77
+ ### Sibling Abort
78
+
79
+ When a tool in a concurrent batch fails, all sibling tools in the same batch receive an abort signal. Each tool's `interruptBehavior` determines the response:
80
+
81
+ - `'cancel'` — tool is immediately aborted
82
+ - `'block'` (default) — tool is allowed to finish
83
+
84
+ ### Parent AbortSignal
85
+
86
+ A parent `AbortSignal` can be passed via `StreamingToolExecutorOptions`. When it fires:
87
+
88
+ 1. Tools with `interruptBehavior() === 'cancel'` are aborted immediately
89
+ 2. Tools with `interruptBehavior() === 'block'` complete normally
90
+ 3. No new queued tools are started
91
+
92
+ ```typescript
93
+ const controller = new AbortController();
94
+
95
+ // Pass signal through agent options or directly to the executor
96
+ for await (const chunk of agent.respondStream({
97
+ history,
98
+ signal: controller.signal,
99
+ })) {
100
+ process.stdout.write(chunk.delta);
101
+ }
102
+
103
+ // Cancel from user action
104
+ controller.abort();
105
+ ```
106
+
107
+ ## Progress Reporting
108
+
109
+ Tools can emit progress messages during execution. These are yielded immediately to the caller without being buffered behind result ordering.
110
+
111
+ ```typescript
112
+ for await (const chunk of agent.respondStream({ history })) {
113
+ if (chunk.toolExecution?.progress) {
114
+ console.log(`[progress] ${chunk.toolExecution.toolCallId}: ${chunk.toolExecution.progress}`);
115
+ }
116
+ if (chunk.toolExecution?.result) {
117
+ console.log(`[result] ${chunk.toolExecution.toolCallId}: done`);
118
+ }
119
+ process.stdout.write(chunk.delta);
120
+ }
121
+ ```
122
+
123
+ ## Result Ordering
124
+
125
+ Results are always yielded in the same order as the original tool call requests, regardless of actual completion order. If tool B finishes before tool A, tool B's result is buffered until tool A's result is yielded first.
126
+
127
+ ## API Reference
128
+
129
+ ### Constructor
130
+
131
+ ```typescript
132
+ new StreamingToolExecutor<TContext, TData>(
133
+ toolContext: ToolContext<TContext, TData>,
134
+ options?: {
135
+ maxParallel?: number; // default: 10
136
+ signal?: AbortSignal; // parent abort signal
137
+ }
138
+ )
139
+ ```
140
+
141
+ ### Methods
142
+
143
+ | Method | Description |
144
+ |---|---|
145
+ | `addTool(toolCall, tool)` | Queue a tool for execution. Concurrency safety is evaluated once at queue time. |
146
+ | `getCompletedResults()` | Synchronous generator yielding available results in request order. |
147
+ | `getRemainingResults()` | Async generator yielding all results (waits for pending tools). |
148
+ | `discard()` | Stop processing new queued tools. Running tools continue per their `interruptBehavior`. |
149
+ | `getUpdatedContext()` | Return accumulated context updates from completed tools. |
150
+ | `hasUnfinishedTools()` | `true` if any tools are still queued or executing. |
151
+
152
+ ### Default Behaviors for Plain `Tool` Objects
153
+
154
+ | Property | Default |
155
+ |---|---|
156
+ | `isConcurrencySafe` | `false` |
157
+ | `isReadOnly` | `false` |
158
+ | `isDestructive` | `false` |
159
+ | `interruptBehavior` | `'block'` |
160
+
161
+ Plain `Tool` objects work without modification — they execute serially and are allowed to complete on abort.
@@ -0,0 +1,96 @@
1
+ # Context Compaction
2
+
3
+ The `CompactionEngine` automatically manages conversation history size when approaching token limits. It applies multi-layered strategies in order of cost, from cheap truncation to LLM-powered summarization.
4
+
5
+ ## Compaction Strategies
6
+
7
+ Strategies are applied in order until the history fits within the token budget:
8
+
9
+ | Strategy | Cost | Description |
10
+ |---|---|---|
11
+ | `none` | Free | History is under threshold — no action taken |
12
+ | `tool_result_budget` | Free | Truncate oversized tool results with a notice |
13
+ | `micro_compact` | Free | Collapse whitespace in verbose tool outputs |
14
+ | `auto_compact` | LLM call | Summarize old messages via the configured AI provider |
15
+
16
+ If the LLM summarization fails, the engine falls back to aggressive truncation (removing oldest messages) and logs a warning. The next compaction attempt will retry summarization.
17
+
18
+ ## Configuration
19
+
20
+ Compaction is configured at the agent level via the `compaction` option:
21
+
22
+ ```typescript
23
+ import { Agent } from "@falai/agent";
24
+
25
+ const agent = new Agent({
26
+ name: "LongConversationAgent",
27
+ provider: anthropicProvider,
28
+ compaction: {
29
+ maxTokens: 100_000,
30
+ compactionThreshold: 0.8, // trigger at 80% of budget
31
+ preserveRecentCount: 10, // always keep last 10 messages
32
+ maxToolResultChars: 5_000, // truncate tool results over 5k chars
33
+ provider: anthropicProvider, // provider for LLM summarization
34
+ },
35
+ });
36
+ ```
37
+
38
+ ### CompactionOptions
39
+
40
+ | Option | Type | Constraint | Description |
41
+ |---|---|---|---|
42
+ | `maxTokens` | `number` | > 0 | Maximum token budget for the conversation |
43
+ | `compactionThreshold` | `number` | 0.5 – 0.95 | Ratio at which compaction triggers |
44
+ | `preserveRecentCount` | `number` | ≥ 2 | Recent messages that are never modified |
45
+ | `maxToolResultChars` | `number` | > 0 | Per-tool-result character limit before truncation |
46
+ | `provider` | `AiProvider` | — | Provider used for LLM summarization |
47
+
48
+ Invalid options throw at construction time.
49
+
50
+ ## How It Works
51
+
52
+ When the `SessionManager` detects that estimated tokens exceed `maxTokens * compactionThreshold`, the `CompactionEngine` runs:
53
+
54
+ 1. **Token estimation** — character-based heuristic (~4 chars/token), no external tokenizer needed
55
+ 2. **Tool result budget** — truncate any tool result exceeding `maxToolResultChars`, append a notice like `[Truncated: 12000 chars total, showing first 5000]`
56
+ 3. **Micro-compact** — collapse whitespace in tool outputs for the compactable portion of history
57
+ 4. **Auto-compact** — summarize old messages via the AI provider, replacing them with a `[Conversation Summary]` system message
58
+
59
+ The last `preserveRecentCount` messages are never modified or removed by any strategy.
60
+
61
+ ## Manual Compaction
62
+
63
+ You can also use the `CompactionEngine` directly:
64
+
65
+ ```typescript
66
+ import { CompactionEngine } from "@falai/agent";
67
+
68
+ const result = await CompactionEngine.checkAndCompact(history, {
69
+ maxTokens: 100_000,
70
+ compactionThreshold: 0.8,
71
+ preserveRecentCount: 10,
72
+ maxToolResultChars: 5_000,
73
+ provider: anthropicProvider,
74
+ });
75
+
76
+ console.log(result.strategy); // 'none' | 'tool_result_budget' | 'micro_compact' | 'auto_compact'
77
+ console.log(result.estimatedTokens); // tokens after compaction
78
+ console.log(result.messagesCompacted);
79
+ ```
80
+
81
+ ### Standalone Utilities
82
+
83
+ ```typescript
84
+ // Estimate tokens for a history
85
+ const tokens = CompactionEngine.estimateTokens(history);
86
+
87
+ // Truncate tool results only
88
+ const budgeted = CompactionEngine.applyToolResultBudget(history, 5_000);
89
+ ```
90
+
91
+ ## Key Properties
92
+
93
+ - **Idempotent** — compacting already-compacted history with the same options produces the same result
94
+ - **Deterministic estimation** — `estimateTokens` always returns the same value for the same input
95
+ - **Preservation guarantee** — the last `preserveRecentCount` messages are never touched
96
+ - **Graceful degradation** — LLM failure falls back to truncation, never crashes