@agentionai/agents 0.8.1 → 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.
@@ -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 */
@@ -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 = new History_1.History([], { transient: true })) {
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.entries;
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.entries);
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.entries);
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.entries);
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.entries);
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.entries);
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.entries);
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.entries);
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.entries);
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
- export type { HistoryEntry, MessageRole, MessageContent } from "./types";
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
- * Internal entry with metadata
9
+ * Metadata stored alongside each history entry.
10
+ * Extended with summary tracking fields for the compression plugin.
7
11
  */
8
- type EntryWithMetadata = HistoryEntry & {
9
- __metadata: {
10
- date: string;
11
- contentLength: number;
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 all entries (without internal metadata)
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
@@ -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: JSON.stringify(entry.content).length,
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 (without internal metadata)
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.entries.filter((e) => e.role !== "system");
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,3 @@
1
+ export { compressionPlugin, type CompressionPluginOptions } from "./compressionPlugin";
2
+ export { toolResultMaskingPlugin, type ToolResultMaskingPlugin, type ToolResultMaskingOptions, } from "./toolResultMaskingPlugin";
3
+ //# sourceMappingURL=index.d.ts.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
@@ -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.8.1",
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": [