@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.
- package/README.md +9 -0
- package/dist/cjs/core/Agent.d.ts +17 -1
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +47 -0
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/BatchPromptBuilder.d.ts +3 -0
- package/dist/cjs/core/BatchPromptBuilder.d.ts.map +1 -1
- package/dist/cjs/core/BatchPromptBuilder.js +4 -1
- package/dist/cjs/core/BatchPromptBuilder.js.map +1 -1
- package/dist/cjs/core/CompactionEngine.d.ts +65 -0
- package/dist/cjs/core/CompactionEngine.d.ts.map +1 -0
- package/dist/cjs/core/CompactionEngine.js +251 -0
- package/dist/cjs/core/CompactionEngine.js.map +1 -0
- package/dist/cjs/core/PromptComposer.d.ts +8 -1
- package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
- package/dist/cjs/core/PromptComposer.js +238 -126
- package/dist/cjs/core/PromptComposer.js.map +1 -1
- package/dist/cjs/core/PromptSectionCache.d.ts +57 -0
- package/dist/cjs/core/PromptSectionCache.d.ts.map +1 -0
- package/dist/cjs/core/PromptSectionCache.js +108 -0
- package/dist/cjs/core/PromptSectionCache.js.map +1 -0
- package/dist/cjs/core/ResponseEngine.d.ts +3 -2
- package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
- package/dist/cjs/core/ResponseEngine.js +8 -8
- package/dist/cjs/core/ResponseEngine.js.map +1 -1
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +120 -70
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/ResponsePipeline.d.ts +2 -1
- package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/core/ResponsePipeline.js +17 -19
- package/dist/cjs/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/core/RoutingEngine.d.ts +10 -0
- package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
- package/dist/cjs/core/RoutingEngine.js +5 -4
- package/dist/cjs/core/RoutingEngine.js.map +1 -1
- package/dist/cjs/core/SessionManager.d.ts.map +1 -1
- package/dist/cjs/core/SessionManager.js +20 -0
- package/dist/cjs/core/SessionManager.js.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.d.ts +142 -0
- package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -0
- package/dist/cjs/core/StreamingToolExecutor.js +455 -0
- package/dist/cjs/core/StreamingToolExecutor.js.map +1 -0
- package/dist/cjs/core/ToolManager.d.ts +18 -1
- package/dist/cjs/core/ToolManager.d.ts.map +1 -1
- package/dist/cjs/core/ToolManager.js +91 -0
- package/dist/cjs/core/ToolManager.js.map +1 -1
- package/dist/cjs/index.d.ts +5 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +8 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.d.ts +7 -0
- package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.js +109 -19
- package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts +32 -0
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +160 -53
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts +5 -0
- package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.js +65 -18
- package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.d.ts +5 -0
- package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.js +57 -18
- package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +44 -0
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/agent.js.map +1 -1
- package/dist/cjs/types/ai.d.ts +2 -2
- package/dist/cjs/types/ai.d.ts.map +1 -1
- package/dist/cjs/types/compaction.d.ts +50 -0
- package/dist/cjs/types/compaction.d.ts.map +1 -0
- package/dist/cjs/types/compaction.js +6 -0
- package/dist/cjs/types/compaction.js.map +1 -0
- package/dist/cjs/types/index.d.ts +4 -2
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/tool.d.ts +84 -0
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/core/Agent.d.ts +17 -1
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +47 -0
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/BatchPromptBuilder.d.ts +3 -0
- package/dist/core/BatchPromptBuilder.d.ts.map +1 -1
- package/dist/core/BatchPromptBuilder.js +4 -1
- package/dist/core/BatchPromptBuilder.js.map +1 -1
- package/dist/core/CompactionEngine.d.ts +65 -0
- package/dist/core/CompactionEngine.d.ts.map +1 -0
- package/dist/core/CompactionEngine.js +244 -0
- package/dist/core/CompactionEngine.js.map +1 -0
- package/dist/core/PromptComposer.d.ts +8 -1
- package/dist/core/PromptComposer.d.ts.map +1 -1
- package/dist/core/PromptComposer.js +238 -126
- package/dist/core/PromptComposer.js.map +1 -1
- package/dist/core/PromptSectionCache.d.ts +57 -0
- package/dist/core/PromptSectionCache.d.ts.map +1 -0
- package/dist/core/PromptSectionCache.js +104 -0
- package/dist/core/PromptSectionCache.js.map +1 -0
- package/dist/core/ResponseEngine.d.ts +3 -2
- package/dist/core/ResponseEngine.d.ts.map +1 -1
- package/dist/core/ResponseEngine.js +8 -8
- package/dist/core/ResponseEngine.js.map +1 -1
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +121 -71
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/ResponsePipeline.d.ts +2 -1
- package/dist/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/core/ResponsePipeline.js +18 -20
- package/dist/core/ResponsePipeline.js.map +1 -1
- package/dist/core/RoutingEngine.d.ts +10 -0
- package/dist/core/RoutingEngine.d.ts.map +1 -1
- package/dist/core/RoutingEngine.js +6 -5
- package/dist/core/RoutingEngine.js.map +1 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +17 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/StreamingToolExecutor.d.ts +142 -0
- package/dist/core/StreamingToolExecutor.d.ts.map +1 -0
- package/dist/core/StreamingToolExecutor.js +448 -0
- package/dist/core/StreamingToolExecutor.js.map +1 -0
- package/dist/core/ToolManager.d.ts +18 -1
- package/dist/core/ToolManager.d.ts.map +1 -1
- package/dist/core/ToolManager.js +91 -0
- package/dist/core/ToolManager.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts +7 -0
- package/dist/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/providers/AnthropicProvider.js +109 -19
- package/dist/providers/AnthropicProvider.js.map +1 -1
- package/dist/providers/GeminiProvider.d.ts +32 -0
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +160 -53
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts +5 -0
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +65 -18
- package/dist/providers/OpenAIProvider.js.map +1 -1
- package/dist/providers/OpenRouterProvider.d.ts +5 -0
- package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/providers/OpenRouterProvider.js +57 -18
- package/dist/providers/OpenRouterProvider.js.map +1 -1
- package/dist/types/agent.d.ts +44 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/agent.js.map +1 -1
- package/dist/types/ai.d.ts +2 -2
- package/dist/types/ai.d.ts.map +1 -1
- package/dist/types/compaction.d.ts +50 -0
- package/dist/types/compaction.d.ts.map +1 -0
- package/dist/types/compaction.js +5 -0
- package/dist/types/compaction.js.map +1 -0
- package/dist/types/index.d.ts +4 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/tool.d.ts +84 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/docs/api/overview.md +140 -0
- package/docs/core/tools/enhanced-tool.md +186 -0
- package/docs/core/tools/streaming-execution.md +161 -0
- package/docs/guides/context-compaction.md +96 -0
- package/docs/guides/prompt-optimization.md +164 -0
- package/examples/advanced-patterns/context-compaction.ts +223 -0
- package/examples/advanced-patterns/streaming-responses.ts +85 -7
- package/examples/tools/enhanced-tool-metadata.ts +268 -0
- package/examples/tools/streaming-tool-execution.ts +283 -0
- package/package.json +1 -1
- package/src/core/Agent.ts +58 -2
- package/src/core/BatchPromptBuilder.ts +4 -1
- package/src/core/CompactionEngine.ts +318 -0
- package/src/core/PromptComposer.ts +259 -156
- package/src/core/PromptSectionCache.ts +136 -0
- package/src/core/ResponseEngine.ts +7 -11
- package/src/core/ResponseModal.ts +133 -83
- package/src/core/ResponsePipeline.ts +22 -22
- package/src/core/RoutingEngine.ts +16 -5
- package/src/core/SessionManager.ts +19 -0
- package/src/core/StreamingToolExecutor.ts +572 -0
- package/src/core/ToolManager.ts +151 -41
- package/src/index.ts +14 -0
- package/src/providers/AnthropicProvider.ts +121 -24
- package/src/providers/GeminiProvider.ts +174 -54
- package/src/providers/OpenAIProvider.ts +77 -25
- package/src/providers/OpenRouterProvider.ts +68 -25
- package/src/types/agent.ts +45 -0
- package/src/types/ai.ts +2 -2
- package/src/types/compaction.ts +52 -0
- package/src/types/index.ts +35 -14
- package/src/types/tool.ts +108 -0
package/docs/api/overview.md
CHANGED
|
@@ -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
|