@agentionai/agents 0.8.1 → 0.10.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/README.md CHANGED
@@ -73,6 +73,7 @@ import { ClaudeAgent, OpenAiAgent } from '@agentionai/agents';
73
73
 
74
74
  - **Multi-Provider, No Lock-in** - Claude, OpenAI, Gemini, Mistral—same interface. Switch models with one line.
75
75
  - **Composable, Not Magical** - Agents are objects. Pipelines are arrays. No hidden state, no surprises.
76
+ - **Multimodal / Vision** - Send images alongside text with a unified `MessageContent[]` API across all providers.
76
77
  - **Full Observability** - Per-call token counts, execution timing, pipeline structure visualization.
77
78
  - **TypeScript-Native** - Strict typing, interfaces, and generics from the ground up.
78
79
  - **RAG Ready** - LanceDB vector store, token-aware chunking, ingestion pipeline out of the box.
@@ -175,6 +176,43 @@ const researcher = new ClaudeAgent({
175
176
  const result = await researcher.execute('Latest developments in quantum computing');
176
177
  ```
177
178
 
179
+ ### Multimodal / Vision
180
+
181
+ Send images alongside text using `imageUrl()` or `imageBase64()`. The same `MessageContent[]` interface works across all providers:
182
+
183
+ ```typescript
184
+ import { ClaudeAgent } from '@agentionai/agents/claude';
185
+ import { imageUrl, imageBase64 } from '@agentionai/agents/core';
186
+ import * as fs from 'fs';
187
+
188
+ const agent = new ClaudeAgent({
189
+ apiKey: process.env.ANTHROPIC_API_KEY,
190
+ model: 'claude-opus-4-6',
191
+ name: 'VisionAgent',
192
+ description: 'You analyze images.',
193
+ });
194
+
195
+ // Remote image by URL
196
+ const response = await agent.execute([
197
+ imageUrl('https://example.com/chart.png'),
198
+ { type: 'text', text: 'Summarize this chart in one sentence.' },
199
+ ]);
200
+
201
+ // Local image as base64
202
+ const data = fs.readFileSync('./photo.jpg').toString('base64');
203
+ const response2 = await agent.execute([
204
+ imageBase64(data, 'image/jpeg'),
205
+ { type: 'text', text: 'What plant is this?' },
206
+ ]);
207
+ ```
208
+
209
+ | Provider | URL | Base64 |
210
+ |----------|:---:|:------:|
211
+ | Claude | ✅ | ✅ |
212
+ | OpenAI | ✅ | ✅ |
213
+ | Gemini | ✅ | ✅ |
214
+ | Mistral | ✅ | ❌ |
215
+
178
216
  ## Core Concepts
179
217
 
180
218
  ### Agents
@@ -187,6 +225,11 @@ JSON Schema + handler pattern. Unique capability: wrap any agent as a tool for d
187
225
 
188
226
  [Learn more →](https://docs.agention.ai/guide/tools)
189
227
 
228
+ ### Multimodal / Vision
229
+ Unified `MessageContent[]` interface for images across all providers. URL and base64 images, mix text and images freely in a single call.
230
+
231
+ [Learn more →](https://docs.agention.ai/guide/multimodal)
232
+
190
233
  ### History
191
234
  Provider-agnostic, persistent (Redis, file, custom), shareable across agents of different providers.
192
235
 
@@ -213,6 +256,7 @@ Per-call and per-node token counts, duration metrics, full execution visibility.
213
256
  - **[Quick Start](https://docs.agention.ai/guide/quickstart)** - Build a weather assistant in 5 minutes
214
257
  - **[Agents](https://docs.agention.ai/guide/agents)** - Agent configuration and providers
215
258
  - **[Tools](https://docs.agention.ai/guide/tools)** - Adding capabilities and agent delegation
259
+ - **[Multimodal / Vision](https://docs.agention.ai/guide/multimodal)** - Sending images across all providers
216
260
  - **[Graph Pipelines](https://docs.agention.ai/guide/graph-pipelines)** - Multi-agent workflows
217
261
  - **[Vector Stores](https://docs.agention.ai/guide/vector-stores)** - RAG and semantic search
218
262
  - **[Examples](https://docs.agention.ai/guide/examples)** - Real-world implementations
@@ -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 */
@@ -1,8 +1,8 @@
1
1
  import EventEmitter from "events";
2
2
  import { Tool } from "../tools/Tool";
3
- import { History, HistoryEntry, MessageRole, MessageContent } from "../history/History";
3
+ import { History, HistoryEntry, MessageRole, MessageContent, ImageMimeType } from "../history/History";
4
4
  import { AgentVendor, CommonAgentConfig, VendorSpecificConfig } from "./AgentConfig";
5
- export type { HistoryEntry, MessageRole, MessageContent };
5
+ export type { HistoryEntry, MessageRole, MessageContent, ImageMimeType };
6
6
  export type { AgentVendor };
7
7
  /**
8
8
  * Agent config as used across all agents
@@ -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()];
@@ -1,7 +1,7 @@
1
1
  import { Message, Usage } from "@anthropic-ai/sdk/resources";
2
2
  import { type ToolDefinition } from "../../tools/Tool";
3
3
  import { BaseAgent, BaseAgentConfig, TokenUsage } from "../BaseAgent";
4
- import { History } from "../../history/History";
4
+ import { History, MessageContent } from "../../history/History";
5
5
  import { ClaudeModel } from "../model-types";
6
6
  type AgentConfig = BaseAgentConfig & {
7
7
  apiKey: string;
@@ -37,7 +37,7 @@ export declare class ClaudeAgent extends BaseAgent {
37
37
  constructor(config: Omit<AgentConfig, "vendor">, history?: History);
38
38
  protected getToolDefinitions(): ToolDefinition[];
39
39
  protected process(_input: string): Promise<string>;
40
- execute(input: string): Promise<string>;
40
+ execute(input: string | MessageContent[]): Promise<string>;
41
41
  protected handleResponse(response: Message): Promise<string>;
42
42
  private handleToolUse;
43
43
  protected parseUsage(input: Usage): TokenUsage;
@@ -64,18 +64,25 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
64
64
  // Reset token usage for this execution
65
65
  this.lastTokenUsage = undefined;
66
66
  this.currentToolCallCount = 0;
67
+ // Normalise input to a display string for viz reporting
68
+ const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
67
69
  // Start visualization reporting
68
70
  if (VizConfig_1.vizConfig.isEnabled()) {
69
- this.vizEventId = VizReporter_1.vizReporter.agentStart(this.id, this.name, this.config.model, "anthropic", input);
71
+ this.vizEventId = VizReporter_1.vizReporter.agentStart(this.id, this.name, this.config.model, "anthropic", inputPreview);
70
72
  }
71
73
  if (this.history.transient) {
72
74
  this.history.clear();
73
75
  // Re-add system message after clear
74
76
  this.addSystemMessage(this.getSystemMessage());
75
77
  }
76
- this.addTextToHistory("user", input);
78
+ if (typeof input === "string") {
79
+ this.addTextToHistory("user", input);
80
+ }
81
+ else {
82
+ this.addMessageToHistory("user", input);
83
+ }
77
84
  try {
78
- const messages = transformers_1.anthropicTransformer.toProvider(this.history.entries);
85
+ const messages = transformers_1.anthropicTransformer.toProvider(this.history.getEntries());
79
86
  const systemMessage = this.history.getSystemMessage();
80
87
  const response = await this.client.messages.create({
81
88
  model: this.config.model,
@@ -177,9 +184,10 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
177
184
  this.addMessageToHistory("user", toolResults);
178
185
  // Continue conversation with tool results
179
186
  try {
180
- const messages = transformers_1.anthropicTransformer.toProvider(this.history.entries);
187
+ const messages = transformers_1.anthropicTransformer.toProvider(this.history.getEntries());
181
188
  const newResponse = await this.client.messages.create({
182
189
  model: this.config.model,
190
+ system: this.history.getSystemMessage(),
183
191
  max_tokens: this.config.maxTokens,
184
192
  messages,
185
193
  tools: this.getToolDefinitions(),
@@ -224,6 +232,12 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
224
232
  }
225
233
  // Track tool call count for viz reporting
226
234
  this.currentToolCallCount += toolUseBlocks.length;
235
+ const agentSource = {
236
+ agentId: this.getId(),
237
+ agentName: this.getName(),
238
+ model: this.config.model,
239
+ vendor: "anthropic",
240
+ };
227
241
  const results = await Promise.all(toolUseBlocks.map(async (block) => {
228
242
  const tool = this.tools.get(block.name);
229
243
  if (!tool) {
@@ -232,10 +246,20 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
232
246
  if (this.debug) {
233
247
  console.error(error);
234
248
  }
249
+ if (VizConfig_1.vizConfig.isEnabled()) {
250
+ const vizEventId = VizReporter_1.vizReporter.toolStart(block.name, block.id, block.input, agentSource);
251
+ VizReporter_1.vizReporter.toolError(vizEventId, block.name, block.id, errorMessage);
252
+ }
235
253
  return (0, History_1.toolResult)(block.id, errorMessage, true);
236
254
  }
255
+ const vizEventId = VizConfig_1.vizConfig.isEnabled()
256
+ ? VizReporter_1.vizReporter.toolStart(block.name, block.id, block.input, agentSource)
257
+ : undefined;
237
258
  try {
238
259
  const result = await tool.execute(this.getId(), this.getName(), block.input, block.id, this.config.model, "anthropic");
260
+ if (vizEventId) {
261
+ VizReporter_1.vizReporter.toolComplete(vizEventId, block.name, block.id, true, result);
262
+ }
239
263
  return (0, History_1.toolResult)(block.id, JSON.stringify(result));
240
264
  }
241
265
  catch (error) {
@@ -245,6 +269,9 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
245
269
  }
246
270
  const toolError = new AgentError_1.ToolExecutionError(errorMessage, block.name, block.input);
247
271
  this.emit(AgentEvent_1.AgentEvent.TOOL_ERROR, toolError);
272
+ if (vizEventId) {
273
+ VizReporter_1.vizReporter.toolError(vizEventId, block.name, block.id, errorMessage);
274
+ }
248
275
  return (0, History_1.toolResult)(block.id, errorMessage, true);
249
276
  }
250
277
  }));
@@ -1,6 +1,6 @@
1
1
  import { FunctionDeclarationsTool, GenerateContentResult, Schema } from "@google/generative-ai";
2
2
  import { BaseAgent, BaseAgentConfig, TokenUsage } from "../BaseAgent";
3
- import { History } from "../../history/History";
3
+ import { History, MessageContent } from "../../history/History";
4
4
  import { GeminiModel } from "../model-types";
5
5
  type AgentConfig = BaseAgentConfig & {
6
6
  apiKey: string;
@@ -50,7 +50,7 @@ export declare class GeminiAgent extends BaseAgent {
50
50
  */
51
51
  private mapJsonSchemaTypeToGemini;
52
52
  protected process(_input: string): Promise<string>;
53
- execute(input: string): Promise<string>;
53
+ execute(input: string | MessageContent[]): Promise<string>;
54
54
  protected handleResponse(response: GenerateContentResult): Promise<string>;
55
55
  private handleFunctionCalls;
56
56
  protected parseUsage(input: {
@@ -165,18 +165,24 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
165
165
  // Reset token usage for this execution
166
166
  this.lastTokenUsage = undefined;
167
167
  this.currentToolCallCount = 0;
168
+ const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
168
169
  // Start visualization reporting
169
170
  if (VizConfig_1.vizConfig.isEnabled()) {
170
- this.vizEventId = VizReporter_1.vizReporter.agentStart(this.id, this.name, this.config.model, "gemini", input);
171
+ this.vizEventId = VizReporter_1.vizReporter.agentStart(this.id, this.name, this.config.model, "gemini", inputPreview);
171
172
  }
172
173
  if (this.history.transient) {
173
174
  this.history.clear();
174
175
  // Re-add system message after clear
175
176
  this.addSystemMessage(this.getSystemMessage());
176
177
  }
177
- this.addTextToHistory("user", input);
178
+ if (typeof input === "string") {
179
+ this.addTextToHistory("user", input);
180
+ }
181
+ else {
182
+ this.addMessageToHistory("user", input);
183
+ }
178
184
  try {
179
- const contents = transformers_1.geminiTransformer.toProvider(this.history.entries);
185
+ const contents = transformers_1.geminiTransformer.toProvider(this.history.getEntries());
180
186
  const systemMessage = this.history.getSystemMessage();
181
187
  const tools = this.getToolDefinitionsForGemini();
182
188
  const response = await this.generativeModel.generateContent({
@@ -290,7 +296,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
290
296
  }
291
297
  // Continue conversation
292
298
  try {
293
- const newContents = transformers_1.geminiTransformer.toProvider(this.history.entries);
299
+ const newContents = transformers_1.geminiTransformer.toProvider(this.history.getEntries());
294
300
  const systemMessage = this.history.getSystemMessage();
295
301
  const tools = this.getToolDefinitionsForGemini();
296
302
  const newResponse = await this.generativeModel.generateContent({
@@ -1,5 +1,5 @@
1
1
  import { BaseAgent, BaseAgentConfig, TokenUsage } from "../BaseAgent";
2
- import { History } from "../../history/History";
2
+ import { History, MessageContent } from "../../history/History";
3
3
  import { ChatCompletionResponse, Tool, UsageInfo } from "@mistralai/mistralai/models/components";
4
4
  import { MistralModel } from "../model-types";
5
5
  type AgentConfig = BaseAgentConfig & {
@@ -38,7 +38,7 @@ export declare class MistralAgent extends BaseAgent {
38
38
  constructor(config: Omit<AgentConfig, "vendor">, history?: History);
39
39
  protected getToolDefinitions(): Tool[];
40
40
  protected process(_input: string): Promise<string>;
41
- execute(input: string): Promise<string>;
41
+ execute(input: string | MessageContent[]): Promise<string>;
42
42
  protected handleResponse(response: ChatCompletionResponse): Promise<string>;
43
43
  private handleToolCalls;
44
44
  protected parseUsage(input: UsageInfo): TokenUsage;
@@ -75,18 +75,24 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
75
75
  // Reset token usage for this execution
76
76
  this.lastTokenUsage = undefined;
77
77
  this.currentToolCallCount = 0;
78
+ const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
78
79
  // Start visualization reporting
79
80
  if (VizConfig_1.vizConfig.isEnabled()) {
80
- this.vizEventId = VizReporter_1.vizReporter.agentStart(this.id, this.name, this.config.model, "mistral", input);
81
+ this.vizEventId = VizReporter_1.vizReporter.agentStart(this.id, this.name, this.config.model, "mistral", inputPreview);
81
82
  }
82
83
  if (this.history.transient) {
83
84
  this.history.clear();
84
85
  // Re-add system message after clear
85
86
  this.addSystemMessage(this.getSystemMessage());
86
87
  }
87
- this.addTextToHistory("user", input);
88
+ if (typeof input === "string") {
89
+ this.addTextToHistory("user", input);
90
+ }
91
+ else {
92
+ this.addMessageToHistory("user", input);
93
+ }
88
94
  try {
89
- const messages = transformers_1.mistralTransformer.toProvider(this.history.entries);
95
+ const messages = transformers_1.mistralTransformer.toProvider(this.history.getEntries());
90
96
  const response = await this.client.chat.complete({
91
97
  model: this.config.model,
92
98
  messages: messages,
@@ -202,7 +208,7 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
202
208
  await (0, promises_1.setTimeout)(this.config.rateLimitDelay || 1500);
203
209
  // Continue conversation
204
210
  try {
205
- const messages = transformers_1.mistralTransformer.toProvider(this.history.entries);
211
+ const messages = transformers_1.mistralTransformer.toProvider(this.history.getEntries());
206
212
  const newResponse = await this.client.chat.complete({
207
213
  model: this.config.model,
208
214
  messages: messages,
@@ -1,5 +1,5 @@
1
1
  import { BaseAgent, BaseAgentConfig, TokenUsage } from "../BaseAgent";
2
- import { History } from "../../history/History";
2
+ import { History, MessageContent } from "../../history/History";
3
3
  import { Tool, Response, ResponseUsage } from "openai/resources/responses/responses";
4
4
  import { OpenAIModel } from "../model-types";
5
5
  type AgentConfig = BaseAgentConfig & {
@@ -39,7 +39,7 @@ export declare class OpenAiAgent extends BaseAgent {
39
39
  constructor(config: Omit<AgentConfig, "vendor">, history?: History);
40
40
  protected getToolDefinitions(): Tool[];
41
41
  protected process(_input: string): Promise<string>;
42
- execute(input: string): Promise<string>;
42
+ execute(input: string | MessageContent[]): Promise<string>;
43
43
  protected handleResponse(response: Response): Promise<string>;
44
44
  private handleToolUse;
45
45
  protected parseUsage(input: ResponseUsage): TokenUsage;
@@ -86,18 +86,24 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
86
86
  // Reset token usage for this execution
87
87
  this.lastTokenUsage = undefined;
88
88
  this.currentToolCallCount = 0;
89
+ const inputPreview = typeof input === "string" ? input : JSON.stringify(input);
89
90
  // Start visualization reporting
90
91
  if (VizConfig_1.vizConfig.isEnabled()) {
91
- this.vizEventId = VizReporter_1.vizReporter.agentStart(this.id, this.name, this.config.model, "openai", input);
92
+ this.vizEventId = VizReporter_1.vizReporter.agentStart(this.id, this.name, this.config.model, "openai", inputPreview);
92
93
  }
93
94
  if (this.history.transient) {
94
95
  this.history.clear();
95
96
  // Re-add system message after clear
96
97
  this.addSystemMessage(this.getSystemMessage());
97
98
  }
98
- this.addTextToHistory("user", input);
99
+ if (typeof input === "string") {
100
+ this.addTextToHistory("user", input);
101
+ }
102
+ else {
103
+ this.addMessageToHistory("user", input);
104
+ }
99
105
  try {
100
- const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.entries);
106
+ const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
101
107
  const response = await this.client.responses.create({
102
108
  model: this.config.model,
103
109
  max_output_tokens: this.config.maxTokens,
@@ -216,7 +222,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
216
222
  }
217
223
  // Continue conversation
218
224
  try {
219
- const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.entries);
225
+ const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
220
226
  const newResponse = await this.client.responses.create({
221
227
  model: this.config.model,
222
228
  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";
4
- export { text, toolUse, toolResult, textMessage, isTextContent, isToolUseContent, isToolResultContent, } 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, ImageMimeType, ImageUrlContent, ImageBase64Content, } from "./types";
7
+ export { text, toolUse, toolResult, textMessage, imageUrl, imageBase64, isTextContent, isToolUseContent, isToolResultContent, isImageUrlContent, isImageBase64Content, isImageContent, } 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