@agentionai/agents 0.10.0 → 0.10.2
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 +9 -0
- package/dist/agents/google/GeminiAgent.js +9 -0
- package/dist/agents/mistral/MistralAgent.js +9 -0
- package/dist/agents/openai/OpenAiAgent.js +9 -0
- package/dist/history/History.d.ts +46 -1
- package/dist/history/History.js +86 -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,12 @@ 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();
|
|
87
|
+
// Suspend auto-trimming so tool_use / tool_result pairs are never split
|
|
88
|
+
// mid-loop. endExecution() in the finally block enforces limits once.
|
|
89
|
+
this.history.beginExecution();
|
|
84
90
|
try {
|
|
85
91
|
const messages = transformers_1.anthropicTransformer.toProvider(this.history.getEntries());
|
|
86
92
|
const systemMessage = this.history.getSystemMessage();
|
|
@@ -122,6 +128,9 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
|
|
|
122
128
|
throw executionError;
|
|
123
129
|
}
|
|
124
130
|
}
|
|
131
|
+
finally {
|
|
132
|
+
this.history.endExecution();
|
|
133
|
+
}
|
|
125
134
|
}
|
|
126
135
|
async handleResponse(response) {
|
|
127
136
|
const usage = this.parseUsage(response.usage);
|
|
@@ -181,6 +181,12 @@ 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();
|
|
187
|
+
// Suspend auto-trimming so tool_use / tool_result pairs are never split
|
|
188
|
+
// mid-loop. endExecution() in the finally block enforces limits once.
|
|
189
|
+
this.history.beginExecution();
|
|
184
190
|
try {
|
|
185
191
|
const contents = transformers_1.geminiTransformer.toProvider(this.history.getEntries());
|
|
186
192
|
const systemMessage = this.history.getSystemMessage();
|
|
@@ -226,6 +232,9 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
|
|
|
226
232
|
throw executionError;
|
|
227
233
|
}
|
|
228
234
|
}
|
|
235
|
+
finally {
|
|
236
|
+
this.history.endExecution();
|
|
237
|
+
}
|
|
229
238
|
}
|
|
230
239
|
async handleResponse(response) {
|
|
231
240
|
const result = response.response;
|
|
@@ -91,6 +91,12 @@ 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();
|
|
97
|
+
// Suspend auto-trimming so tool_use / tool_result pairs are never split
|
|
98
|
+
// mid-loop. endExecution() in the finally block enforces limits once.
|
|
99
|
+
this.history.beginExecution();
|
|
94
100
|
try {
|
|
95
101
|
const messages = transformers_1.mistralTransformer.toProvider(this.history.getEntries());
|
|
96
102
|
const response = await this.client.chat.complete({
|
|
@@ -130,6 +136,9 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
|
|
|
130
136
|
throw executionError;
|
|
131
137
|
}
|
|
132
138
|
}
|
|
139
|
+
finally {
|
|
140
|
+
this.history.endExecution();
|
|
141
|
+
}
|
|
133
142
|
}
|
|
134
143
|
async handleResponse(response) {
|
|
135
144
|
if (!response.choices || response.choices.length === 0) {
|
|
@@ -102,6 +102,12 @@ 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();
|
|
108
|
+
// Suspend auto-trimming so tool_use / tool_result pairs are never split
|
|
109
|
+
// mid-loop. endExecution() in the finally block enforces limits once.
|
|
110
|
+
this.history.beginExecution();
|
|
105
111
|
try {
|
|
106
112
|
const inputMessages = transformers_1.openAiTransformer.toProvider(this.history.getEntries());
|
|
107
113
|
const response = await this.client.responses.create({
|
|
@@ -147,6 +153,9 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
|
|
|
147
153
|
throw executionError;
|
|
148
154
|
}
|
|
149
155
|
}
|
|
156
|
+
finally {
|
|
157
|
+
this.history.endExecution();
|
|
158
|
+
}
|
|
150
159
|
}
|
|
151
160
|
async handleResponse(response) {
|
|
152
161
|
if (!response.output || !response.output.length) {
|
|
@@ -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,8 @@ export declare class History extends EventEmitter {
|
|
|
123
123
|
transient: boolean;
|
|
124
124
|
private _plugins;
|
|
125
125
|
private _reducing;
|
|
126
|
+
private _executing;
|
|
127
|
+
private _sessionAnchor;
|
|
126
128
|
constructor(entries?: HistoryEntry[], options?: HistoryOptions);
|
|
127
129
|
/**
|
|
128
130
|
* Register a plugin with this history instance.
|
|
@@ -137,6 +139,20 @@ export declare class History extends EventEmitter {
|
|
|
137
139
|
* ```
|
|
138
140
|
*/
|
|
139
141
|
use(plugin: HistoryPlugin): this;
|
|
142
|
+
/**
|
|
143
|
+
* Mark the current entry count as the session boundary.
|
|
144
|
+
* Call this at the start of each agent `execute()` after adding the user
|
|
145
|
+
* message. Transform plugins (e.g. toolResultMaskingPlugin) will not mask
|
|
146
|
+
* any entries added at or after this position, preventing tool results from
|
|
147
|
+
* the current execution loop from being masked mid-session.
|
|
148
|
+
*/
|
|
149
|
+
setSessionAnchor(): void;
|
|
150
|
+
/**
|
|
151
|
+
* The entry index set by the last call to `setSessionAnchor()`, or `null`
|
|
152
|
+
* if no anchor has been set. Entries at this index or beyond belong to the
|
|
153
|
+
* current session and should not be masked by transform plugins.
|
|
154
|
+
*/
|
|
155
|
+
get sessionAnchor(): number | null;
|
|
140
156
|
/**
|
|
141
157
|
* Add a complete history entry
|
|
142
158
|
*/
|
|
@@ -232,11 +248,40 @@ export declare class History extends EventEmitter {
|
|
|
232
248
|
* Create a copy of this history
|
|
233
249
|
*/
|
|
234
250
|
clone(options?: HistoryOptions): History;
|
|
251
|
+
/**
|
|
252
|
+
* Signal the start of an agent execute() loop. While executing, automatic
|
|
253
|
+
* trimming on addEntry() is suspended so tool_use / tool_result pairs are
|
|
254
|
+
* never split mid-loop. Call endExecution() in a finally block to resume.
|
|
255
|
+
*/
|
|
256
|
+
beginExecution(): void;
|
|
257
|
+
/**
|
|
258
|
+
* Signal the end of an agent execute() loop. Resumes automatic trimming and
|
|
259
|
+
* immediately enforces maxLength / maxTokens limits on the accumulated history.
|
|
260
|
+
*/
|
|
261
|
+
endExecution(): void;
|
|
262
|
+
/**
|
|
263
|
+
* Explicitly enforce maxLength and maxTokens limits. Useful when using
|
|
264
|
+
* History standalone, outside of an agent execute() loop.
|
|
265
|
+
*/
|
|
266
|
+
trim(): void;
|
|
267
|
+
/**
|
|
268
|
+
* Apply maxLength and maxTokens trimming to the current entry list.
|
|
269
|
+
* Safe to call after bulk-loading entries (e.g. RedisHistory.load()).
|
|
270
|
+
* Subclasses may call this after directly manipulating _entries.
|
|
271
|
+
*/
|
|
272
|
+
protected applyTrimming(): void;
|
|
235
273
|
/**
|
|
236
274
|
* Drop oldest non-system entries until totalEstimatedTokens fits within budget.
|
|
237
275
|
* Called synchronously from addEntry() as a safety net.
|
|
238
276
|
* The system message is always preserved.
|
|
239
277
|
*/
|
|
240
278
|
private trimToTokenBudget;
|
|
279
|
+
/**
|
|
280
|
+
* After any trim, remove tool_result blocks whose paired tool_use was dropped.
|
|
281
|
+
* Entries that become empty after filtering are also removed.
|
|
282
|
+
* This prevents 400 errors from providers that require tool_result blocks to
|
|
283
|
+
* have a corresponding tool_use in the conversation history.
|
|
284
|
+
*/
|
|
285
|
+
private sanitizeToolPairs;
|
|
241
286
|
}
|
|
242
287
|
//# sourceMappingURL=History.d.ts.map
|
package/dist/history/History.js
CHANGED
|
@@ -127,6 +127,8 @@ class History extends events_1.default {
|
|
|
127
127
|
this.transient = false;
|
|
128
128
|
this._plugins = [];
|
|
129
129
|
this._reducing = false;
|
|
130
|
+
this._executing = false;
|
|
131
|
+
this._sessionAnchor = null;
|
|
130
132
|
this.options = options;
|
|
131
133
|
this.transient = Boolean(options?.transient);
|
|
132
134
|
// Convert initial entries to internal format with metadata
|
|
@@ -154,6 +156,24 @@ class History extends events_1.default {
|
|
|
154
156
|
plugin.onRegistered?.(this);
|
|
155
157
|
return this;
|
|
156
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Mark the current entry count as the session boundary.
|
|
161
|
+
* Call this at the start of each agent `execute()` after adding the user
|
|
162
|
+
* message. Transform plugins (e.g. toolResultMaskingPlugin) will not mask
|
|
163
|
+
* any entries added at or after this position, preventing tool results from
|
|
164
|
+
* the current execution loop from being masked mid-session.
|
|
165
|
+
*/
|
|
166
|
+
setSessionAnchor() {
|
|
167
|
+
this._sessionAnchor = this._entries.length;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The entry index set by the last call to `setSessionAnchor()`, or `null`
|
|
171
|
+
* if no anchor has been set. Entries at this index or beyond belong to the
|
|
172
|
+
* current session and should not be masked by transform plugins.
|
|
173
|
+
*/
|
|
174
|
+
get sessionAnchor() {
|
|
175
|
+
return this._sessionAnchor;
|
|
176
|
+
}
|
|
157
177
|
// ===========================================================================
|
|
158
178
|
// Core write operations
|
|
159
179
|
// ===========================================================================
|
|
@@ -172,12 +192,7 @@ class History extends events_1.default {
|
|
|
172
192
|
...entry,
|
|
173
193
|
__metadata,
|
|
174
194
|
});
|
|
175
|
-
|
|
176
|
-
this._entries = this._entries.slice(this._entries.length - this.options.maxLength);
|
|
177
|
-
}
|
|
178
|
-
if (this.options.maxTokens) {
|
|
179
|
-
this.trimToTokenBudget();
|
|
180
|
-
}
|
|
195
|
+
this.applyTrimming();
|
|
181
196
|
this.emit("entry", entry);
|
|
182
197
|
// Fire plugin afterAdd hooks. Skipped during reduce() to avoid recursion
|
|
183
198
|
// when compression plugins add summary entries to the history.
|
|
@@ -385,6 +400,45 @@ class History extends events_1.default {
|
|
|
385
400
|
// ===========================================================================
|
|
386
401
|
// Private helpers
|
|
387
402
|
// ===========================================================================
|
|
403
|
+
/**
|
|
404
|
+
* Signal the start of an agent execute() loop. While executing, automatic
|
|
405
|
+
* trimming on addEntry() is suspended so tool_use / tool_result pairs are
|
|
406
|
+
* never split mid-loop. Call endExecution() in a finally block to resume.
|
|
407
|
+
*/
|
|
408
|
+
beginExecution() {
|
|
409
|
+
this._executing = true;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Signal the end of an agent execute() loop. Resumes automatic trimming and
|
|
413
|
+
* immediately enforces maxLength / maxTokens limits on the accumulated history.
|
|
414
|
+
*/
|
|
415
|
+
endExecution() {
|
|
416
|
+
this._executing = false;
|
|
417
|
+
this.applyTrimming();
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Explicitly enforce maxLength and maxTokens limits. Useful when using
|
|
421
|
+
* History standalone, outside of an agent execute() loop.
|
|
422
|
+
*/
|
|
423
|
+
trim() {
|
|
424
|
+
this.applyTrimming();
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Apply maxLength and maxTokens trimming to the current entry list.
|
|
428
|
+
* Safe to call after bulk-loading entries (e.g. RedisHistory.load()).
|
|
429
|
+
* Subclasses may call this after directly manipulating _entries.
|
|
430
|
+
*/
|
|
431
|
+
applyTrimming() {
|
|
432
|
+
if (this._executing)
|
|
433
|
+
return;
|
|
434
|
+
if (this.options.maxLength && this._entries.length > this.options.maxLength) {
|
|
435
|
+
this._entries = this._entries.slice(this._entries.length - this.options.maxLength);
|
|
436
|
+
this.sanitizeToolPairs();
|
|
437
|
+
}
|
|
438
|
+
if (this.options.maxTokens) {
|
|
439
|
+
this.trimToTokenBudget();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
388
442
|
/**
|
|
389
443
|
* Drop oldest non-system entries until totalEstimatedTokens fits within budget.
|
|
390
444
|
* Called synchronously from addEntry() as a safety net.
|
|
@@ -400,6 +454,32 @@ class History extends events_1.default {
|
|
|
400
454
|
break;
|
|
401
455
|
this._entries.splice(firstNonSystem, 1);
|
|
402
456
|
}
|
|
457
|
+
this.sanitizeToolPairs();
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* After any trim, remove tool_result blocks whose paired tool_use was dropped.
|
|
461
|
+
* Entries that become empty after filtering are also removed.
|
|
462
|
+
* This prevents 400 errors from providers that require tool_result blocks to
|
|
463
|
+
* have a corresponding tool_use in the conversation history.
|
|
464
|
+
*/
|
|
465
|
+
sanitizeToolPairs() {
|
|
466
|
+
// Collect all tool_use IDs still present in the history
|
|
467
|
+
const toolUseIds = new Set();
|
|
468
|
+
for (const entry of this._entries) {
|
|
469
|
+
for (const block of entry.content) {
|
|
470
|
+
if ((0, types_1.isToolUseContent)(block)) {
|
|
471
|
+
toolUseIds.add(block.id);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// Filter out orphaned tool_result blocks; drop entries that become empty
|
|
476
|
+
this._entries = this._entries.filter((entry) => {
|
|
477
|
+
const filtered = entry.content.filter((block) => !(0, types_1.isToolResultContent)(block) || toolUseIds.has(block.tool_use_id));
|
|
478
|
+
if (filtered.length === 0)
|
|
479
|
+
return false;
|
|
480
|
+
entry.content = filtered;
|
|
481
|
+
return true;
|
|
482
|
+
});
|
|
403
483
|
}
|
|
404
484
|
}
|
|
405
485
|
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];
|