@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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Prompt Optimization
|
|
2
|
+
|
|
3
|
+
The `PromptSectionCache` optimizes prompt generation by memoizing static sections across turns and recomputing only dynamic sections per-turn. Combined with the native history format change, this reduces redundant computation and token usage.
|
|
4
|
+
|
|
5
|
+
## Section Types
|
|
6
|
+
|
|
7
|
+
Prompt sections are classified as either static or dynamic:
|
|
8
|
+
|
|
9
|
+
| Type | Behavior | Examples |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `static` | Cached after first computation, reused across turns | Agent identity, glossary, knowledge base, route descriptions, scoring rules |
|
|
12
|
+
| `dynamic` | Recomputed on every `resolveAll()` call | Instructions, directives, available tools, guidelines |
|
|
13
|
+
|
|
14
|
+
Static sections only change when the underlying state changes (context update, session switch, route change). Dynamic sections depend on per-turn state and are always fresh.
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
Prompt caching is enabled by default. Configure it via the `promptCache` option on the agent:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { Agent } from "@falai/agent";
|
|
22
|
+
|
|
23
|
+
const agent = new Agent({
|
|
24
|
+
name: "MyAgent",
|
|
25
|
+
provider: anthropicProvider,
|
|
26
|
+
promptCache: {
|
|
27
|
+
enabled: true, // default: true
|
|
28
|
+
volatileKeys: [], // keys that always recompute, even if registered as static
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### PromptCacheConfig
|
|
34
|
+
|
|
35
|
+
| Option | Type | Default | Description |
|
|
36
|
+
|---|---|---|---|
|
|
37
|
+
| `enabled` | `boolean` | `true` | Enable/disable section memoization |
|
|
38
|
+
| `volatileKeys` | `string[]` | `[]` | Section keys forced to recompute every turn |
|
|
39
|
+
|
|
40
|
+
Set `enabled: false` to disable caching entirely (useful for debugging):
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const agent = new Agent({
|
|
44
|
+
name: "DebugAgent",
|
|
45
|
+
provider: anthropicProvider,
|
|
46
|
+
promptCache: { enabled: false },
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## PromptSectionCache API
|
|
51
|
+
|
|
52
|
+
### `register(key, type, compute)`
|
|
53
|
+
|
|
54
|
+
Register a section with a unique key, type (`'static'` or `'dynamic'`), and a compute function.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
cache.register("agentMeta", "static", async () => {
|
|
58
|
+
return "## Agent Identity\nYou are MyAgent.";
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
cache.register("directives", "dynamic", () => {
|
|
62
|
+
return "## Directives\n- Address the user's question";
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `get(key)`
|
|
67
|
+
|
|
68
|
+
Retrieve a section's value. Static sections return the cached value when available; dynamic sections always recompute.
|
|
69
|
+
|
|
70
|
+
### `resolveAll()`
|
|
71
|
+
|
|
72
|
+
Resolve all registered sections in registration order. Returns `(string | null)[]`.
|
|
73
|
+
|
|
74
|
+
### `invalidate(key)`
|
|
75
|
+
|
|
76
|
+
Force a specific section to recompute on the next `resolveAll()` call.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
cache.invalidate("knowledgeBase");
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `invalidateAll()`
|
|
83
|
+
|
|
84
|
+
Force all sections to recompute. Called automatically on session change or `/clear`.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
cache.invalidateAll();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Automatic Cache Invalidation
|
|
91
|
+
|
|
92
|
+
The framework invalidates relevant caches automatically when state changes:
|
|
93
|
+
|
|
94
|
+
| Event | Sections Invalidated |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `agent.updateContext()` | `agentMeta`, `knowledgeBase` |
|
|
97
|
+
| Session change / clear | All sections (`invalidateAll()`) |
|
|
98
|
+
| Route switch | Route-dependent sections (active routes, route rules, route knowledge base) |
|
|
99
|
+
|
|
100
|
+
No manual cache management is needed for typical usage.
|
|
101
|
+
|
|
102
|
+
## Native History Format
|
|
103
|
+
|
|
104
|
+
History is now sent as native provider messages via `GenerateMessageInput.history` instead of being JSON-serialized into the system prompt. This saves tokens (no JSON overhead) and lets providers optimize for their native message format.
|
|
105
|
+
|
|
106
|
+
### Migration from `addInteractionHistory()`
|
|
107
|
+
|
|
108
|
+
The `PromptComposer.addInteractionHistory()` method is deprecated. If you were calling it directly:
|
|
109
|
+
|
|
110
|
+
**Before:**
|
|
111
|
+
```typescript
|
|
112
|
+
const pc = new PromptComposer(context);
|
|
113
|
+
await pc.addAgentMeta(agentOptions);
|
|
114
|
+
await pc.addInteractionHistory(history); // embedded in prompt string
|
|
115
|
+
await pc.addLastMessage(lastMessage);
|
|
116
|
+
const prompt = await pc.build();
|
|
117
|
+
|
|
118
|
+
const response = await provider.generateMessage({ prompt, history: [] });
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**After:**
|
|
122
|
+
```typescript
|
|
123
|
+
const pc = new PromptComposer(context, cache);
|
|
124
|
+
await pc.addAgentMeta(agentOptions);
|
|
125
|
+
// No addInteractionHistory() ā history flows natively
|
|
126
|
+
const prompt = await pc.build();
|
|
127
|
+
|
|
128
|
+
const response = await provider.generateMessage({ prompt, history });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The `addInteractionHistory()` method still works for backward compatibility but is marked `@deprecated` and will be removed in a future version.
|
|
132
|
+
|
|
133
|
+
## Manual Cache Usage
|
|
134
|
+
|
|
135
|
+
You can use `PromptSectionCache` directly for custom prompt pipelines:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { PromptSectionCache } from "@falai/agent";
|
|
139
|
+
|
|
140
|
+
const cache = new PromptSectionCache({ enabled: true });
|
|
141
|
+
|
|
142
|
+
cache.register("identity", "static", () => "You are a helpful assistant.");
|
|
143
|
+
cache.register("tools", "dynamic", () => "Available: search, calculate");
|
|
144
|
+
|
|
145
|
+
// First call: both sections computed
|
|
146
|
+
const sections1 = await cache.resolveAll(); // ["You are a helpful assistant.", "Available: search, calculate"]
|
|
147
|
+
|
|
148
|
+
// Second call: identity served from cache, tools recomputed
|
|
149
|
+
const sections2 = await cache.resolveAll();
|
|
150
|
+
|
|
151
|
+
// Invalidate a specific section
|
|
152
|
+
cache.invalidate("identity");
|
|
153
|
+
|
|
154
|
+
// Next call: identity recomputed, tools recomputed (always)
|
|
155
|
+
const sections3 = await cache.resolveAll();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Key Properties
|
|
159
|
+
|
|
160
|
+
- **Static sections cache** ā computed once per session, reused across turns until invalidated
|
|
161
|
+
- **Dynamic sections recompute** ā always fresh on every `resolveAll()` call
|
|
162
|
+
- **Automatic invalidation** ā context updates, session changes, and route switches trigger targeted invalidation
|
|
163
|
+
- **Configurable** ā disable caching or mark specific keys as volatile
|
|
164
|
+
- **Backward compatible** ā `addInteractionHistory()` still works, just deprecated
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Compaction Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the CompactionEngine for managing conversation history size.
|
|
5
|
+
* Shows agent-level compaction config and how long conversations trigger
|
|
6
|
+
* automatic compaction through layered strategies.
|
|
7
|
+
*
|
|
8
|
+
* Key concepts:
|
|
9
|
+
* - Agent-level compaction configuration via `AgentCompactionConfig`
|
|
10
|
+
* - Token estimation using character-based heuristic (~4 chars/token)
|
|
11
|
+
* - Layered compaction strategies: tool_result_budget ā micro_compact ā auto_compact
|
|
12
|
+
* - Preservation of recent messages during compaction
|
|
13
|
+
* - Manual compaction via CompactionEngine API
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
Agent,
|
|
18
|
+
CompactionEngine,
|
|
19
|
+
GeminiProvider,
|
|
20
|
+
type HistoryItem,
|
|
21
|
+
type CompactionOptions,
|
|
22
|
+
} from "../../src/index";
|
|
23
|
+
|
|
24
|
+
// --- Agent-level compaction config ---
|
|
25
|
+
|
|
26
|
+
async function demonstrateAgentCompaction() {
|
|
27
|
+
console.log("=== Agent-Level Compaction Config ===\n");
|
|
28
|
+
|
|
29
|
+
const provider = new GeminiProvider({
|
|
30
|
+
apiKey: process.env.GEMINI_API_KEY || "demo-key",
|
|
31
|
+
model: "models/gemini-2.5-flash",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Compaction is configured at the agent level.
|
|
35
|
+
// The agent validates options on construction and wires the CompactionEngine
|
|
36
|
+
// into the SessionManager so compaction happens transparently.
|
|
37
|
+
const agent = new Agent({
|
|
38
|
+
name: "LongConversationAgent",
|
|
39
|
+
description: "An agent that handles long conversations gracefully",
|
|
40
|
+
provider,
|
|
41
|
+
compaction: {
|
|
42
|
+
maxTokens: 100_000,
|
|
43
|
+
compactionThreshold: 0.8, // compact at 80% of budget
|
|
44
|
+
preserveRecentCount: 10, // always keep last 10 messages
|
|
45
|
+
maxToolResultChars: 5_000, // truncate tool results over 5k chars
|
|
46
|
+
enabled: true,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log("Agent created with compaction config:");
|
|
51
|
+
console.log(" maxTokens: 100,000");
|
|
52
|
+
console.log(" compactionThreshold: 0.8 (triggers at 80k tokens)");
|
|
53
|
+
console.log(" preserveRecentCount: 10");
|
|
54
|
+
console.log(" maxToolResultChars: 5,000");
|
|
55
|
+
console.log();
|
|
56
|
+
console.log("Compaction runs automatically in SessionManager when history grows.");
|
|
57
|
+
console.log("No manual intervention needed for typical usage.\n");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// --- Manual CompactionEngine usage ---
|
|
61
|
+
|
|
62
|
+
async function demonstrateManualCompaction() {
|
|
63
|
+
console.log("=== Manual CompactionEngine Usage ===\n");
|
|
64
|
+
|
|
65
|
+
// Build a synthetic history with large tool results
|
|
66
|
+
const history: HistoryItem[] = [
|
|
67
|
+
{ role: "user", content: "Analyze the codebase for security issues." },
|
|
68
|
+
{ role: "assistant", content: "I'll scan the files for common vulnerabilities." },
|
|
69
|
+
{ role: "tool", tool_call_id: "tc_1", name: "scan_files", content: "x".repeat(20_000) },
|
|
70
|
+
{ role: "assistant", content: "Found some issues. Let me check more files." },
|
|
71
|
+
{ role: "tool", tool_call_id: "tc_2", name: "scan_files", content: "y".repeat(15_000) },
|
|
72
|
+
{ role: "user", content: "What about SQL injection?" },
|
|
73
|
+
{ role: "assistant", content: "Let me search for raw SQL queries." },
|
|
74
|
+
{ role: "tool", tool_call_id: "tc_3", name: "search_code", content: "z".repeat(10_000) },
|
|
75
|
+
{ role: "user", content: "Summarize the findings." },
|
|
76
|
+
{ role: "assistant", content: "Here is a summary of the security audit." },
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
// 1. Token estimation
|
|
80
|
+
const tokens = CompactionEngine.estimateTokens(history);
|
|
81
|
+
console.log(`Estimated tokens: ${tokens}`);
|
|
82
|
+
console.log(`Total messages: ${history.length}\n`);
|
|
83
|
+
|
|
84
|
+
// 2. Tool result budgeting (no LLM call needed)
|
|
85
|
+
const budgeted = CompactionEngine.applyToolResultBudget(history, 5_000);
|
|
86
|
+
const budgetedTokens = CompactionEngine.estimateTokens(budgeted);
|
|
87
|
+
console.log("After tool result budget (maxChars=5000):");
|
|
88
|
+
console.log(` Tokens: ${tokens} ā ${budgetedTokens}`);
|
|
89
|
+
|
|
90
|
+
for (let i = 0; i < budgeted.length; i++) {
|
|
91
|
+
if (budgeted[i].role === "tool") {
|
|
92
|
+
const truncated = budgeted[i].content.length < history[i].content.length;
|
|
93
|
+
console.log(` Message ${i} (tool): ${truncated ? "truncated" : "unchanged"} (${budgeted[i].content.length} chars)`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
console.log();
|
|
97
|
+
|
|
98
|
+
// 3. Full compaction with a mock provider
|
|
99
|
+
// In real usage you'd pass the agent's provider for LLM summarization.
|
|
100
|
+
// Here we show the layered strategy selection.
|
|
101
|
+
const mockProvider = {
|
|
102
|
+
generateMessage: async () => ({
|
|
103
|
+
content: "Security audit found 3 potential SQL injection points and 2 XSS vulnerabilities.",
|
|
104
|
+
toolCalls: [],
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const options: CompactionOptions = {
|
|
109
|
+
maxTokens: 5_000, // tight budget to force compaction
|
|
110
|
+
compactionThreshold: 0.8,
|
|
111
|
+
preserveRecentCount: 4,
|
|
112
|
+
maxToolResultChars: 2_000,
|
|
113
|
+
provider: mockProvider as any,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const result = await CompactionEngine.checkAndCompact(history, options);
|
|
117
|
+
|
|
118
|
+
console.log("Full compaction result:");
|
|
119
|
+
console.log(` Strategy: ${result.strategy}`);
|
|
120
|
+
console.log(` Estimated tokens: ${result.estimatedTokens}`);
|
|
121
|
+
console.log(` Messages compacted: ${result.messagesCompacted}`);
|
|
122
|
+
console.log(` History length: ${result.history.length} (was ${history.length})`);
|
|
123
|
+
if (result.summary) {
|
|
124
|
+
console.log(` Summary: "${result.summary}"`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// --- Demonstrating compaction strategies ---
|
|
129
|
+
|
|
130
|
+
async function demonstrateStrategies() {
|
|
131
|
+
console.log("\n=== Compaction Strategy Ladder ===\n");
|
|
132
|
+
|
|
133
|
+
const smallHistory: HistoryItem[] = [
|
|
134
|
+
{ role: "user", content: "Hello" },
|
|
135
|
+
{ role: "assistant", content: "Hi there!" },
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const mockProvider = {
|
|
139
|
+
generateMessage: async () => ({
|
|
140
|
+
content: "Conversation summary.",
|
|
141
|
+
toolCalls: [],
|
|
142
|
+
}),
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const baseOptions: CompactionOptions = {
|
|
146
|
+
maxTokens: 10_000,
|
|
147
|
+
compactionThreshold: 0.8,
|
|
148
|
+
preserveRecentCount: 2,
|
|
149
|
+
maxToolResultChars: 1_000,
|
|
150
|
+
provider: mockProvider as any,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Strategy: 'none' ā history is well under budget
|
|
154
|
+
const r1 = await CompactionEngine.checkAndCompact(smallHistory, baseOptions);
|
|
155
|
+
console.log(`Small history (${CompactionEngine.estimateTokens(smallHistory)} tokens):`);
|
|
156
|
+
console.log(` ā Strategy: ${r1.strategy}\n`);
|
|
157
|
+
|
|
158
|
+
// Strategy: 'tool_result_budget' ā large tool results push over threshold
|
|
159
|
+
const mediumHistory: HistoryItem[] = [
|
|
160
|
+
{ role: "user", content: "Analyze this." },
|
|
161
|
+
{ role: "tool", tool_call_id: "tc_m1", name: "analyze", content: "a".repeat(30_000) },
|
|
162
|
+
{ role: "user", content: "Thanks." },
|
|
163
|
+
{ role: "assistant", content: "You're welcome." },
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
const r2 = await CompactionEngine.checkAndCompact(mediumHistory, {
|
|
167
|
+
...baseOptions,
|
|
168
|
+
maxTokens: 2_000,
|
|
169
|
+
});
|
|
170
|
+
console.log(`Medium history with large tool result (${CompactionEngine.estimateTokens(mediumHistory)} tokens):`);
|
|
171
|
+
console.log(` ā Strategy: ${r2.strategy}`);
|
|
172
|
+
console.log(` ā Tokens after: ${r2.estimatedTokens}\n`);
|
|
173
|
+
|
|
174
|
+
// Strategy: 'auto_compact' ā many messages push well over budget
|
|
175
|
+
const longHistory: HistoryItem[] = Array.from({ length: 50 }, (_, i) => ({
|
|
176
|
+
role: (i % 2 === 0 ? "user" : "assistant") as "user" | "assistant",
|
|
177
|
+
content: `Message ${i}: ${"lorem ipsum ".repeat(100)}`,
|
|
178
|
+
}));
|
|
179
|
+
|
|
180
|
+
const r3 = await CompactionEngine.checkAndCompact(longHistory, {
|
|
181
|
+
...baseOptions,
|
|
182
|
+
maxTokens: 5_000,
|
|
183
|
+
});
|
|
184
|
+
console.log(`Long history (${CompactionEngine.estimateTokens(longHistory)} tokens, ${longHistory.length} messages):`);
|
|
185
|
+
console.log(` ā Strategy: ${r3.strategy}`);
|
|
186
|
+
console.log(` ā Tokens after: ${r3.estimatedTokens}`);
|
|
187
|
+
console.log(` ā Messages compacted: ${r3.messagesCompacted}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// --- Validation demo ---
|
|
191
|
+
|
|
192
|
+
function demonstrateValidation() {
|
|
193
|
+
console.log("\n=== CompactionOptions Validation ===\n");
|
|
194
|
+
|
|
195
|
+
const invalidConfigs = [
|
|
196
|
+
{ label: "threshold too low (0.3)", opts: { compactionThreshold: 0.3, preserveRecentCount: 4, maxToolResultChars: 1000, maxTokens: 10000 } },
|
|
197
|
+
{ label: "threshold too high (0.99)", opts: { compactionThreshold: 0.99, preserveRecentCount: 4, maxToolResultChars: 1000, maxTokens: 10000 } },
|
|
198
|
+
{ label: "preserveRecentCount < 2", opts: { compactionThreshold: 0.8, preserveRecentCount: 1, maxToolResultChars: 1000, maxTokens: 10000 } },
|
|
199
|
+
{ label: "maxToolResultChars <= 0", opts: { compactionThreshold: 0.8, preserveRecentCount: 4, maxToolResultChars: 0, maxTokens: 10000 } },
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
for (const { label, opts } of invalidConfigs) {
|
|
203
|
+
try {
|
|
204
|
+
CompactionEngine.validateOptions(opts as any);
|
|
205
|
+
console.log(` ${label}: accepted (unexpected)`);
|
|
206
|
+
} catch (e) {
|
|
207
|
+
console.log(` ${label}: rejected ā ${(e as Error).message}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function main() {
|
|
213
|
+
await demonstrateAgentCompaction();
|
|
214
|
+
await demonstrateManualCompaction();
|
|
215
|
+
await demonstrateStrategies();
|
|
216
|
+
demonstrateValidation();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
220
|
+
main().catch(console.error);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export { main };
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
AnthropicProvider,
|
|
15
15
|
OpenAIProvider,
|
|
16
16
|
GeminiProvider,
|
|
17
|
+
type EnhancedTool,
|
|
17
18
|
} from "../../src/index";
|
|
18
19
|
|
|
19
20
|
// Custom context type
|
|
@@ -141,8 +142,8 @@ async function legacyStreamingWithAnthropic() {
|
|
|
141
142
|
|
|
142
143
|
// Legacy respondStream API - requires manual session management
|
|
143
144
|
let fullMessage = "";
|
|
144
|
-
for await (const chunk of agent.respondStream({
|
|
145
|
-
history: agent.session.getHistory()
|
|
145
|
+
for await (const chunk of agent.respondStream({
|
|
146
|
+
history: agent.session.getHistory()
|
|
146
147
|
})) {
|
|
147
148
|
if (chunk.delta) {
|
|
148
149
|
process.stdout.write(chunk.delta);
|
|
@@ -157,7 +158,7 @@ async function legacyStreamingWithAnthropic() {
|
|
|
157
158
|
);
|
|
158
159
|
console.log(` - Data:`, agent.session.getData() || "None");
|
|
159
160
|
console.log(` - Tool Calls: ${chunk.toolCalls?.length || 0}`);
|
|
160
|
-
|
|
161
|
+
|
|
161
162
|
// Manual session history management required
|
|
162
163
|
await agent.session.addMessage("assistant", fullMessage);
|
|
163
164
|
console.log(` - Session Messages: ${agent.session.getHistory().length}`);
|
|
@@ -220,7 +221,7 @@ async function modernStreamingWithOpenAI() {
|
|
|
220
221
|
` - Route: ${chunk.session?.currentRoute?.title || "None"}`
|
|
221
222
|
);
|
|
222
223
|
console.log(` - Data:`, agent.session.getData() || "None");
|
|
223
|
-
|
|
224
|
+
|
|
224
225
|
// Session automatically updated - no manual work needed!
|
|
225
226
|
console.log(` - Session Messages: ${agent.session.getHistory().length}`);
|
|
226
227
|
}
|
|
@@ -267,10 +268,10 @@ async function modernStreamingComparison() {
|
|
|
267
268
|
|
|
268
269
|
// Manual session management
|
|
269
270
|
await agent.session.addMessage("user", userMessage);
|
|
270
|
-
|
|
271
|
+
|
|
271
272
|
let oldWayMessage = "";
|
|
272
|
-
for await (const chunk of agent.respondStream({
|
|
273
|
-
history: agent.session.getHistory()
|
|
273
|
+
for await (const chunk of agent.respondStream({
|
|
274
|
+
history: agent.session.getHistory()
|
|
274
275
|
})) {
|
|
275
276
|
if (chunk.delta) {
|
|
276
277
|
process.stdout.write(chunk.delta);
|
|
@@ -528,6 +529,81 @@ async function logFeedback(data: { rating: number; comments: string }) {
|
|
|
528
529
|
console.log("⨠Feedback logged successfully!");
|
|
529
530
|
}
|
|
530
531
|
|
|
532
|
+
async function streamingWithToolExecution() {
|
|
533
|
+
console.log("\nš¤ Example 7: Streaming with Tool Execution\n");
|
|
534
|
+
|
|
535
|
+
const provider = new AnthropicProvider({
|
|
536
|
+
apiKey: process.env.ANTHROPIC_API_KEY || "",
|
|
537
|
+
model: "claude-sonnet-4-5",
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// Define EnhancedTools with concurrency metadata
|
|
541
|
+
const readFileTool: EnhancedTool = {
|
|
542
|
+
id: "read_file",
|
|
543
|
+
name: "Read File",
|
|
544
|
+
description: "Read a file from disk",
|
|
545
|
+
parameters: {
|
|
546
|
+
type: "object",
|
|
547
|
+
properties: { path: { type: "string" } },
|
|
548
|
+
required: ["path"],
|
|
549
|
+
},
|
|
550
|
+
handler: async (_ctx, args) => {
|
|
551
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
552
|
+
return { data: `Contents of ${args?.path}`, success: true };
|
|
553
|
+
},
|
|
554
|
+
isConcurrencySafe: () => true,
|
|
555
|
+
isReadOnly: () => true,
|
|
556
|
+
interruptBehavior: () => "cancel",
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
const writeFileTool: EnhancedTool = {
|
|
560
|
+
id: "write_file",
|
|
561
|
+
name: "Write File",
|
|
562
|
+
description: "Write content to a file",
|
|
563
|
+
parameters: {
|
|
564
|
+
type: "object",
|
|
565
|
+
properties: { path: { type: "string" }, content: { type: "string" } },
|
|
566
|
+
required: ["path", "content"],
|
|
567
|
+
},
|
|
568
|
+
handler: async (_ctx, args) => {
|
|
569
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
570
|
+
return { data: `Wrote to ${args?.path}`, success: true };
|
|
571
|
+
},
|
|
572
|
+
isConcurrencySafe: () => false,
|
|
573
|
+
isDestructive: () => true,
|
|
574
|
+
interruptBehavior: () => "block",
|
|
575
|
+
maxResultSizeChars: 1_000,
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
const agent = new Agent({
|
|
579
|
+
name: "ToolStreamingAssistant",
|
|
580
|
+
description: "Demonstrates streaming with concurrent tool execution",
|
|
581
|
+
provider,
|
|
582
|
+
tools: [readFileTool, writeFileTool],
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
try {
|
|
586
|
+
console.log("š¤ Streaming with tool execution...\n");
|
|
587
|
+
console.log("When the LLM calls multiple read-only tools, they execute in parallel.");
|
|
588
|
+
console.log("Write tools wait for exclusive access.\n");
|
|
589
|
+
console.log("Response: ");
|
|
590
|
+
|
|
591
|
+
for await (const chunk of agent.stream("Read index.ts and utils.ts, then write output.ts")) {
|
|
592
|
+
if (chunk.delta) {
|
|
593
|
+
process.stdout.write(chunk.delta);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (chunk.done) {
|
|
597
|
+
console.log("\n\nā
Stream complete!");
|
|
598
|
+
console.log(` Tool Calls: ${chunk.toolCalls?.length || 0}`);
|
|
599
|
+
console.log(` Session Messages: ${agent.session.getHistory().length}`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
} catch (error) {
|
|
603
|
+
console.error("ā Error:", error);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
531
607
|
async function main() {
|
|
532
608
|
console.log("š Starting Streaming Examples\n");
|
|
533
609
|
console.log("=".repeat(60));
|
|
@@ -539,6 +615,7 @@ async function main() {
|
|
|
539
615
|
{ name: "API Comparison (Gemini)", fn: modernStreamingComparison },
|
|
540
616
|
{ name: "Modern Streaming with Routes", fn: modernStreamingWithRoutes },
|
|
541
617
|
{ name: "Modern Streaming with Abort", fn: modernStreamingWithAbortSignal },
|
|
618
|
+
{ name: "Streaming with Tool Execution", fn: streamingWithToolExecution },
|
|
542
619
|
];
|
|
543
620
|
|
|
544
621
|
console.log("\nAvailable Examples:");
|
|
@@ -553,6 +630,7 @@ async function main() {
|
|
|
553
630
|
console.log(" - Streaming provides real-time responses for better UX");
|
|
554
631
|
console.log(" - Use AbortSignal to cancel long-running streams");
|
|
555
632
|
console.log(" - Access chunk.route and chunk.step for flow information");
|
|
633
|
+
console.log(" - NEW: EnhancedTool metadata enables parallel read-only tool execution");
|
|
556
634
|
|
|
557
635
|
console.log("\n" + "=".repeat(60));
|
|
558
636
|
|