@agentionai/agents 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/anthropic/ClaudeAgent.js +3 -0
- package/dist/agents/google/GeminiAgent.js +3 -0
- package/dist/agents/mistral/MistralAgent.js +3 -0
- package/dist/agents/openai/OpenAiAgent.js +3 -0
- package/dist/history/History.d.ts +29 -1
- package/dist/history/History.js +60 -6
- package/dist/history/RedisHistory.d.ts +5 -3
- package/dist/history/RedisHistory.js +22 -11
- package/dist/history/plugins/toolResultMaskingPlugin.js +6 -0
- package/package.json +1 -1
|
@@ -81,6 +81,9 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
81
81
|
else {
|
|
82
82
|
this.addMessageToHistory("user", input);
|
|
83
83
|
}
|
|
84
|
+
// Mark session boundary so transform plugins (e.g. toolResultMaskingPlugin)
|
|
85
|
+
// don't mask tool results produced within this execute() loop.
|
|
86
|
+
this.history.setSessionAnchor();
|
|
84
87
|
try {
|
|
85
88
|
const messages = transformers_1.anthropicTransformer.toProvider(this.history.getEntries());
|
|
86
89
|
const systemMessage = this.history.getSystemMessage();
|
|
@@ -181,6 +181,9 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
|
|
|
181
181
|
else {
|
|
182
182
|
this.addMessageToHistory("user", input);
|
|
183
183
|
}
|
|
184
|
+
// Mark session boundary so transform plugins (e.g. toolResultMaskingPlugin)
|
|
185
|
+
// don't mask tool results produced within this execute() loop.
|
|
186
|
+
this.history.setSessionAnchor();
|
|
184
187
|
try {
|
|
185
188
|
const contents = transformers_1.geminiTransformer.toProvider(this.history.getEntries());
|
|
186
189
|
const systemMessage = this.history.getSystemMessage();
|
|
@@ -91,6 +91,9 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
|
|
|
91
91
|
else {
|
|
92
92
|
this.addMessageToHistory("user", input);
|
|
93
93
|
}
|
|
94
|
+
// Mark session boundary so transform plugins (e.g. toolResultMaskingPlugin)
|
|
95
|
+
// don't mask tool results produced within this execute() loop.
|
|
96
|
+
this.history.setSessionAnchor();
|
|
94
97
|
try {
|
|
95
98
|
const messages = transformers_1.mistralTransformer.toProvider(this.history.getEntries());
|
|
96
99
|
const response = await this.client.chat.complete({
|
|
@@ -102,6 +102,9 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
102
102
|
else {
|
|
103
103
|
this.addMessageToHistory("user", input);
|
|
104
104
|
}
|
|
105
|
+
// Mark session boundary so transform plugins (e.g. toolResultMaskingPlugin)
|
|
106
|
+
// don't mask tool results produced within this execute() loop.
|
|
107
|
+
this.history.setSessionAnchor();
|
|
105
108
|
try {
|
|
106
109
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
107
110
|
const response = await this.client.responses.create({
|
|
@@ -61,7 +61,7 @@ type EntryWithMetadata = ReducibleEntry;
|
|
|
61
61
|
/**
|
|
62
62
|
* History configuration options
|
|
63
63
|
*/
|
|
64
|
-
type HistoryOptions = {
|
|
64
|
+
export type HistoryOptions = {
|
|
65
65
|
maxLength?: number;
|
|
66
66
|
/**
|
|
67
67
|
* Maximum estimated tokens to retain in history. When exceeded, oldest
|
|
@@ -123,6 +123,7 @@ export declare class History extends EventEmitter {
|
|
|
123
123
|
transient: boolean;
|
|
124
124
|
private _plugins;
|
|
125
125
|
private _reducing;
|
|
126
|
+
private _sessionAnchor;
|
|
126
127
|
constructor(entries?: HistoryEntry[], options?: HistoryOptions);
|
|
127
128
|
/**
|
|
128
129
|
* Register a plugin with this history instance.
|
|
@@ -137,6 +138,20 @@ export declare class History extends EventEmitter {
|
|
|
137
138
|
* ```
|
|
138
139
|
*/
|
|
139
140
|
use(plugin: HistoryPlugin): this;
|
|
141
|
+
/**
|
|
142
|
+
* Mark the current entry count as the session boundary.
|
|
143
|
+
* Call this at the start of each agent `execute()` after adding the user
|
|
144
|
+
* message. Transform plugins (e.g. toolResultMaskingPlugin) will not mask
|
|
145
|
+
* any entries added at or after this position, preventing tool results from
|
|
146
|
+
* the current execution loop from being masked mid-session.
|
|
147
|
+
*/
|
|
148
|
+
setSessionAnchor(): void;
|
|
149
|
+
/**
|
|
150
|
+
* The entry index set by the last call to `setSessionAnchor()`, or `null`
|
|
151
|
+
* if no anchor has been set. Entries at this index or beyond belong to the
|
|
152
|
+
* current session and should not be masked by transform plugins.
|
|
153
|
+
*/
|
|
154
|
+
get sessionAnchor(): number | null;
|
|
140
155
|
/**
|
|
141
156
|
* Add a complete history entry
|
|
142
157
|
*/
|
|
@@ -232,11 +247,24 @@ export declare class History extends EventEmitter {
|
|
|
232
247
|
* Create a copy of this history
|
|
233
248
|
*/
|
|
234
249
|
clone(options?: HistoryOptions): History;
|
|
250
|
+
/**
|
|
251
|
+
* Apply maxLength and maxTokens trimming to the current entry list.
|
|
252
|
+
* Safe to call after bulk-loading entries (e.g. RedisHistory.load()).
|
|
253
|
+
* Subclasses may call this after directly manipulating _entries.
|
|
254
|
+
*/
|
|
255
|
+
protected applyTrimming(): void;
|
|
235
256
|
/**
|
|
236
257
|
* Drop oldest non-system entries until totalEstimatedTokens fits within budget.
|
|
237
258
|
* Called synchronously from addEntry() as a safety net.
|
|
238
259
|
* The system message is always preserved.
|
|
239
260
|
*/
|
|
240
261
|
private trimToTokenBudget;
|
|
262
|
+
/**
|
|
263
|
+
* After any trim, remove tool_result blocks whose paired tool_use was dropped.
|
|
264
|
+
* Entries that become empty after filtering are also removed.
|
|
265
|
+
* This prevents 400 errors from providers that require tool_result blocks to
|
|
266
|
+
* have a corresponding tool_use in the conversation history.
|
|
267
|
+
*/
|
|
268
|
+
private sanitizeToolPairs;
|
|
241
269
|
}
|
|
242
270
|
//# sourceMappingURL=History.d.ts.map
|
package/dist/history/History.js
CHANGED
|
@@ -127,6 +127,7 @@ class History extends events_1.default {
|
|
|
127
127
|
this.transient = false;
|
|
128
128
|
this._plugins = [];
|
|
129
129
|
this._reducing = false;
|
|
130
|
+
this._sessionAnchor = null;
|
|
130
131
|
this.options = options;
|
|
131
132
|
this.transient = Boolean(options?.transient);
|
|
132
133
|
// Convert initial entries to internal format with metadata
|
|
@@ -154,6 +155,24 @@ class History extends events_1.default {
|
|
|
154
155
|
plugin.onRegistered?.(this);
|
|
155
156
|
return this;
|
|
156
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Mark the current entry count as the session boundary.
|
|
160
|
+
* Call this at the start of each agent `execute()` after adding the user
|
|
161
|
+
* message. Transform plugins (e.g. toolResultMaskingPlugin) will not mask
|
|
162
|
+
* any entries added at or after this position, preventing tool results from
|
|
163
|
+
* the current execution loop from being masked mid-session.
|
|
164
|
+
*/
|
|
165
|
+
setSessionAnchor() {
|
|
166
|
+
this._sessionAnchor = this._entries.length;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The entry index set by the last call to `setSessionAnchor()`, or `null`
|
|
170
|
+
* if no anchor has been set. Entries at this index or beyond belong to the
|
|
171
|
+
* current session and should not be masked by transform plugins.
|
|
172
|
+
*/
|
|
173
|
+
get sessionAnchor() {
|
|
174
|
+
return this._sessionAnchor;
|
|
175
|
+
}
|
|
157
176
|
// ===========================================================================
|
|
158
177
|
// Core write operations
|
|
159
178
|
// ===========================================================================
|
|
@@ -172,12 +191,7 @@ class History extends events_1.default {
|
|
|
172
191
|
...entry,
|
|
173
192
|
__metadata,
|
|
174
193
|
});
|
|
175
|
-
|
|
176
|
-
this._entries = this._entries.slice(this._entries.length - this.options.maxLength);
|
|
177
|
-
}
|
|
178
|
-
if (this.options.maxTokens) {
|
|
179
|
-
this.trimToTokenBudget();
|
|
180
|
-
}
|
|
194
|
+
this.applyTrimming();
|
|
181
195
|
this.emit("entry", entry);
|
|
182
196
|
// Fire plugin afterAdd hooks. Skipped during reduce() to avoid recursion
|
|
183
197
|
// when compression plugins add summary entries to the history.
|
|
@@ -385,6 +399,20 @@ class History extends events_1.default {
|
|
|
385
399
|
// ===========================================================================
|
|
386
400
|
// Private helpers
|
|
387
401
|
// ===========================================================================
|
|
402
|
+
/**
|
|
403
|
+
* Apply maxLength and maxTokens trimming to the current entry list.
|
|
404
|
+
* Safe to call after bulk-loading entries (e.g. RedisHistory.load()).
|
|
405
|
+
* Subclasses may call this after directly manipulating _entries.
|
|
406
|
+
*/
|
|
407
|
+
applyTrimming() {
|
|
408
|
+
if (this.options.maxLength && this._entries.length > this.options.maxLength) {
|
|
409
|
+
this._entries = this._entries.slice(this._entries.length - this.options.maxLength);
|
|
410
|
+
this.sanitizeToolPairs();
|
|
411
|
+
}
|
|
412
|
+
if (this.options.maxTokens) {
|
|
413
|
+
this.trimToTokenBudget();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
388
416
|
/**
|
|
389
417
|
* Drop oldest non-system entries until totalEstimatedTokens fits within budget.
|
|
390
418
|
* Called synchronously from addEntry() as a safety net.
|
|
@@ -400,6 +428,32 @@ class History extends events_1.default {
|
|
|
400
428
|
break;
|
|
401
429
|
this._entries.splice(firstNonSystem, 1);
|
|
402
430
|
}
|
|
431
|
+
this.sanitizeToolPairs();
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* After any trim, remove tool_result blocks whose paired tool_use was dropped.
|
|
435
|
+
* Entries that become empty after filtering are also removed.
|
|
436
|
+
* This prevents 400 errors from providers that require tool_result blocks to
|
|
437
|
+
* have a corresponding tool_use in the conversation history.
|
|
438
|
+
*/
|
|
439
|
+
sanitizeToolPairs() {
|
|
440
|
+
// Collect all tool_use IDs still present in the history
|
|
441
|
+
const toolUseIds = new Set();
|
|
442
|
+
for (const entry of this._entries) {
|
|
443
|
+
for (const block of entry.content) {
|
|
444
|
+
if ((0, types_1.isToolUseContent)(block)) {
|
|
445
|
+
toolUseIds.add(block.id);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// Filter out orphaned tool_result blocks; drop entries that become empty
|
|
450
|
+
this._entries = this._entries.filter((entry) => {
|
|
451
|
+
const filtered = entry.content.filter((block) => !(0, types_1.isToolResultContent)(block) || toolUseIds.has(block.tool_use_id));
|
|
452
|
+
if (filtered.length === 0)
|
|
453
|
+
return false;
|
|
454
|
+
entry.content = filtered;
|
|
455
|
+
return true;
|
|
456
|
+
});
|
|
403
457
|
}
|
|
404
458
|
}
|
|
405
459
|
exports.History = History;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { History } from "./History";
|
|
1
|
+
import { History, HistoryOptions } from "./History";
|
|
2
2
|
interface RedisInstance {
|
|
3
3
|
get(key: string): Promise<string | null>;
|
|
4
4
|
set(key: string, content: string): Promise<"OK">;
|
|
5
5
|
}
|
|
6
6
|
export declare class RedisHistory extends History {
|
|
7
7
|
private redisInstance;
|
|
8
|
-
constructor(redisInstance: RedisInstance);
|
|
8
|
+
constructor(redisInstance: RedisInstance, options?: HistoryOptions);
|
|
9
9
|
/**
|
|
10
|
-
* Loads history entries from Redis using the specified key
|
|
10
|
+
* Loads history entries from Redis using the specified key.
|
|
11
|
+
* Entries are re-added via addEntry() so metadata is computed correctly.
|
|
12
|
+
* Trimming (maxLength / maxTokens) is applied after load.
|
|
11
13
|
*
|
|
12
14
|
* @param {string} key - The Redis key to retrieve history entries from
|
|
13
15
|
* @returns {Promise<void>} A promise that resolves when history is loaded
|
|
@@ -3,12 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RedisHistory = void 0;
|
|
4
4
|
const History_1 = require("./History");
|
|
5
5
|
class RedisHistory extends History_1.History {
|
|
6
|
-
constructor(redisInstance) {
|
|
7
|
-
super([],
|
|
6
|
+
constructor(redisInstance, options = {}) {
|
|
7
|
+
super([], options);
|
|
8
8
|
this.redisInstance = redisInstance;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* Loads history entries from Redis using the specified key
|
|
11
|
+
* Loads history entries from Redis using the specified key.
|
|
12
|
+
* Entries are re-added via addEntry() so metadata is computed correctly.
|
|
13
|
+
* Trimming (maxLength / maxTokens) is applied after load.
|
|
12
14
|
*
|
|
13
15
|
* @param {string} key - The Redis key to retrieve history entries from
|
|
14
16
|
* @returns {Promise<void>} A promise that resolves when history is loaded
|
|
@@ -16,15 +18,26 @@ class RedisHistory extends History_1.History {
|
|
|
16
18
|
*/
|
|
17
19
|
async load(key) {
|
|
18
20
|
try {
|
|
19
|
-
// Retrieve the serialized history from Redis
|
|
20
21
|
const serializedHistory = await this.redisInstance.get(key);
|
|
21
|
-
|
|
22
|
-
if (!serializedHistory) {
|
|
22
|
+
if (!serializedHistory)
|
|
23
23
|
return;
|
|
24
|
-
}
|
|
25
|
-
// Parse the serialized history and create a new History instance
|
|
26
24
|
const entries = JSON.parse(serializedHistory);
|
|
27
|
-
this._entries =
|
|
25
|
+
this._entries = [];
|
|
26
|
+
// Re-add via addEntry to compute metadata; suppress plugins during bulk load
|
|
27
|
+
// by temporarily bypassing plugin afterAdd hooks (handled by _reducing flag
|
|
28
|
+
// which is private — so we push entries directly and call applyTrimming once).
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
const serialized = JSON.stringify(entry.content);
|
|
31
|
+
this._entries.push({
|
|
32
|
+
...entry,
|
|
33
|
+
__metadata: {
|
|
34
|
+
date: new Date().toISOString(),
|
|
35
|
+
contentLength: serialized.length,
|
|
36
|
+
estimatedTokens: Math.ceil(serialized.length / 4),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
this.applyTrimming();
|
|
28
41
|
}
|
|
29
42
|
catch (error) {
|
|
30
43
|
console.error(`Error loading history from Redis key "${key}":`, error);
|
|
@@ -40,9 +53,7 @@ class RedisHistory extends History_1.History {
|
|
|
40
53
|
*/
|
|
41
54
|
async save(key) {
|
|
42
55
|
try {
|
|
43
|
-
// Serialize the current history entries
|
|
44
56
|
const serializedHistory = this.toJSON();
|
|
45
|
-
// Save the serialized history to Redis
|
|
46
57
|
await this.redisInstance.set(key, serializedHistory);
|
|
47
58
|
}
|
|
48
59
|
catch (error) {
|
|
@@ -88,7 +88,13 @@ function toolResultMaskingPlugin(options) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
const maskable = [];
|
|
91
|
+
const sessionAnchor = _history?.sessionAnchor ?? null;
|
|
91
92
|
for (let ei = 0; ei < entries.length; ei++) {
|
|
93
|
+
// Never mask entries from the current execute() session — doing so
|
|
94
|
+
// would cause the model to call retrieve_tool_result mid-loop, whose
|
|
95
|
+
// result would itself be masked, creating an infinite retrieval loop.
|
|
96
|
+
if (sessionAnchor !== null && ei >= sessionAnchor)
|
|
97
|
+
continue;
|
|
92
98
|
const entry = entries[ei];
|
|
93
99
|
for (let bi = 0; bi < entry.content.length; bi++) {
|
|
94
100
|
const block = entry.content[bi];
|