@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.
@@ -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
@@ -40,6 +40,22 @@ exports.anthropicTransformer = {
40
40
  is_error: block.is_error,
41
41
  };
42
42
  }
43
+ if ((0, types_1.isImageUrlContent)(block)) {
44
+ return {
45
+ type: "image",
46
+ source: { type: "url", url: block.url },
47
+ };
48
+ }
49
+ if ((0, types_1.isImageBase64Content)(block)) {
50
+ return {
51
+ type: "image",
52
+ source: {
53
+ type: "base64",
54
+ media_type: block.mimeType,
55
+ data: block.data,
56
+ },
57
+ };
58
+ }
43
59
  throw new Error(`Unknown content type: ${block.type}`);
44
60
  });
45
61
  return { role, content };
@@ -122,11 +138,14 @@ exports.openAiTransformer = {
122
138
  });
123
139
  continue;
124
140
  }
125
- // Separate tool_use from other content for OpenAI format
141
+ // Separate content blocks by type for OpenAI format
126
142
  const textBlocks = entry.content.filter(types_1.isTextContent);
127
143
  const toolUseBlocks = entry.content.filter(types_1.isToolUseContent);
128
144
  const toolResultBlocks = entry.content.filter(types_1.isToolResultContent);
129
- // Add text message if present
145
+ const imageUrlBlocks = entry.content.filter(types_1.isImageUrlContent);
146
+ const imageBase64Blocks = entry.content.filter(types_1.isImageBase64Content);
147
+ const hasImages = imageUrlBlocks.length > 0 || imageBase64Blocks.length > 0;
148
+ // Add text/image message if present
130
149
  if (textBlocks.length > 0 && entry.role !== "user") {
131
150
  items.push({
132
151
  type: "message",
@@ -135,13 +154,42 @@ exports.openAiTransformer = {
135
154
  });
136
155
  }
137
156
  else if (entry.role === "user" &&
138
- textBlocks.length > 0 &&
157
+ (textBlocks.length > 0 || hasImages) &&
139
158
  toolResultBlocks.length === 0) {
140
- items.push({
141
- type: "message",
142
- role: "user",
143
- content: textBlocks.map((c) => c.text).join("\n"),
144
- });
159
+ if (hasImages) {
160
+ // Mixed content: build an array of content parts
161
+ const parts = [];
162
+ for (const block of entry.content) {
163
+ if ((0, types_1.isTextContent)(block)) {
164
+ parts.push({ type: "input_text", text: block.text });
165
+ }
166
+ else if ((0, types_1.isImageUrlContent)(block)) {
167
+ parts.push({
168
+ type: "input_image",
169
+ image_url: block.url,
170
+ ...(block.detail ? { detail: block.detail } : {}),
171
+ });
172
+ }
173
+ else if ((0, types_1.isImageBase64Content)(block)) {
174
+ parts.push({
175
+ type: "input_image",
176
+ image_url: `data:${block.mimeType};base64,${block.data}`,
177
+ });
178
+ }
179
+ }
180
+ items.push({
181
+ type: "message",
182
+ role: "user",
183
+ content: parts,
184
+ });
185
+ }
186
+ else {
187
+ items.push({
188
+ type: "message",
189
+ role: "user",
190
+ content: textBlocks.map((c) => c.text).join("\n"),
191
+ });
192
+ }
145
193
  }
146
194
  // Add tool calls as separate function_call items (OpenAI format)
147
195
  // Convert IDs to OpenAI format if they came from another provider
@@ -243,7 +291,7 @@ exports.mistralTransformer = {
243
291
  messages.push(msg);
244
292
  continue;
245
293
  }
246
- // User role - could be text or tool results
294
+ // User role - could be text, images, or tool results
247
295
  if (toolResultBlocks.length > 0) {
248
296
  // Mistral uses separate "tool" role messages for each result
249
297
  // We need to find the corresponding tool name from the assistant's tool_calls
@@ -258,11 +306,31 @@ exports.mistralTransformer = {
258
306
  });
259
307
  }
260
308
  }
261
- else if (textBlocks.length > 0) {
262
- messages.push({
263
- role: "user",
264
- content: textBlocks.map((c) => c.text).join("\n"),
265
- });
309
+ else {
310
+ const imageUrlBlocks = entry.content.filter(types_1.isImageUrlContent);
311
+ const imageBase64Blocks = entry.content.filter(types_1.isImageBase64Content);
312
+ if (imageBase64Blocks.length > 0) {
313
+ throw new Error("Mistral does not support base64 image inputs. Convert images to URLs before using with MistralAgent.");
314
+ }
315
+ if (imageUrlBlocks.length > 0) {
316
+ // Mistral vision: array content with text + image_url parts
317
+ const parts = [];
318
+ for (const block of entry.content) {
319
+ if ((0, types_1.isTextContent)(block)) {
320
+ parts.push({ type: "text", text: block.text });
321
+ }
322
+ else if ((0, types_1.isImageUrlContent)(block)) {
323
+ parts.push({ type: "image_url", image_url: block.url });
324
+ }
325
+ }
326
+ messages.push({ role: "user", content: parts });
327
+ }
328
+ else if (textBlocks.length > 0) {
329
+ messages.push({
330
+ role: "user",
331
+ content: textBlocks.map((c) => c.text).join("\n"),
332
+ });
333
+ }
266
334
  }
267
335
  }
268
336
  return messages;
@@ -336,6 +404,25 @@ exports.geminiTransformer = {
336
404
  for (const block of textBlocks) {
337
405
  parts.push({ text: block.text });
338
406
  }
407
+ // Add image parts
408
+ for (const block of entry.content) {
409
+ if ((0, types_1.isImageUrlContent)(block)) {
410
+ parts.push({
411
+ fileData: {
412
+ mimeType: block.mimeType ?? "image/jpeg",
413
+ fileUri: block.url,
414
+ },
415
+ });
416
+ }
417
+ else if ((0, types_1.isImageBase64Content)(block)) {
418
+ parts.push({
419
+ inlineData: {
420
+ mimeType: block.mimeType,
421
+ data: block.data,
422
+ },
423
+ });
424
+ }
425
+ }
339
426
  // Add function call parts (for assistant/model messages)
340
427
  for (const block of toolUseBlocks) {
341
428
  parts.push({
@@ -29,10 +29,34 @@ export type ToolResultContent = {
29
29
  content: string;
30
30
  is_error?: boolean;
31
31
  };
32
+ /**
33
+ * Supported image MIME types across all providers
34
+ */
35
+ export type ImageMimeType = "image/jpeg" | "image/png" | "image/gif" | "image/webp";
36
+ /**
37
+ * Image referenced by URL
38
+ */
39
+ export type ImageUrlContent = {
40
+ type: "image_url";
41
+ url: string;
42
+ /** Required hint for Gemini (fileData); optional for other providers */
43
+ mimeType?: ImageMimeType;
44
+ /** OpenAI detail level hint — ignored by other providers */
45
+ detail?: "low" | "high" | "auto";
46
+ };
47
+ /**
48
+ * Image provided as raw base64-encoded data (no data: URI prefix)
49
+ */
50
+ export type ImageBase64Content = {
51
+ type: "image_base64";
52
+ /** Raw base64 string — do not include the `data:<mime>;base64,` prefix */
53
+ data: string;
54
+ mimeType: ImageMimeType;
55
+ };
32
56
  /**
33
57
  * Union of all content types
34
58
  */
35
- export type MessageContent = TextContent | ToolUseContent | ToolResultContent;
59
+ export type MessageContent = TextContent | ToolUseContent | ToolResultContent | ImageUrlContent | ImageBase64Content;
36
60
  /**
37
61
  * Anthropic-specific metadata
38
62
  */
@@ -111,6 +135,9 @@ export type HistoryEntry = {
111
135
  export declare function isTextContent(content: MessageContent): content is TextContent;
112
136
  export declare function isToolUseContent(content: MessageContent): content is ToolUseContent;
113
137
  export declare function isToolResultContent(content: MessageContent): content is ToolResultContent;
138
+ export declare function isImageUrlContent(content: MessageContent): content is ImageUrlContent;
139
+ export declare function isImageBase64Content(content: MessageContent): content is ImageBase64Content;
140
+ export declare function isImageContent(content: MessageContent): content is ImageUrlContent | ImageBase64Content;
114
141
  /**
115
142
  * Create a text content block
116
143
  */
@@ -127,4 +154,27 @@ export declare function toolResult(tool_use_id: string, content: string, is_erro
127
154
  * Create a simple text message entry
128
155
  */
129
156
  export declare function textMessage(role: MessageRole, value: string): HistoryEntry;
157
+ /**
158
+ * Create an image URL content block
159
+ */
160
+ export declare function imageUrl(url: string, options?: {
161
+ mimeType?: ImageMimeType;
162
+ detail?: "low" | "high" | "auto";
163
+ }): ImageUrlContent;
164
+ /**
165
+ * Create a base64 image content block
166
+ */
167
+ export declare function imageBase64(data: string, mimeType: ImageMimeType): ImageBase64Content;
168
+ /**
169
+ * Options controlling how history.reduce() compacts stored entries.
170
+ * All fields are optional — supply whichever constraints apply.
171
+ */
172
+ export type ReduceOptions = {
173
+ /** Compress/drop entries until total estimated tokens fall below this value. */
174
+ maxTokens?: number;
175
+ /** Compress/drop entries until the entry count falls below this value. */
176
+ maxEntries?: number;
177
+ /** Compress/drop entries whose timestamp predates this date. */
178
+ olderThan?: Date;
179
+ };
130
180
  //# sourceMappingURL=types.d.ts.map
@@ -9,10 +9,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.isTextContent = isTextContent;
10
10
  exports.isToolUseContent = isToolUseContent;
11
11
  exports.isToolResultContent = isToolResultContent;
12
+ exports.isImageUrlContent = isImageUrlContent;
13
+ exports.isImageBase64Content = isImageBase64Content;
14
+ exports.isImageContent = isImageContent;
12
15
  exports.text = text;
13
16
  exports.toolUse = toolUse;
14
17
  exports.toolResult = toolResult;
15
18
  exports.textMessage = textMessage;
19
+ exports.imageUrl = imageUrl;
20
+ exports.imageBase64 = imageBase64;
16
21
  // =============================================================================
17
22
  // Helper Type Guards
18
23
  // =============================================================================
@@ -25,6 +30,15 @@ function isToolUseContent(content) {
25
30
  function isToolResultContent(content) {
26
31
  return content.type === "tool_result";
27
32
  }
33
+ function isImageUrlContent(content) {
34
+ return content.type === "image_url";
35
+ }
36
+ function isImageBase64Content(content) {
37
+ return content.type === "image_base64";
38
+ }
39
+ function isImageContent(content) {
40
+ return content.type === "image_url" || content.type === "image_base64";
41
+ }
28
42
  // =============================================================================
29
43
  // Utility Functions
30
44
  // =============================================================================
@@ -52,4 +66,16 @@ function toolResult(tool_use_id, content, is_error) {
52
66
  function textMessage(role, value) {
53
67
  return { role, content: [text(value)] };
54
68
  }
69
+ /**
70
+ * Create an image URL content block
71
+ */
72
+ function imageUrl(url, options) {
73
+ return { type: "image_url", url, ...options };
74
+ }
75
+ /**
76
+ * Create a base64 image content block
77
+ */
78
+ function imageBase64(data, mimeType) {
79
+ return { type: "image_base64", data, mimeType };
80
+ }
55
81
  //# sourceMappingURL=types.js.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.10.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": [