@agentionai/agents 0.8.1-beta → 0.9.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.
- package/dist/agents/AgentConfig.d.ts +6 -0
- package/dist/agents/BaseAgent.d.ts +1 -1
- package/dist/agents/BaseAgent.js +8 -4
- package/dist/agents/anthropic/ClaudeAgent.js +21 -2
- package/dist/agents/google/GeminiAgent.js +2 -2
- package/dist/agents/mistral/MistralAgent.js +2 -2
- package/dist/agents/openai/OpenAiAgent.js +2 -2
- package/dist/history/History.d.ts +143 -8
- package/dist/history/History.js +223 -4
- package/dist/history/index.d.ts +5 -0
- package/dist/history/index.js +17 -0
- package/dist/history/plugins/compressionPlugin.d.ts +54 -0
- package/dist/history/plugins/compressionPlugin.js +143 -0
- package/dist/history/plugins/index.d.ts +3 -0
- package/dist/history/plugins/index.js +8 -0
- package/dist/history/plugins/toolResultMaskingPlugin.d.ts +66 -0
- package/dist/history/plugins/toolResultMaskingPlugin.js +141 -0
- package/dist/history/types.d.ts +12 -0
- package/package.json +9 -1
|
@@ -18,6 +18,12 @@ export interface CommonAgentConfig {
|
|
|
18
18
|
debug?: boolean;
|
|
19
19
|
/** Maximum number of messages to retain in conversation history */
|
|
20
20
|
maxHistoryLength?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Maximum estimated tokens to retain in conversation history.
|
|
23
|
+
* When exceeded, oldest non-system entries are dropped.
|
|
24
|
+
* Takes precedence over maxHistoryLength for context-window-aware trimming.
|
|
25
|
+
*/
|
|
26
|
+
maxHistoryTokens?: number;
|
|
21
27
|
/** Model identifier (e.g., "claude-3-5-sonnet-20241022", "gpt-4") */
|
|
22
28
|
model?: string;
|
|
23
29
|
/** Array of tools the agent can use during execution */
|
|
@@ -22,13 +22,13 @@ export type TokenUsage = {
|
|
|
22
22
|
* Handles the BaseConfig
|
|
23
23
|
*/
|
|
24
24
|
export declare abstract class BaseAgent<TInput = unknown, TOutput = unknown> extends EventEmitter {
|
|
25
|
-
protected history: History;
|
|
26
25
|
protected id: string;
|
|
27
26
|
protected debug: boolean;
|
|
28
27
|
protected name: string;
|
|
29
28
|
protected description: string;
|
|
30
29
|
protected tools: Map<string, Tool<unknown>>;
|
|
31
30
|
protected maxHistoryLength: number;
|
|
31
|
+
protected history: History;
|
|
32
32
|
/** The vendor/provider for this agent (anthropic, openai, mistral, gemini) */
|
|
33
33
|
protected vendor: AgentVendor;
|
|
34
34
|
/** The model identifier for this agent */
|
package/dist/agents/BaseAgent.js
CHANGED
|
@@ -21,9 +21,8 @@ class BaseAgent extends events_1.default {
|
|
|
21
21
|
* prompts, then supply a History object.
|
|
22
22
|
*
|
|
23
23
|
*/
|
|
24
|
-
constructor(config, history
|
|
24
|
+
constructor(config, history) {
|
|
25
25
|
super();
|
|
26
|
-
this.history = history;
|
|
27
26
|
this.debug = true;
|
|
28
27
|
this.id = config.id;
|
|
29
28
|
this.debug = config.debug || false;
|
|
@@ -31,6 +30,12 @@ class BaseAgent extends events_1.default {
|
|
|
31
30
|
this.description = config.description;
|
|
32
31
|
this.vendor = config.vendor;
|
|
33
32
|
this.model = config.model || "unknown";
|
|
33
|
+
this.maxHistoryLength = config.maxHistoryLength || 100;
|
|
34
|
+
this.history = history ?? new History_1.History([], {
|
|
35
|
+
transient: true,
|
|
36
|
+
maxLength: config.maxHistoryLength,
|
|
37
|
+
maxTokens: config.maxHistoryTokens,
|
|
38
|
+
});
|
|
34
39
|
if (config.agents) {
|
|
35
40
|
const agentTools = config.agents.map((agent) => {
|
|
36
41
|
return Tool_1.Tool.fromAgent(agent, `You can use this agent ${agent.getName()} to execute tasks`);
|
|
@@ -40,7 +45,6 @@ class BaseAgent extends events_1.default {
|
|
|
40
45
|
: agentTools;
|
|
41
46
|
}
|
|
42
47
|
this.tools = new Map((config.tools || []).map((tool) => [tool.name, tool]));
|
|
43
|
-
this.maxHistoryLength = config.maxHistoryLength || 100;
|
|
44
48
|
}
|
|
45
49
|
getToolDefinitions() {
|
|
46
50
|
return Array.from(this.tools.values()).map((tool) => tool.getPrompt());
|
|
@@ -108,7 +112,7 @@ class BaseAgent extends events_1.default {
|
|
|
108
112
|
return this.model;
|
|
109
113
|
}
|
|
110
114
|
getHistoryEntries() {
|
|
111
|
-
return this.history.
|
|
115
|
+
return this.history.getEntries();
|
|
112
116
|
}
|
|
113
117
|
getTools() {
|
|
114
118
|
return [...this.tools.values()];
|
|
@@ -75,7 +75,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
75
75
|
}
|
|
76
76
|
this.addTextToHistory("user", input);
|
|
77
77
|
try {
|
|
78
|
-
const messages = transformers_1.anthropicTransformer.toProvider(this.history.
|
|
78
|
+
const messages = transformers_1.anthropicTransformer.toProvider(this.history.getEntries());
|
|
79
79
|
const systemMessage = this.history.getSystemMessage();
|
|
80
80
|
const response = await this.client.messages.create({
|
|
81
81
|
model: this.config.model,
|
|
@@ -177,7 +177,7 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
177
177
|
this.addMessageToHistory("user", toolResults);
|
|
178
178
|
// Continue conversation with tool results
|
|
179
179
|
try {
|
|
180
|
-
const messages = transformers_1.anthropicTransformer.toProvider(this.history.
|
|
180
|
+
const messages = transformers_1.anthropicTransformer.toProvider(this.history.getEntries());
|
|
181
181
|
const newResponse = await this.client.messages.create({
|
|
182
182
|
model: this.config.model,
|
|
183
183
|
max_tokens: this.config.maxTokens,
|
|
@@ -224,6 +224,12 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
224
224
|
}
|
|
225
225
|
// Track tool call count for viz reporting
|
|
226
226
|
this.currentToolCallCount += toolUseBlocks.length;
|
|
227
|
+
const agentSource = {
|
|
228
|
+
agentId: this.getId(),
|
|
229
|
+
agentName: this.getName(),
|
|
230
|
+
model: this.config.model,
|
|
231
|
+
vendor: "anthropic",
|
|
232
|
+
};
|
|
227
233
|
const results = await Promise.all(toolUseBlocks.map(async (block) => {
|
|
228
234
|
const tool = this.tools.get(block.name);
|
|
229
235
|
if (!tool) {
|
|
@@ -232,10 +238,20 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
232
238
|
if (this.debug) {
|
|
233
239
|
console.error(error);
|
|
234
240
|
}
|
|
241
|
+
if (VizConfig_1.vizConfig.isEnabled()) {
|
|
242
|
+
const vizEventId = VizReporter_1.vizReporter.toolStart(block.name, block.id, block.input, agentSource);
|
|
243
|
+
VizReporter_1.vizReporter.toolError(vizEventId, block.name, block.id, errorMessage);
|
|
244
|
+
}
|
|
235
245
|
return (0, History_1.toolResult)(block.id, errorMessage, true);
|
|
236
246
|
}
|
|
247
|
+
const vizEventId = VizConfig_1.vizConfig.isEnabled()
|
|
248
|
+
? VizReporter_1.vizReporter.toolStart(block.name, block.id, block.input, agentSource)
|
|
249
|
+
: undefined;
|
|
237
250
|
try {
|
|
238
251
|
const result = await tool.execute(this.getId(), this.getName(), block.input, block.id, this.config.model, "anthropic");
|
|
252
|
+
if (vizEventId) {
|
|
253
|
+
VizReporter_1.vizReporter.toolComplete(vizEventId, block.name, block.id, true, result);
|
|
254
|
+
}
|
|
239
255
|
return (0, History_1.toolResult)(block.id, JSON.stringify(result));
|
|
240
256
|
}
|
|
241
257
|
catch (error) {
|
|
@@ -245,6 +261,9 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
245
261
|
}
|
|
246
262
|
const toolError = new AgentError_1.ToolExecutionError(errorMessage, block.name, block.input);
|
|
247
263
|
this.emit(AgentEvent_1.AgentEvent.TOOL_ERROR, toolError);
|
|
264
|
+
if (vizEventId) {
|
|
265
|
+
VizReporter_1.vizReporter.toolError(vizEventId, block.name, block.id, errorMessage);
|
|
266
|
+
}
|
|
248
267
|
return (0, History_1.toolResult)(block.id, errorMessage, true);
|
|
249
268
|
}
|
|
250
269
|
}));
|
|
@@ -176,7 +176,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
|
|
|
176
176
|
}
|
|
177
177
|
this.addTextToHistory("user", input);
|
|
178
178
|
try {
|
|
179
|
-
const contents = transformers_1.geminiTransformer.toProvider(this.history.
|
|
179
|
+
const contents = transformers_1.geminiTransformer.toProvider(this.history.getEntries());
|
|
180
180
|
const systemMessage = this.history.getSystemMessage();
|
|
181
181
|
const tools = this.getToolDefinitionsForGemini();
|
|
182
182
|
const response = await this.generativeModel.generateContent({
|
|
@@ -290,7 +290,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
|
|
|
290
290
|
}
|
|
291
291
|
// Continue conversation
|
|
292
292
|
try {
|
|
293
|
-
const newContents = transformers_1.geminiTransformer.toProvider(this.history.
|
|
293
|
+
const newContents = transformers_1.geminiTransformer.toProvider(this.history.getEntries());
|
|
294
294
|
const systemMessage = this.history.getSystemMessage();
|
|
295
295
|
const tools = this.getToolDefinitionsForGemini();
|
|
296
296
|
const newResponse = await this.generativeModel.generateContent({
|
|
@@ -86,7 +86,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
|
|
|
86
86
|
}
|
|
87
87
|
this.addTextToHistory("user", input);
|
|
88
88
|
try {
|
|
89
|
-
const messages = transformers_1.mistralTransformer.toProvider(this.history.
|
|
89
|
+
const messages = transformers_1.mistralTransformer.toProvider(this.history.getEntries());
|
|
90
90
|
const response = await this.client.chat.complete({
|
|
91
91
|
model: this.config.model,
|
|
92
92
|
messages: messages,
|
|
@@ -202,7 +202,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
|
|
|
202
202
|
await (0, promises_1.setTimeout)(this.config.rateLimitDelay || 1500);
|
|
203
203
|
// Continue conversation
|
|
204
204
|
try {
|
|
205
|
-
const messages = transformers_1.mistralTransformer.toProvider(this.history.
|
|
205
|
+
const messages = transformers_1.mistralTransformer.toProvider(this.history.getEntries());
|
|
206
206
|
const newResponse = await this.client.chat.complete({
|
|
207
207
|
model: this.config.model,
|
|
208
208
|
messages: messages,
|
|
@@ -97,7 +97,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
97
97
|
}
|
|
98
98
|
this.addTextToHistory("user", input);
|
|
99
99
|
try {
|
|
100
|
-
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.
|
|
100
|
+
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
101
101
|
const response = await this.client.responses.create({
|
|
102
102
|
model: this.config.model,
|
|
103
103
|
max_output_tokens: this.config.maxTokens,
|
|
@@ -216,7 +216,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
216
216
|
}
|
|
217
217
|
// Continue conversation
|
|
218
218
|
try {
|
|
219
|
-
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.
|
|
219
|
+
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
220
220
|
const newResponse = await this.client.responses.create({
|
|
221
221
|
model: this.config.model,
|
|
222
222
|
max_output_tokens: this.config.maxTokens,
|
|
@@ -1,22 +1,80 @@
|
|
|
1
1
|
import EventEmitter from "events";
|
|
2
2
|
import { HistoryEntry, MessageRole, MessageContent } from "./types";
|
|
3
|
-
|
|
3
|
+
import type { ReduceOptions } from "./types";
|
|
4
|
+
/** @internal — exposed for test teardown only */
|
|
5
|
+
export declare function resetTokenxCache(): void;
|
|
6
|
+
export type { HistoryEntry, MessageRole, MessageContent, ReduceOptions } from "./types";
|
|
4
7
|
export { text, toolUse, toolResult, textMessage, isTextContent, isToolUseContent, isToolResultContent, } from "./types";
|
|
5
8
|
/**
|
|
6
|
-
*
|
|
9
|
+
* Metadata stored alongside each history entry.
|
|
10
|
+
* Extended with summary tracking fields for the compression plugin.
|
|
7
11
|
*/
|
|
8
|
-
type
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
export type EntryMetadata = {
|
|
13
|
+
date: string;
|
|
14
|
+
contentLength: number;
|
|
15
|
+
estimatedTokens: number;
|
|
16
|
+
/**
|
|
17
|
+
* True when this entry was produced by a compression plugin as a rolling
|
|
18
|
+
* summary of earlier turns. Used so subsequent compressions can include
|
|
19
|
+
* the existing summary as prior context rather than treating it as a
|
|
20
|
+
* regular conversation turn.
|
|
21
|
+
*/
|
|
22
|
+
isSummary?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* ISO date range covered by a summary entry.
|
|
25
|
+
* Only present when isSummary is true.
|
|
26
|
+
*/
|
|
27
|
+
coversRange?: {
|
|
28
|
+
from: string;
|
|
29
|
+
to: string;
|
|
12
30
|
};
|
|
13
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* A history entry with its internal metadata attached.
|
|
34
|
+
* Passed to plugin reduce() and transform() hooks.
|
|
35
|
+
*/
|
|
36
|
+
export type ReducibleEntry = HistoryEntry & {
|
|
37
|
+
__metadata: EntryMetadata;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A history plugin. Register with `history.use(plugin)`.
|
|
41
|
+
*
|
|
42
|
+
* Hooks:
|
|
43
|
+
* - `onRegistered` — called once immediately when the plugin is registered
|
|
44
|
+
* - `afterAdd` — called fire-and-forget after every addEntry(); errors are
|
|
45
|
+
* routed to the `onPluginError` option / `"pluginError"` event, never thrown
|
|
46
|
+
* - `reduce` — called by history.reduce(); receives and returns the full
|
|
47
|
+
* entry array; plugins are piped in registration order
|
|
48
|
+
* - `transform` — pure read-time rewrite; called by history.getEntries();
|
|
49
|
+
* sync, cheap, must not mutate stored entries; applied in registration order
|
|
50
|
+
*/
|
|
51
|
+
export type HistoryPlugin = {
|
|
52
|
+
onRegistered?: (history: History) => void;
|
|
53
|
+
afterAdd?: (history: History) => void | Promise<void>;
|
|
54
|
+
reduce?: (entries: ReducibleEntry[], options: ReduceOptions) => Promise<ReducibleEntry[]>;
|
|
55
|
+
transform?: (entries: ReducibleEntry[]) => ReducibleEntry[];
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Internal entry with metadata
|
|
59
|
+
*/
|
|
60
|
+
type EntryWithMetadata = ReducibleEntry;
|
|
14
61
|
/**
|
|
15
62
|
* History configuration options
|
|
16
63
|
*/
|
|
17
64
|
type HistoryOptions = {
|
|
18
65
|
maxLength?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Maximum estimated tokens to retain in history. When exceeded, oldest
|
|
68
|
+
* non-system entries are dropped via the addEntry() safety net.
|
|
69
|
+
* The system message is always preserved.
|
|
70
|
+
*/
|
|
71
|
+
maxTokens?: number;
|
|
19
72
|
transient?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Called when a plugin's afterAdd hook throws. If not provided, the error
|
|
75
|
+
* is re-emitted as a "pluginError" event on the History instance.
|
|
76
|
+
*/
|
|
77
|
+
onPluginError?: (error: Error, plugin: HistoryPlugin, hook: "afterAdd") => void;
|
|
20
78
|
};
|
|
21
79
|
/**
|
|
22
80
|
* Manages conversation history in a provider-agnostic format.
|
|
@@ -27,6 +85,10 @@ type HistoryOptions = {
|
|
|
27
85
|
* History can be shared between agents of different providers, enabling
|
|
28
86
|
* cross-provider conversations and handoffs.
|
|
29
87
|
*
|
|
88
|
+
* Plugins can be registered with `history.use(plugin)` to add read-time
|
|
89
|
+
* transforms (e.g., tool result masking) or async reduce strategies
|
|
90
|
+
* (e.g., rolling LLM summarization).
|
|
91
|
+
*
|
|
30
92
|
* @example Basic usage
|
|
31
93
|
* ```typescript
|
|
32
94
|
* const history = new History();
|
|
@@ -34,6 +96,19 @@ type HistoryOptions = {
|
|
|
34
96
|
* history.addText("assistant", "Hi there!");
|
|
35
97
|
* ```
|
|
36
98
|
*
|
|
99
|
+
* @example With tool result masking plugin
|
|
100
|
+
* ```typescript
|
|
101
|
+
* import { toolResultMaskingPlugin } from "agention-lib/history/plugins";
|
|
102
|
+
*
|
|
103
|
+
* const maskingPlugin = toolResultMaskingPlugin({ keepRecentResults: 2 });
|
|
104
|
+
* const history = new History().use(maskingPlugin);
|
|
105
|
+
*
|
|
106
|
+
* const agent = new ClaudeAgent(
|
|
107
|
+
* { tools: [maskingPlugin.retrieveTool, ...otherTools] },
|
|
108
|
+
* history
|
|
109
|
+
* );
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
37
112
|
* @example Sharing between agents
|
|
38
113
|
* ```typescript
|
|
39
114
|
* const history = new History();
|
|
@@ -46,7 +121,22 @@ export declare class History extends EventEmitter {
|
|
|
46
121
|
protected _entries: EntryWithMetadata[];
|
|
47
122
|
private options;
|
|
48
123
|
transient: boolean;
|
|
124
|
+
private _plugins;
|
|
125
|
+
private _reducing;
|
|
49
126
|
constructor(entries?: HistoryEntry[], options?: HistoryOptions);
|
|
127
|
+
/**
|
|
128
|
+
* Register a plugin with this history instance.
|
|
129
|
+
* Calls plugin.onRegistered(this) immediately after registration.
|
|
130
|
+
* Returns `this` for chaining.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* history
|
|
135
|
+
* .use(compressionPlugin(summaryAgent))
|
|
136
|
+
* .use(toolResultMaskingPlugin({ keepRecentResults: 2 }));
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
use(plugin: HistoryPlugin): this;
|
|
50
140
|
/**
|
|
51
141
|
* Add a complete history entry
|
|
52
142
|
*/
|
|
@@ -64,9 +154,27 @@ export declare class History extends EventEmitter {
|
|
|
64
154
|
*/
|
|
65
155
|
addSystem(content: string): void;
|
|
66
156
|
/**
|
|
67
|
-
* Get
|
|
157
|
+
* Get entries as agents should see them — with all registered transform
|
|
158
|
+
* plugins applied in registration order.
|
|
159
|
+
*
|
|
160
|
+
* Use this when building API requests. The raw `entries` getter is
|
|
161
|
+
* reserved for serialization, cloning, and other internal purposes.
|
|
162
|
+
*/
|
|
163
|
+
getEntries(): HistoryEntry[];
|
|
164
|
+
/**
|
|
165
|
+
* Get all entries without transform plugins applied (raw storage).
|
|
166
|
+
* Use for serialization, cloning, and debugging.
|
|
68
167
|
*/
|
|
69
168
|
get entries(): HistoryEntry[];
|
|
169
|
+
/**
|
|
170
|
+
* Get the full content of a tool result by its tool_use_id.
|
|
171
|
+
* Always reads from raw stored entries — never affected by transform plugins.
|
|
172
|
+
*
|
|
173
|
+
* For RedisHistory: call await load() before using this method.
|
|
174
|
+
*
|
|
175
|
+
* @returns The full result string, or undefined if not found.
|
|
176
|
+
*/
|
|
177
|
+
getToolResult(tool_use_id: string): string | undefined;
|
|
70
178
|
/**
|
|
71
179
|
* Get the number of entries
|
|
72
180
|
*/
|
|
@@ -75,6 +183,11 @@ export declare class History extends EventEmitter {
|
|
|
75
183
|
* Get total content size in characters
|
|
76
184
|
*/
|
|
77
185
|
get size(): number;
|
|
186
|
+
/**
|
|
187
|
+
* Get total estimated token count across all entries.
|
|
188
|
+
* Uses a rough approximation of 1 token ≈ 4 characters.
|
|
189
|
+
*/
|
|
190
|
+
get totalEstimatedTokens(): number;
|
|
78
191
|
/**
|
|
79
192
|
* Get the last entry
|
|
80
193
|
*/
|
|
@@ -84,9 +197,25 @@ export declare class History extends EventEmitter {
|
|
|
84
197
|
*/
|
|
85
198
|
getSystemMessage(): string | undefined;
|
|
86
199
|
/**
|
|
87
|
-
* Get entries without system messages
|
|
200
|
+
* Get entries without system messages, with transform plugins applied.
|
|
88
201
|
*/
|
|
89
202
|
getMessagesWithoutSystem(): HistoryEntry[];
|
|
203
|
+
/**
|
|
204
|
+
* Asynchronously compact history using registered reduce plugins.
|
|
205
|
+
*
|
|
206
|
+
* Plugins are called in registration order, each receiving and returning
|
|
207
|
+
* the full entry array. If no plugin has a `reduce` hook, this is a no-op —
|
|
208
|
+
* the addEntry() safety net (FIFO drop via maxTokens) runs independently.
|
|
209
|
+
*
|
|
210
|
+
* Re-entrant calls during an in-progress reduce() return immediately.
|
|
211
|
+
*
|
|
212
|
+
* @example Rolling summarization
|
|
213
|
+
* ```typescript
|
|
214
|
+
* history.use(compressionPlugin(summaryAgent));
|
|
215
|
+
* await history.reduce({ maxTokens: 4000 });
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
reduce(options?: ReduceOptions): Promise<void>;
|
|
90
219
|
/**
|
|
91
220
|
* Clear all history entries
|
|
92
221
|
*/
|
|
@@ -103,5 +232,11 @@ export declare class History extends EventEmitter {
|
|
|
103
232
|
* Create a copy of this history
|
|
104
233
|
*/
|
|
105
234
|
clone(options?: HistoryOptions): History;
|
|
235
|
+
/**
|
|
236
|
+
* Drop oldest non-system entries until totalEstimatedTokens fits within budget.
|
|
237
|
+
* Called synchronously from addEntry() as a safety net.
|
|
238
|
+
* The system message is always preserved.
|
|
239
|
+
*/
|
|
240
|
+
private trimToTokenBudget;
|
|
106
241
|
}
|
|
107
242
|
//# sourceMappingURL=History.d.ts.map
|
package/dist/history/History.js
CHANGED
|
@@ -1,11 +1,58 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
39
|
exports.History = exports.isToolResultContent = exports.isToolUseContent = exports.isTextContent = exports.textMessage = exports.toolResult = exports.toolUse = exports.text = void 0;
|
|
40
|
+
exports.resetTokenxCache = resetTokenxCache;
|
|
7
41
|
const events_1 = __importDefault(require("events"));
|
|
8
42
|
const types_1 = require("./types");
|
|
43
|
+
// Cached tokenx estimator — starts as a character-based fallback and is
|
|
44
|
+
// replaced with the real tokenx implementation once the module loads.
|
|
45
|
+
let _estimateTokenCount = (t) => Math.ceil(t.length / 4);
|
|
46
|
+
void Promise.resolve().then(() => __importStar(require("tokenx"))).then((mod) => {
|
|
47
|
+
_estimateTokenCount = mod.estimateTokenCount;
|
|
48
|
+
})
|
|
49
|
+
.catch(() => {
|
|
50
|
+
/* keep fallback */
|
|
51
|
+
});
|
|
52
|
+
/** @internal — exposed for test teardown only */
|
|
53
|
+
function resetTokenxCache() {
|
|
54
|
+
_estimateTokenCount = (t) => Math.ceil(t.length / 4);
|
|
55
|
+
}
|
|
9
56
|
var types_2 = require("./types");
|
|
10
57
|
Object.defineProperty(exports, "text", { enumerable: true, get: function () { return types_2.text; } });
|
|
11
58
|
Object.defineProperty(exports, "toolUse", { enumerable: true, get: function () { return types_2.toolUse; } });
|
|
@@ -23,6 +70,10 @@ Object.defineProperty(exports, "isToolResultContent", { enumerable: true, get: f
|
|
|
23
70
|
* History can be shared between agents of different providers, enabling
|
|
24
71
|
* cross-provider conversations and handoffs.
|
|
25
72
|
*
|
|
73
|
+
* Plugins can be registered with `history.use(plugin)` to add read-time
|
|
74
|
+
* transforms (e.g., tool result masking) or async reduce strategies
|
|
75
|
+
* (e.g., rolling LLM summarization).
|
|
76
|
+
*
|
|
26
77
|
* @example Basic usage
|
|
27
78
|
* ```typescript
|
|
28
79
|
* const history = new History();
|
|
@@ -30,6 +81,19 @@ Object.defineProperty(exports, "isToolResultContent", { enumerable: true, get: f
|
|
|
30
81
|
* history.addText("assistant", "Hi there!");
|
|
31
82
|
* ```
|
|
32
83
|
*
|
|
84
|
+
* @example With tool result masking plugin
|
|
85
|
+
* ```typescript
|
|
86
|
+
* import { toolResultMaskingPlugin } from "agention-lib/history/plugins";
|
|
87
|
+
*
|
|
88
|
+
* const maskingPlugin = toolResultMaskingPlugin({ keepRecentResults: 2 });
|
|
89
|
+
* const history = new History().use(maskingPlugin);
|
|
90
|
+
*
|
|
91
|
+
* const agent = new ClaudeAgent(
|
|
92
|
+
* { tools: [maskingPlugin.retrieveTool, ...otherTools] },
|
|
93
|
+
* history
|
|
94
|
+
* );
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
33
97
|
* @example Sharing between agents
|
|
34
98
|
* ```typescript
|
|
35
99
|
* const history = new History();
|
|
@@ -43,6 +107,8 @@ class History extends events_1.default {
|
|
|
43
107
|
super();
|
|
44
108
|
this._entries = [];
|
|
45
109
|
this.transient = false;
|
|
110
|
+
this._plugins = [];
|
|
111
|
+
this._reducing = false;
|
|
46
112
|
this.options = options;
|
|
47
113
|
this.transient = Boolean(options?.transient);
|
|
48
114
|
// Convert initial entries to internal format with metadata
|
|
@@ -50,13 +116,39 @@ class History extends events_1.default {
|
|
|
50
116
|
this.addEntry(entry);
|
|
51
117
|
}
|
|
52
118
|
}
|
|
119
|
+
// ===========================================================================
|
|
120
|
+
// Plugin registration
|
|
121
|
+
// ===========================================================================
|
|
122
|
+
/**
|
|
123
|
+
* Register a plugin with this history instance.
|
|
124
|
+
* Calls plugin.onRegistered(this) immediately after registration.
|
|
125
|
+
* Returns `this` for chaining.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* history
|
|
130
|
+
* .use(compressionPlugin(summaryAgent))
|
|
131
|
+
* .use(toolResultMaskingPlugin({ keepRecentResults: 2 }));
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
use(plugin) {
|
|
135
|
+
this._plugins.push(plugin);
|
|
136
|
+
plugin.onRegistered?.(this);
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
// ===========================================================================
|
|
140
|
+
// Core write operations
|
|
141
|
+
// ===========================================================================
|
|
53
142
|
/**
|
|
54
143
|
* Add a complete history entry
|
|
55
144
|
*/
|
|
56
145
|
addEntry(entry) {
|
|
146
|
+
const serialized = JSON.stringify(entry.content);
|
|
147
|
+
const contentLength = serialized.length;
|
|
57
148
|
const __metadata = {
|
|
58
149
|
date: new Date().toISOString(),
|
|
59
|
-
contentLength
|
|
150
|
+
contentLength,
|
|
151
|
+
estimatedTokens: _estimateTokenCount(serialized),
|
|
60
152
|
};
|
|
61
153
|
this._entries.push({
|
|
62
154
|
...entry,
|
|
@@ -65,7 +157,27 @@ class History extends events_1.default {
|
|
|
65
157
|
if (this.options.maxLength && this._entries.length > this.options.maxLength) {
|
|
66
158
|
this._entries = this._entries.slice(this._entries.length - this.options.maxLength);
|
|
67
159
|
}
|
|
160
|
+
if (this.options.maxTokens) {
|
|
161
|
+
this.trimToTokenBudget();
|
|
162
|
+
}
|
|
68
163
|
this.emit("entry", entry);
|
|
164
|
+
// Fire plugin afterAdd hooks. Skipped during reduce() to avoid recursion
|
|
165
|
+
// when compression plugins add summary entries to the history.
|
|
166
|
+
if (!this._reducing) {
|
|
167
|
+
for (const plugin of this._plugins) {
|
|
168
|
+
if (!plugin.afterAdd)
|
|
169
|
+
continue;
|
|
170
|
+
void Promise.resolve(plugin.afterAdd(this)).catch((err) => {
|
|
171
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
172
|
+
if (this.options.onPluginError) {
|
|
173
|
+
this.options.onPluginError(error, plugin, "afterAdd");
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
this.emit("pluginError", error, plugin, "afterAdd");
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
69
181
|
}
|
|
70
182
|
/**
|
|
71
183
|
* Add a simple text message
|
|
@@ -88,8 +200,28 @@ class History extends events_1.default {
|
|
|
88
200
|
addSystem(content) {
|
|
89
201
|
this.addText("system", content);
|
|
90
202
|
}
|
|
203
|
+
// ===========================================================================
|
|
204
|
+
// Read operations
|
|
205
|
+
// ===========================================================================
|
|
206
|
+
/**
|
|
207
|
+
* Get entries as agents should see them — with all registered transform
|
|
208
|
+
* plugins applied in registration order.
|
|
209
|
+
*
|
|
210
|
+
* Use this when building API requests. The raw `entries` getter is
|
|
211
|
+
* reserved for serialization, cloning, and other internal purposes.
|
|
212
|
+
*/
|
|
213
|
+
getEntries() {
|
|
214
|
+
let entries = this._entries;
|
|
215
|
+
for (const plugin of this._plugins) {
|
|
216
|
+
if (plugin.transform) {
|
|
217
|
+
entries = plugin.transform(entries);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return entries.map(({ __metadata, ...rest }) => rest);
|
|
221
|
+
}
|
|
91
222
|
/**
|
|
92
|
-
* Get all entries
|
|
223
|
+
* Get all entries without transform plugins applied (raw storage).
|
|
224
|
+
* Use for serialization, cloning, and debugging.
|
|
93
225
|
*/
|
|
94
226
|
get entries() {
|
|
95
227
|
return this._entries.map((entry) => {
|
|
@@ -97,6 +229,24 @@ class History extends events_1.default {
|
|
|
97
229
|
return rest;
|
|
98
230
|
});
|
|
99
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Get the full content of a tool result by its tool_use_id.
|
|
234
|
+
* Always reads from raw stored entries — never affected by transform plugins.
|
|
235
|
+
*
|
|
236
|
+
* For RedisHistory: call await load() before using this method.
|
|
237
|
+
*
|
|
238
|
+
* @returns The full result string, or undefined if not found.
|
|
239
|
+
*/
|
|
240
|
+
getToolResult(tool_use_id) {
|
|
241
|
+
for (const entry of this._entries) {
|
|
242
|
+
for (const block of entry.content) {
|
|
243
|
+
if ((0, types_1.isToolResultContent)(block) && block.tool_use_id === tool_use_id) {
|
|
244
|
+
return block.content;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
100
250
|
/**
|
|
101
251
|
* Get the number of entries
|
|
102
252
|
*/
|
|
@@ -111,6 +261,15 @@ class History extends events_1.default {
|
|
|
111
261
|
return total + __metadata.contentLength;
|
|
112
262
|
}, 0);
|
|
113
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Get total estimated token count across all entries.
|
|
266
|
+
* Uses a rough approximation of 1 token ≈ 4 characters.
|
|
267
|
+
*/
|
|
268
|
+
get totalEstimatedTokens() {
|
|
269
|
+
return this._entries.reduce((total, { __metadata }) => {
|
|
270
|
+
return total + __metadata.estimatedTokens;
|
|
271
|
+
}, 0);
|
|
272
|
+
}
|
|
114
273
|
/**
|
|
115
274
|
* Get the last entry
|
|
116
275
|
*/
|
|
@@ -133,11 +292,52 @@ class History extends events_1.default {
|
|
|
133
292
|
.join("\n");
|
|
134
293
|
}
|
|
135
294
|
/**
|
|
136
|
-
* Get entries without system messages
|
|
295
|
+
* Get entries without system messages, with transform plugins applied.
|
|
137
296
|
*/
|
|
138
297
|
getMessagesWithoutSystem() {
|
|
139
|
-
return this.
|
|
298
|
+
return this.getEntries().filter((e) => e.role !== "system");
|
|
140
299
|
}
|
|
300
|
+
// ===========================================================================
|
|
301
|
+
// Async reduction
|
|
302
|
+
// ===========================================================================
|
|
303
|
+
/**
|
|
304
|
+
* Asynchronously compact history using registered reduce plugins.
|
|
305
|
+
*
|
|
306
|
+
* Plugins are called in registration order, each receiving and returning
|
|
307
|
+
* the full entry array. If no plugin has a `reduce` hook, this is a no-op —
|
|
308
|
+
* the addEntry() safety net (FIFO drop via maxTokens) runs independently.
|
|
309
|
+
*
|
|
310
|
+
* Re-entrant calls during an in-progress reduce() return immediately.
|
|
311
|
+
*
|
|
312
|
+
* @example Rolling summarization
|
|
313
|
+
* ```typescript
|
|
314
|
+
* history.use(compressionPlugin(summaryAgent));
|
|
315
|
+
* await history.reduce({ maxTokens: 4000 });
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
async reduce(options = {}) {
|
|
319
|
+
if (this._reducing)
|
|
320
|
+
return;
|
|
321
|
+
const hasReducePlugin = this._plugins.some((p) => Boolean(p.reduce));
|
|
322
|
+
if (!hasReducePlugin)
|
|
323
|
+
return;
|
|
324
|
+
this._reducing = true;
|
|
325
|
+
try {
|
|
326
|
+
let entries = [...this._entries];
|
|
327
|
+
for (const plugin of this._plugins) {
|
|
328
|
+
if (plugin.reduce) {
|
|
329
|
+
entries = await plugin.reduce(entries, options);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
this._entries = entries;
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
this._reducing = false;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// ===========================================================================
|
|
339
|
+
// Utility
|
|
340
|
+
// ===========================================================================
|
|
141
341
|
/**
|
|
142
342
|
* Clear all history entries
|
|
143
343
|
*/
|
|
@@ -164,6 +364,25 @@ class History extends events_1.default {
|
|
|
164
364
|
clone(options) {
|
|
165
365
|
return new History(this.entries, options ?? this.options);
|
|
166
366
|
}
|
|
367
|
+
// ===========================================================================
|
|
368
|
+
// Private helpers
|
|
369
|
+
// ===========================================================================
|
|
370
|
+
/**
|
|
371
|
+
* Drop oldest non-system entries until totalEstimatedTokens fits within budget.
|
|
372
|
+
* Called synchronously from addEntry() as a safety net.
|
|
373
|
+
* The system message is always preserved.
|
|
374
|
+
*/
|
|
375
|
+
trimToTokenBudget(maxTokens) {
|
|
376
|
+
const budget = maxTokens ?? this.options.maxTokens;
|
|
377
|
+
if (!budget)
|
|
378
|
+
return;
|
|
379
|
+
while (this.totalEstimatedTokens > budget && this._entries.length > 1) {
|
|
380
|
+
const firstNonSystem = this._entries.findIndex((e) => e.role !== "system");
|
|
381
|
+
if (firstNonSystem === -1)
|
|
382
|
+
break;
|
|
383
|
+
this._entries.splice(firstNonSystem, 1);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
167
386
|
}
|
|
168
387
|
exports.History = History;
|
|
169
388
|
//# sourceMappingURL=History.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { History, resetTokenxCache, type EntryMetadata, type ReducibleEntry, type HistoryPlugin, } from "./History";
|
|
2
|
+
export { RedisHistory } from "./RedisHistory";
|
|
3
|
+
export type { HistoryEntry, MessageRole, MessageContent, TextContent, ToolUseContent, ToolResultContent, ProviderMeta, ReduceOptions, } from "./types";
|
|
4
|
+
export { text, toolUse, toolResult, textMessage, isTextContent, isToolUseContent, isToolResultContent, } from "./types";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isToolResultContent = exports.isToolUseContent = exports.isTextContent = exports.textMessage = exports.toolResult = exports.toolUse = exports.text = exports.RedisHistory = exports.resetTokenxCache = exports.History = void 0;
|
|
4
|
+
var History_1 = require("./History");
|
|
5
|
+
Object.defineProperty(exports, "History", { enumerable: true, get: function () { return History_1.History; } });
|
|
6
|
+
Object.defineProperty(exports, "resetTokenxCache", { enumerable: true, get: function () { return History_1.resetTokenxCache; } });
|
|
7
|
+
var RedisHistory_1 = require("./RedisHistory");
|
|
8
|
+
Object.defineProperty(exports, "RedisHistory", { enumerable: true, get: function () { return RedisHistory_1.RedisHistory; } });
|
|
9
|
+
var types_1 = require("./types");
|
|
10
|
+
Object.defineProperty(exports, "text", { enumerable: true, get: function () { return types_1.text; } });
|
|
11
|
+
Object.defineProperty(exports, "toolUse", { enumerable: true, get: function () { return types_1.toolUse; } });
|
|
12
|
+
Object.defineProperty(exports, "toolResult", { enumerable: true, get: function () { return types_1.toolResult; } });
|
|
13
|
+
Object.defineProperty(exports, "textMessage", { enumerable: true, get: function () { return types_1.textMessage; } });
|
|
14
|
+
Object.defineProperty(exports, "isTextContent", { enumerable: true, get: function () { return types_1.isTextContent; } });
|
|
15
|
+
Object.defineProperty(exports, "isToolUseContent", { enumerable: true, get: function () { return types_1.isToolUseContent; } });
|
|
16
|
+
Object.defineProperty(exports, "isToolResultContent", { enumerable: true, get: function () { return types_1.isToolResultContent; } });
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ReduceOptions } from "../types";
|
|
2
|
+
import type { HistoryPlugin } from "../History";
|
|
3
|
+
import type { BaseAgent } from "../../agents/BaseAgent";
|
|
4
|
+
/** Options for {@link compressionPlugin}. */
|
|
5
|
+
export type CompressionPluginOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* When set, the plugin automatically calls `history.reduce()` from its
|
|
8
|
+
* `afterAdd` hook whenever the history exceeds the given threshold.
|
|
9
|
+
* The same `ReduceOptions` object is forwarded to `reduce()`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* history.use(compressionPlugin(summaryAgent, { autoReduceWhen: { maxTokens: 6000 } }));
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
autoReduceWhen?: ReduceOptions;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Creates a rolling-summary compression plugin.
|
|
20
|
+
*
|
|
21
|
+
* When `history.reduce(options)` is called, this plugin compresses old entries
|
|
22
|
+
* into a single summary entry using the provided agent. On subsequent reduces,
|
|
23
|
+
* the existing summary is included as prior context so the agent can extend it
|
|
24
|
+
* (rolling strategy) — at most one summary entry exists at any time.
|
|
25
|
+
*
|
|
26
|
+
* The summary entry uses `role: "user"` because no LLM provider has a
|
|
27
|
+
* dedicated summary role. Content is always wrapped as
|
|
28
|
+
* `[Earlier conversation summary: ...]` so it can be detected by pattern
|
|
29
|
+
* as well as the `isSummary` metadata flag.
|
|
30
|
+
*
|
|
31
|
+
* Pass `autoReduceWhen` to trigger compression automatically after every
|
|
32
|
+
* `addEntry()` call when the given threshold is exceeded — no manual
|
|
33
|
+
* `history.reduce()` call required.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const summaryAgent = new ClaudeAgent({
|
|
38
|
+
* id: "summarizer",
|
|
39
|
+
* name: "Summarizer",
|
|
40
|
+
* description: "Summarizes conversation history",
|
|
41
|
+
* apiKey: process.env.ANTHROPIC_API_KEY!,
|
|
42
|
+
* model: "claude-haiku-4-5-20251001",
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* // Manual trigger
|
|
46
|
+
* history.use(compressionPlugin(summaryAgent));
|
|
47
|
+
* await history.reduce({ maxTokens: 4000 });
|
|
48
|
+
*
|
|
49
|
+
* // Automatic trigger
|
|
50
|
+
* history.use(compressionPlugin(summaryAgent, { autoReduceWhen: { maxTokens: 6000 } }));
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function compressionPlugin(agent: BaseAgent, options?: CompressionPluginOptions): HistoryPlugin;
|
|
54
|
+
//# sourceMappingURL=compressionPlugin.d.ts.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compressionPlugin = compressionPlugin;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
/**
|
|
6
|
+
* Creates a rolling-summary compression plugin.
|
|
7
|
+
*
|
|
8
|
+
* When `history.reduce(options)` is called, this plugin compresses old entries
|
|
9
|
+
* into a single summary entry using the provided agent. On subsequent reduces,
|
|
10
|
+
* the existing summary is included as prior context so the agent can extend it
|
|
11
|
+
* (rolling strategy) — at most one summary entry exists at any time.
|
|
12
|
+
*
|
|
13
|
+
* The summary entry uses `role: "user"` because no LLM provider has a
|
|
14
|
+
* dedicated summary role. Content is always wrapped as
|
|
15
|
+
* `[Earlier conversation summary: ...]` so it can be detected by pattern
|
|
16
|
+
* as well as the `isSummary` metadata flag.
|
|
17
|
+
*
|
|
18
|
+
* Pass `autoReduceWhen` to trigger compression automatically after every
|
|
19
|
+
* `addEntry()` call when the given threshold is exceeded — no manual
|
|
20
|
+
* `history.reduce()` call required.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const summaryAgent = new ClaudeAgent({
|
|
25
|
+
* id: "summarizer",
|
|
26
|
+
* name: "Summarizer",
|
|
27
|
+
* description: "Summarizes conversation history",
|
|
28
|
+
* apiKey: process.env.ANTHROPIC_API_KEY!,
|
|
29
|
+
* model: "claude-haiku-4-5-20251001",
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // Manual trigger
|
|
33
|
+
* history.use(compressionPlugin(summaryAgent));
|
|
34
|
+
* await history.reduce({ maxTokens: 4000 });
|
|
35
|
+
*
|
|
36
|
+
* // Automatic trigger
|
|
37
|
+
* history.use(compressionPlugin(summaryAgent, { autoReduceWhen: { maxTokens: 6000 } }));
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
function compressionPlugin(agent, options = {}) {
|
|
41
|
+
const { autoReduceWhen } = options;
|
|
42
|
+
function shouldAutoReduce(history) {
|
|
43
|
+
if (!autoReduceWhen)
|
|
44
|
+
return false;
|
|
45
|
+
if (autoReduceWhen.maxTokens !== undefined) {
|
|
46
|
+
return history.totalEstimatedTokens > autoReduceWhen.maxTokens;
|
|
47
|
+
}
|
|
48
|
+
if (autoReduceWhen.maxEntries !== undefined) {
|
|
49
|
+
return history.length > autoReduceWhen.maxEntries;
|
|
50
|
+
}
|
|
51
|
+
// olderThan: always trigger so reduce() can evaluate
|
|
52
|
+
if (autoReduceWhen.olderThan !== undefined)
|
|
53
|
+
return true;
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
afterAdd(history) {
|
|
58
|
+
if (!autoReduceWhen)
|
|
59
|
+
return;
|
|
60
|
+
if (!shouldAutoReduce(history))
|
|
61
|
+
return;
|
|
62
|
+
// Fire-and-forget — History._reducing guard prevents re-entrancy
|
|
63
|
+
void history.reduce(autoReduceWhen);
|
|
64
|
+
},
|
|
65
|
+
async reduce(entries, options) {
|
|
66
|
+
const { maxTokens, maxEntries, olderThan } = options;
|
|
67
|
+
// Separate system entries — always preserved verbatim
|
|
68
|
+
const systemEntries = entries.filter((e) => e.role === "system");
|
|
69
|
+
const nonSystemEntries = entries.filter((e) => e.role !== "system");
|
|
70
|
+
if (nonSystemEntries.length === 0)
|
|
71
|
+
return entries;
|
|
72
|
+
// Determine which non-system entries to compress
|
|
73
|
+
let toCompress = [];
|
|
74
|
+
if (olderThan) {
|
|
75
|
+
const cutoff = olderThan.toISOString();
|
|
76
|
+
toCompress = nonSystemEntries.filter((e) => e.__metadata.date < cutoff);
|
|
77
|
+
}
|
|
78
|
+
else if (maxEntries !== undefined) {
|
|
79
|
+
const totalNonSystem = nonSystemEntries.length;
|
|
80
|
+
if (totalNonSystem <= maxEntries)
|
|
81
|
+
return entries;
|
|
82
|
+
toCompress = nonSystemEntries.slice(0, totalNonSystem - maxEntries);
|
|
83
|
+
}
|
|
84
|
+
else if (maxTokens !== undefined) {
|
|
85
|
+
// Accumulate from oldest until we'd fit within budget
|
|
86
|
+
let totalTokens = entries.reduce((sum, e) => sum + e.__metadata.estimatedTokens, 0);
|
|
87
|
+
let i = 0;
|
|
88
|
+
while (totalTokens > maxTokens && i < nonSystemEntries.length) {
|
|
89
|
+
toCompress.push(nonSystemEntries[i]);
|
|
90
|
+
totalTokens -= nonSystemEntries[i].__metadata.estimatedTokens;
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (toCompress.length === 0)
|
|
95
|
+
return entries;
|
|
96
|
+
// Build prompt — include existing rolling summary as prior context
|
|
97
|
+
const existingSummary = toCompress.find((e) => e.__metadata.isSummary);
|
|
98
|
+
const rawToCompress = toCompress.filter((e) => !e.__metadata.isSummary);
|
|
99
|
+
let prompt = "Produce a concise summary of the following conversation. " +
|
|
100
|
+
"Preserve key facts, decisions, and outcomes. " +
|
|
101
|
+
"Omit filler and repetition.\n\n";
|
|
102
|
+
if (existingSummary) {
|
|
103
|
+
const summaryText = existingSummary.content
|
|
104
|
+
.filter(types_1.isTextContent)
|
|
105
|
+
.map((c) => c.text)
|
|
106
|
+
.join("\n");
|
|
107
|
+
prompt += `Prior context:\n${summaryText}\n\nAdditional turns to incorporate:\n`;
|
|
108
|
+
}
|
|
109
|
+
for (const entry of rawToCompress) {
|
|
110
|
+
const lines = entry.content
|
|
111
|
+
.filter(types_1.isTextContent)
|
|
112
|
+
.map((c) => c.text)
|
|
113
|
+
.join(" ");
|
|
114
|
+
prompt += `[${entry.role}]: ${lines}\n`;
|
|
115
|
+
}
|
|
116
|
+
const summaryText = await agent.execute(prompt);
|
|
117
|
+
// Determine date range covered by this summary
|
|
118
|
+
const allCompressed = existingSummary
|
|
119
|
+
? [existingSummary, ...rawToCompress]
|
|
120
|
+
: rawToCompress;
|
|
121
|
+
const earliestDate = existingSummary?.__metadata.coversRange?.from ??
|
|
122
|
+
allCompressed[0].__metadata.date;
|
|
123
|
+
const latestDate = allCompressed[allCompressed.length - 1].__metadata.date;
|
|
124
|
+
const content = `[Earlier conversation summary: ${summaryText}]`;
|
|
125
|
+
const __metadata = {
|
|
126
|
+
date: new Date().toISOString(),
|
|
127
|
+
contentLength: content.length,
|
|
128
|
+
estimatedTokens: Math.ceil(content.length / 4),
|
|
129
|
+
isSummary: true,
|
|
130
|
+
coversRange: { from: earliestDate, to: latestDate },
|
|
131
|
+
};
|
|
132
|
+
const summaryEntry = {
|
|
133
|
+
role: "user",
|
|
134
|
+
content: [(0, types_1.text)(content)],
|
|
135
|
+
__metadata,
|
|
136
|
+
};
|
|
137
|
+
const toCompressSet = new Set(toCompress);
|
|
138
|
+
const recent = nonSystemEntries.filter((e) => !toCompressSet.has(e));
|
|
139
|
+
return [...systemEntries, summaryEntry, ...recent];
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=compressionPlugin.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toolResultMaskingPlugin = exports.compressionPlugin = void 0;
|
|
4
|
+
var compressionPlugin_1 = require("./compressionPlugin");
|
|
5
|
+
Object.defineProperty(exports, "compressionPlugin", { enumerable: true, get: function () { return compressionPlugin_1.compressionPlugin; } });
|
|
6
|
+
var toolResultMaskingPlugin_1 = require("./toolResultMaskingPlugin");
|
|
7
|
+
Object.defineProperty(exports, "toolResultMaskingPlugin", { enumerable: true, get: function () { return toolResultMaskingPlugin_1.toolResultMaskingPlugin; } });
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Tool } from "../../tools/Tool";
|
|
2
|
+
import type { HistoryPlugin } from "../History";
|
|
3
|
+
export type ToolResultMaskingOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* Number of most-recent tool results to keep verbatim.
|
|
6
|
+
* Older results are replaced with a reference marker.
|
|
7
|
+
* @default 2
|
|
8
|
+
*/
|
|
9
|
+
keepRecentResults?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Tool names that are never masked, regardless of age or size.
|
|
12
|
+
* Mutually exclusive with `include`.
|
|
13
|
+
*/
|
|
14
|
+
exclude?: string[];
|
|
15
|
+
/**
|
|
16
|
+
* When set, only results from these tools are masked.
|
|
17
|
+
* Mutually exclusive with `exclude`.
|
|
18
|
+
*/
|
|
19
|
+
include?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Skip masking for results whose estimated token count is below this
|
|
22
|
+
* threshold. Small results don't consume a `keepRecentResults` slot.
|
|
23
|
+
*/
|
|
24
|
+
minTokensToMask?: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* A HistoryPlugin with an attached `retrieveTool`.
|
|
28
|
+
* Register the plugin with `history.use(maskingPlugin)`, then add
|
|
29
|
+
* `maskingPlugin.retrieveTool` to the agent's tool list so the model can
|
|
30
|
+
* fetch masked results on demand.
|
|
31
|
+
*/
|
|
32
|
+
export type ToolResultMaskingPlugin = HistoryPlugin & {
|
|
33
|
+
/** Tool the agent can call to retrieve a masked result by its tool_use_id. */
|
|
34
|
+
readonly retrieveTool: Tool<string>;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Creates a read-time tool result masking plugin.
|
|
38
|
+
*
|
|
39
|
+
* Old tool results are replaced with a reference marker `[MASKED - ref: <id>]`
|
|
40
|
+
* at read time via `history.getEntries()`. Stored entries are never mutated —
|
|
41
|
+
* the full content is always available via `history.getToolResult(id)` or the
|
|
42
|
+
* attached `retrieveTool`.
|
|
43
|
+
*
|
|
44
|
+
* Only results that pass the include/exclude/minTokensToMask filters are
|
|
45
|
+
* candidates for masking. Filtered-out results stay verbatim and do not
|
|
46
|
+
* consume a `keepRecentResults` slot.
|
|
47
|
+
*
|
|
48
|
+
* @throws If both `exclude` and `include` are provided.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const maskingPlugin = toolResultMaskingPlugin({
|
|
53
|
+
* keepRecentResults: 2,
|
|
54
|
+
* exclude: ["calculator", "get_date"],
|
|
55
|
+
* minTokensToMask: 50,
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* history.use(maskingPlugin);
|
|
59
|
+
*
|
|
60
|
+
* const agent = new ClaudeAgent({
|
|
61
|
+
* tools: [maskingPlugin.retrieveTool, ...otherTools],
|
|
62
|
+
* }, history);
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function toolResultMaskingPlugin(options?: ToolResultMaskingOptions): ToolResultMaskingPlugin;
|
|
66
|
+
//# sourceMappingURL=toolResultMaskingPlugin.d.ts.map
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toolResultMaskingPlugin = toolResultMaskingPlugin;
|
|
4
|
+
const Tool_1 = require("../../tools/Tool");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
/**
|
|
7
|
+
* Creates a read-time tool result masking plugin.
|
|
8
|
+
*
|
|
9
|
+
* Old tool results are replaced with a reference marker `[MASKED - ref: <id>]`
|
|
10
|
+
* at read time via `history.getEntries()`. Stored entries are never mutated —
|
|
11
|
+
* the full content is always available via `history.getToolResult(id)` or the
|
|
12
|
+
* attached `retrieveTool`.
|
|
13
|
+
*
|
|
14
|
+
* Only results that pass the include/exclude/minTokensToMask filters are
|
|
15
|
+
* candidates for masking. Filtered-out results stay verbatim and do not
|
|
16
|
+
* consume a `keepRecentResults` slot.
|
|
17
|
+
*
|
|
18
|
+
* @throws If both `exclude` and `include` are provided.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const maskingPlugin = toolResultMaskingPlugin({
|
|
23
|
+
* keepRecentResults: 2,
|
|
24
|
+
* exclude: ["calculator", "get_date"],
|
|
25
|
+
* minTokensToMask: 50,
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* history.use(maskingPlugin);
|
|
29
|
+
*
|
|
30
|
+
* const agent = new ClaudeAgent({
|
|
31
|
+
* tools: [maskingPlugin.retrieveTool, ...otherTools],
|
|
32
|
+
* }, history);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
function toolResultMaskingPlugin(options) {
|
|
36
|
+
const { keepRecentResults = 2, exclude, include, minTokensToMask, } = options ?? {};
|
|
37
|
+
if (exclude && include) {
|
|
38
|
+
throw new Error("toolResultMaskingPlugin: `exclude` and `include` are mutually exclusive");
|
|
39
|
+
}
|
|
40
|
+
if (keepRecentResults === 0) {
|
|
41
|
+
console.warn("[toolResultMaskingPlugin] keepRecentResults: 0 masks all tool results. " +
|
|
42
|
+
"Ensure maskingPlugin.retrieveTool is added to the agent's tool list.");
|
|
43
|
+
}
|
|
44
|
+
// History reference captured at registration time via onRegistered
|
|
45
|
+
let _history;
|
|
46
|
+
const retrieveTool = new Tool_1.Tool({
|
|
47
|
+
name: "retrieve_tool_result",
|
|
48
|
+
description: "Retrieve the full result of a previous tool call that has been masked " +
|
|
49
|
+
"in the conversation history. Use this when you need to re-examine an " +
|
|
50
|
+
"earlier tool result.",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
tool_call_id: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "The tool_use_id of the masked tool result to retrieve",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ["tool_call_id"],
|
|
60
|
+
},
|
|
61
|
+
execute: async (input) => {
|
|
62
|
+
if (!_history) {
|
|
63
|
+
throw new Error("retrieve_tool_result: plugin has not been registered with a history " +
|
|
64
|
+
"instance. Call history.use(maskingPlugin) before executing the agent.");
|
|
65
|
+
}
|
|
66
|
+
const result = _history.getToolResult(input.tool_call_id);
|
|
67
|
+
if (result === undefined) {
|
|
68
|
+
return `No tool result found for id: ${input.tool_call_id}`;
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
const plugin = {
|
|
74
|
+
get retrieveTool() {
|
|
75
|
+
return retrieveTool;
|
|
76
|
+
},
|
|
77
|
+
onRegistered(history) {
|
|
78
|
+
_history = history;
|
|
79
|
+
},
|
|
80
|
+
transform(entries) {
|
|
81
|
+
// First pass: build a map of tool_use_id → tool_name from tool_use blocks
|
|
82
|
+
const toolNameById = new Map();
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
for (const block of entry.content) {
|
|
85
|
+
if ((0, types_1.isToolUseContent)(block)) {
|
|
86
|
+
toolNameById.set(block.id, block.name);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const maskable = [];
|
|
91
|
+
for (let ei = 0; ei < entries.length; ei++) {
|
|
92
|
+
const entry = entries[ei];
|
|
93
|
+
for (let bi = 0; bi < entry.content.length; bi++) {
|
|
94
|
+
const block = entry.content[bi];
|
|
95
|
+
if (!(0, types_1.isToolResultContent)(block))
|
|
96
|
+
continue;
|
|
97
|
+
const toolName = toolNameById.get(block.tool_use_id) ?? "";
|
|
98
|
+
// include mode: only mask listed tools
|
|
99
|
+
if (include && !include.includes(toolName))
|
|
100
|
+
continue;
|
|
101
|
+
// exclude mode: skip listed tools
|
|
102
|
+
if (exclude && exclude.includes(toolName))
|
|
103
|
+
continue;
|
|
104
|
+
// minTokensToMask: skip small results
|
|
105
|
+
if (minTokensToMask !== undefined) {
|
|
106
|
+
const estimatedTokens = Math.ceil(block.content.length / 4);
|
|
107
|
+
if (estimatedTokens < minTokensToMask)
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
maskable.push({ entryIdx: ei, blockIdx: bi, id: block.tool_use_id });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Determine which maskable results to mask (all but the last N)
|
|
114
|
+
const toMask = new Set();
|
|
115
|
+
const keepFrom = Math.max(0, maskable.length - keepRecentResults);
|
|
116
|
+
for (let i = 0; i < keepFrom; i++) {
|
|
117
|
+
toMask.add(maskable[i].id);
|
|
118
|
+
}
|
|
119
|
+
if (toMask.size === 0)
|
|
120
|
+
return entries;
|
|
121
|
+
// Third pass: build new entry array with masked content (never mutate originals)
|
|
122
|
+
return entries.map((entry) => {
|
|
123
|
+
const needsChange = entry.content.some((block) => (0, types_1.isToolResultContent)(block) && toMask.has(block.tool_use_id));
|
|
124
|
+
if (!needsChange)
|
|
125
|
+
return entry;
|
|
126
|
+
const newContent = entry.content.map((block) => {
|
|
127
|
+
if ((0, types_1.isToolResultContent)(block) && toMask.has(block.tool_use_id)) {
|
|
128
|
+
return {
|
|
129
|
+
...block,
|
|
130
|
+
content: `[MASKED - ref: ${block.tool_use_id}]`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return block;
|
|
134
|
+
});
|
|
135
|
+
return { ...entry, content: newContent };
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
return plugin;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=toolResultMaskingPlugin.js.map
|
package/dist/history/types.d.ts
CHANGED
|
@@ -127,4 +127,16 @@ export declare function toolResult(tool_use_id: string, content: string, is_erro
|
|
|
127
127
|
* Create a simple text message entry
|
|
128
128
|
*/
|
|
129
129
|
export declare function textMessage(role: MessageRole, value: string): HistoryEntry;
|
|
130
|
+
/**
|
|
131
|
+
* Options controlling how history.reduce() compacts stored entries.
|
|
132
|
+
* All fields are optional — supply whichever constraints apply.
|
|
133
|
+
*/
|
|
134
|
+
export type ReduceOptions = {
|
|
135
|
+
/** Compress/drop entries until total estimated tokens fall below this value. */
|
|
136
|
+
maxTokens?: number;
|
|
137
|
+
/** Compress/drop entries until the entry count falls below this value. */
|
|
138
|
+
maxEntries?: number;
|
|
139
|
+
/** Compress/drop entries whose timestamp predates this date. */
|
|
140
|
+
olderThan?: Date;
|
|
141
|
+
};
|
|
130
142
|
//# sourceMappingURL=types.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentionai/agents",
|
|
3
3
|
"author": "Laurent Zuijdwijk",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"description": "Agent Library",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -53,6 +53,14 @@
|
|
|
53
53
|
"./ingestion": {
|
|
54
54
|
"types": "./dist/ingestion/index.d.ts",
|
|
55
55
|
"default": "./dist/ingestion/index.js"
|
|
56
|
+
},
|
|
57
|
+
"./history": {
|
|
58
|
+
"types": "./dist/history/index.d.ts",
|
|
59
|
+
"default": "./dist/history/index.js"
|
|
60
|
+
},
|
|
61
|
+
"./history/plugins": {
|
|
62
|
+
"types": "./dist/history/plugins/index.d.ts",
|
|
63
|
+
"default": "./dist/history/plugins/index.js"
|
|
56
64
|
}
|
|
57
65
|
},
|
|
58
66
|
"files": [
|