@absolutejs/ai 0.4.1 → 0.5.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/CHANGELOG.md +11 -0
- package/changelog.json +38 -0
- package/dist/ai/index.js +59 -34
- package/dist/ai/index.js.map +14 -13
- package/dist/ai/providers/anthropic.js +5 -2
- package/dist/ai/providers/anthropic.js.map +3 -3
- package/dist/ai/providers/gemini.js +5 -2
- package/dist/ai/providers/gemini.js.map +3 -3
- package/dist/ai/providers/ollama.js +3 -1
- package/dist/ai/providers/ollama.js.map +3 -3
- package/dist/ai/providers/openai.js +19 -5
- package/dist/ai/providers/openai.js.map +4 -4
- package/dist/ai/providers/openaiCompatible.js +19 -5
- package/dist/ai/providers/openaiCompatible.js.map +4 -4
- package/dist/ai/providers/openaiResponses.js +5 -2
- package/dist/ai/providers/openaiResponses.js.map +3 -3
- package/dist/ai/providers/openrouter.js +23 -6
- package/dist/ai/providers/openrouter.js.map +5 -5
- package/dist/ai/tools/index.js +6 -1
- package/dist/ai/tools/index.js.map +3 -3
- package/dist/ai/tools/untrusted.js +6 -1
- package/dist/ai/tools/untrusted.js.map +3 -3
- package/dist/angular/ai/index.js +7 -9
- package/dist/angular/ai/index.js.map +2 -2
- package/dist/src/ai/index.d.ts +1 -0
- package/dist/src/ai/toolOutput.d.ts +8 -0
- package/dist/types/ai.d.ts +11 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,17 @@ This file is generated by `absolute-changelog` from the entries in
|
|
|
6
6
|
`changelog/`. Edit an entry, not this file — and add new ones under
|
|
7
7
|
`changelog/unreleased/`.
|
|
8
8
|
|
|
9
|
+
## 0.5.0 — 2026-10-19
|
|
10
|
+
|
|
11
|
+
### Breaking
|
|
12
|
+
|
|
13
|
+
- **AIToolDefinition.handler may return { text, images } as well as a string** (`AIToolDefinition`, `toolOutputText`)
|
|
14
|
+
_Migration:_ Code that calls a tool's handler itself (a wrapper, a test) now receives AIToolOutput, a string or { text, images }. Read its text with toolOutputText(output). Handlers that return a string need no change.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Tools can return images for the model to see, such as a screenshot: a handler returns { text, images } and the tool loops send the images inside the tool result, mapped for Anthropic, OpenAI Responses, OpenAI Chat Completions, Gemini and Ollama** (`AIToolResult`, `AIToolOutput`, `toolOutputText`, `AIProviderContentBlock`, `anthropic`, `openai`, `openaiResponses`, `gemini`, `ollama`, `streamAIToSSE`, `streamAIWithTools`, `generateAIWithTools`, `hardenUntrustedTool`, `createAIStoredToolResultRecovery`)
|
|
19
|
+
|
|
9
20
|
## 0.4.1 — 2026-10-17
|
|
10
21
|
|
|
11
22
|
### Fixed
|
package/changelog.json
CHANGED
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
"contract": 1,
|
|
3
3
|
"name": "@absolutejs/ai",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"changes": [
|
|
7
|
+
{
|
|
8
|
+
"kind": "breaking",
|
|
9
|
+
"migration": {
|
|
10
|
+
"instruction": "Code that calls a tool's handler itself (a wrapper, a test) now receives AIToolOutput, a string or { text, images }. Read its text with toolOutputText(output). Handlers that return a string need no change.",
|
|
11
|
+
"manual": true
|
|
12
|
+
},
|
|
13
|
+
"summary": "AIToolDefinition.handler may return { text, images } as well as a string",
|
|
14
|
+
"symbols": [
|
|
15
|
+
"AIToolDefinition",
|
|
16
|
+
"toolOutputText"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"kind": "added",
|
|
21
|
+
"summary": "Tools can return images for the model to see, such as a screenshot: a handler returns { text, images } and the tool loops send the images inside the tool result, mapped for Anthropic, OpenAI Responses, OpenAI Chat Completions, Gemini and Ollama",
|
|
22
|
+
"symbols": [
|
|
23
|
+
"AIToolResult",
|
|
24
|
+
"AIToolOutput",
|
|
25
|
+
"toolOutputText",
|
|
26
|
+
"AIProviderContentBlock",
|
|
27
|
+
"anthropic",
|
|
28
|
+
"openai",
|
|
29
|
+
"openaiResponses",
|
|
30
|
+
"gemini",
|
|
31
|
+
"ollama",
|
|
32
|
+
"streamAIToSSE",
|
|
33
|
+
"streamAIWithTools",
|
|
34
|
+
"generateAIWithTools",
|
|
35
|
+
"hardenUntrustedTool",
|
|
36
|
+
"createAIStoredToolResultRecovery"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"date": "2026-10-19",
|
|
41
|
+
"version": "0.5.0"
|
|
42
|
+
},
|
|
5
43
|
{
|
|
6
44
|
"changes": [
|
|
7
45
|
{
|
package/dist/ai/index.js
CHANGED
|
@@ -258,6 +258,14 @@ var parseAIMessage = (raw) => {
|
|
|
258
258
|
};
|
|
259
259
|
var serializeAIMessage = (message) => JSON.stringify(message);
|
|
260
260
|
|
|
261
|
+
// src/ai/toolOutput.ts
|
|
262
|
+
var toolOutputText = (output) => typeof output === "string" ? output : output.text;
|
|
263
|
+
var toolOutputImages = (output) => typeof output === "string" || !output.images?.length ? undefined : output.images;
|
|
264
|
+
var toolResultBlock = (toolUseId, output, content = toolOutputText(output)) => {
|
|
265
|
+
const images = toolOutputImages(output);
|
|
266
|
+
return images ? { content, images, tool_use_id: toolUseId, type: "tool_result" } : { content, tool_use_id: toolUseId, type: "tool_result" };
|
|
267
|
+
};
|
|
268
|
+
|
|
261
269
|
// src/ai/inputCapacity.ts
|
|
262
270
|
class AIInputError extends Error {
|
|
263
271
|
code;
|
|
@@ -643,14 +651,11 @@ var processToolTurn = async (socket, options, state, messageId, conversationId,
|
|
|
643
651
|
const toolResultBlocks = [];
|
|
644
652
|
for (const toolCall of toolCalls) {
|
|
645
653
|
await sendToolRunning(socket, toolCall.name, toolCall.input, messageId, conversationId);
|
|
646
|
-
const
|
|
654
|
+
const output = await executeTool(options, toolCall.name, toolCall.input);
|
|
655
|
+
const result = toolOutputText(output);
|
|
647
656
|
await sendToolComplete(socket, toolCall.name, result, messageId, conversationId);
|
|
648
657
|
options.onToolUse?.(toolCall.name, toolCall.input, result);
|
|
649
|
-
toolResultBlocks.push(
|
|
650
|
-
content: result,
|
|
651
|
-
tool_use_id: toolCall.id,
|
|
652
|
-
type: "tool_result"
|
|
653
|
-
});
|
|
658
|
+
toolResultBlocks.push(toolResultBlock(toolCall.id, output));
|
|
654
659
|
state.executedToolKeys.add(serializeToolCall(toolCall.name, toolCall.input));
|
|
655
660
|
}
|
|
656
661
|
state.currentMessages.push({
|
|
@@ -1112,7 +1117,8 @@ var executeToolCalls = async function* (pendingToolCalls, options, renderers, tu
|
|
|
1112
1117
|
turnState.allToolsHtml += renderers.toolRunning(toolCall.name, toolCall.input);
|
|
1113
1118
|
yield { data: turnState.allToolsHtml, event: "tools" };
|
|
1114
1119
|
}
|
|
1115
|
-
const
|
|
1120
|
+
const output = await executeTool2(options, toolCall.name, toolCall.input);
|
|
1121
|
+
const result = toolOutputText(output);
|
|
1116
1122
|
if (options.structuredEvents) {
|
|
1117
1123
|
yield {
|
|
1118
1124
|
data: JSON.stringify({
|
|
@@ -1129,11 +1135,7 @@ var executeToolCalls = async function* (pendingToolCalls, options, renderers, tu
|
|
|
1129
1135
|
}
|
|
1130
1136
|
options.onToolUse?.(toolCall.name, toolCall.input, result);
|
|
1131
1137
|
const resultContent = options.maxToolResultChars !== undefined ? truncateToolResult(result, options.maxToolResultChars) : result;
|
|
1132
|
-
toolResultBlocks.push(
|
|
1133
|
-
content: resultContent,
|
|
1134
|
-
tool_use_id: toolCall.id,
|
|
1135
|
-
type: "tool_result"
|
|
1136
|
-
});
|
|
1138
|
+
toolResultBlocks.push(toolResultBlock(toolCall.id, output, resultContent));
|
|
1137
1139
|
}
|
|
1138
1140
|
return toolResultBlocks;
|
|
1139
1141
|
};
|
|
@@ -1833,12 +1835,8 @@ var generateAIWithTools = async (options) => {
|
|
|
1833
1835
|
const resultBlocks = await Promise.all(result.toolCalls.map(async (call) => {
|
|
1834
1836
|
const definition = tools[call.name];
|
|
1835
1837
|
const output = definition ? await Promise.resolve(definition.handler(call.input)).catch((err) => `Error: ${err instanceof Error ? err.message : String(err)}`) : `Error: unknown tool "${call.name}"`;
|
|
1836
|
-
onToolUse?.(call.name, call.input, output);
|
|
1837
|
-
return
|
|
1838
|
-
content: output,
|
|
1839
|
-
tool_use_id: call.id,
|
|
1840
|
-
type: "tool_result"
|
|
1841
|
-
};
|
|
1838
|
+
onToolUse?.(call.name, call.input, toolOutputText(output));
|
|
1839
|
+
return toolResultBlock(call.id, output);
|
|
1842
1840
|
}));
|
|
1843
1841
|
return [
|
|
1844
1842
|
...result.requestMessages ?? messages,
|
|
@@ -1988,14 +1986,15 @@ var flushThinking3 = (blocks, thinking) => {
|
|
|
1988
1986
|
var executeToolHandler = async (tools, call) => {
|
|
1989
1987
|
const definition = tools[call.name];
|
|
1990
1988
|
if (!definition) {
|
|
1991
|
-
return { ok: false,
|
|
1989
|
+
return { ok: false, output: `Error: unknown tool "${call.name}"` };
|
|
1992
1990
|
}
|
|
1993
1991
|
try {
|
|
1994
|
-
|
|
1992
|
+
const output = await definition.handler(call.input);
|
|
1993
|
+
return { ok: true, output };
|
|
1995
1994
|
} catch (err) {
|
|
1996
1995
|
return {
|
|
1997
1996
|
ok: false,
|
|
1998
|
-
|
|
1997
|
+
output: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
1999
1998
|
};
|
|
2000
1999
|
}
|
|
2001
2000
|
};
|
|
@@ -2142,7 +2141,8 @@ var streamAIWithTools = async function* (options) {
|
|
|
2142
2141
|
type: "tool_start"
|
|
2143
2142
|
};
|
|
2144
2143
|
const startedAt = Date.now();
|
|
2145
|
-
const { ok,
|
|
2144
|
+
const { ok, output } = await executeToolHandler(tools, call);
|
|
2145
|
+
const result = toolOutputText(output);
|
|
2146
2146
|
executedKeys.add(toolCallKey(call));
|
|
2147
2147
|
yield {
|
|
2148
2148
|
id: call.id,
|
|
@@ -2153,11 +2153,7 @@ var streamAIWithTools = async function* (options) {
|
|
|
2153
2153
|
result,
|
|
2154
2154
|
type: "tool_result"
|
|
2155
2155
|
};
|
|
2156
|
-
resultBlocks.push(
|
|
2157
|
-
content: result,
|
|
2158
|
-
tool_use_id: call.id,
|
|
2159
|
-
type: "tool_result"
|
|
2160
|
-
});
|
|
2156
|
+
resultBlocks.push(toolResultBlock(call.id, output));
|
|
2161
2157
|
if (ok && stopAfterTools?.includes(call.name)) {
|
|
2162
2158
|
terminalToolSucceeded = true;
|
|
2163
2159
|
break;
|
|
@@ -5179,7 +5175,10 @@ var convertToolBlock = (block) => {
|
|
|
5179
5175
|
if (block.type === "tool_result") {
|
|
5180
5176
|
return {
|
|
5181
5177
|
call_id: block.tool_use_id,
|
|
5182
|
-
output:
|
|
5178
|
+
output: block.images?.length ? [
|
|
5179
|
+
{ text: block.content, type: "input_text" },
|
|
5180
|
+
...block.images.map((source) => mapBlockToResponsesFormat({ source, type: "image" }))
|
|
5181
|
+
] : block.content,
|
|
5183
5182
|
type: "function_call_output"
|
|
5184
5183
|
};
|
|
5185
5184
|
}
|
|
@@ -5709,6 +5708,7 @@ var NOT_FOUND4 = -1;
|
|
|
5709
5708
|
var isRecord3 = (value) => typeof value === "object" && value !== null;
|
|
5710
5709
|
var isRecordArray2 = (value) => Array.isArray(value) && value.length > 0 && isRecord3(value[0]);
|
|
5711
5710
|
var hasArrayContent = (msg) => typeof msg.content !== "string" && Array.isArray(msg.content);
|
|
5711
|
+
var imageUrl = (source) => source.type === "url" ? source.url : `data:${source.media_type};base64,${source.data}`;
|
|
5712
5712
|
var buildToolMessages = (blocks) => {
|
|
5713
5713
|
const toolUseBlocks = blocks.filter((block) => block.type === "tool_use");
|
|
5714
5714
|
const toolResultBlocks = blocks.filter((block) => block.type === "tool_result");
|
|
@@ -5734,6 +5734,18 @@ var buildToolMessages = (blocks) => {
|
|
|
5734
5734
|
tool_call_id: result.tool_use_id
|
|
5735
5735
|
});
|
|
5736
5736
|
}
|
|
5737
|
+
const images = toolResultBlocks.flatMap((result) => result.images ?? []);
|
|
5738
|
+
if (images.length > 0)
|
|
5739
|
+
messages.push({
|
|
5740
|
+
content: [
|
|
5741
|
+
{ text: "Images returned by the tool calls above:", type: "text" },
|
|
5742
|
+
...images.map((source) => ({
|
|
5743
|
+
image_url: { url: imageUrl(source) },
|
|
5744
|
+
type: "image_url"
|
|
5745
|
+
}))
|
|
5746
|
+
],
|
|
5747
|
+
role: "user"
|
|
5748
|
+
});
|
|
5737
5749
|
return messages;
|
|
5738
5750
|
};
|
|
5739
5751
|
var mapToolDefinitions = (tools) => tools.map((tool) => ({
|
|
@@ -5747,9 +5759,7 @@ var mapToolDefinitions = (tools) => tools.map((tool) => ({
|
|
|
5747
5759
|
var mapContentBlockToOpenAI = (block) => {
|
|
5748
5760
|
if (block.type === "image") {
|
|
5749
5761
|
return {
|
|
5750
|
-
image_url: {
|
|
5751
|
-
url: block.source.type === "url" ? block.source.url : `data:${block.source.media_type};base64,${block.source.data}`
|
|
5752
|
-
},
|
|
5762
|
+
image_url: { url: imageUrl(block.source) },
|
|
5753
5763
|
type: "image_url"
|
|
5754
5764
|
};
|
|
5755
5765
|
}
|
|
@@ -6372,7 +6382,10 @@ var convertMessageContent = (content) => {
|
|
|
6372
6382
|
if (typeof content === "string") {
|
|
6373
6383
|
return [{ text: content }];
|
|
6374
6384
|
}
|
|
6375
|
-
return content.
|
|
6385
|
+
return content.flatMap((block) => block.type === "tool_result" && block.images?.length ? [
|
|
6386
|
+
mapContentBlock(block),
|
|
6387
|
+
...block.images.map((source) => mapContentBlock({ source, type: "image" }))
|
|
6388
|
+
] : [mapContentBlock(block)]).filter((mapped) => mapped !== null);
|
|
6376
6389
|
};
|
|
6377
6390
|
var hasFunctionResponse = (content) => content.some((block) => block.type === "tool_result");
|
|
6378
6391
|
var convertSingleMessage = (msg) => {
|
|
@@ -6672,7 +6685,10 @@ var mapContentBlock2 = (block) => {
|
|
|
6672
6685
|
}
|
|
6673
6686
|
if (block.type === "tool_result") {
|
|
6674
6687
|
return {
|
|
6675
|
-
content: block.
|
|
6688
|
+
content: block.images?.length ? [
|
|
6689
|
+
{ text: block.content, type: "text" },
|
|
6690
|
+
...block.images.map((source) => ({ source, type: "image" }))
|
|
6691
|
+
] : block.content,
|
|
6676
6692
|
tool_use_id: block.tool_use_id,
|
|
6677
6693
|
type: "tool_result"
|
|
6678
6694
|
};
|
|
@@ -7418,8 +7434,10 @@ var convertMessage2 = (msg) => {
|
|
|
7418
7434
|
});
|
|
7419
7435
|
}
|
|
7420
7436
|
for (const block of toolResultBlocks) {
|
|
7437
|
+
const images = (block.images ?? []).flatMap((source) => source.type === "base64" ? [source.data] : []);
|
|
7421
7438
|
results.push({
|
|
7422
7439
|
content: typeof block.content === "string" ? block.content : "",
|
|
7440
|
+
...images.length > 0 ? { images } : {},
|
|
7423
7441
|
role: "tool"
|
|
7424
7442
|
});
|
|
7425
7443
|
}
|
|
@@ -9026,6 +9044,12 @@ var createAIStoredToolResultRecovery = (toolNames) => async ({ params }) => {
|
|
|
9026
9044
|
if (block.type !== "tool_result" || !eligible.has(block.tool_use_id) || block === newest)
|
|
9027
9045
|
return block;
|
|
9028
9046
|
const marker = "[Earlier lookup removed from working context. Its immutable original remains saved. The original tool call and source/version arguments above are unchanged: repeat that lookup if needed to verify facts. Do not infer facts from this marker.]";
|
|
9047
|
+
if (block.images?.length)
|
|
9048
|
+
return {
|
|
9049
|
+
content: marker,
|
|
9050
|
+
tool_use_id: block.tool_use_id,
|
|
9051
|
+
type: block.type
|
|
9052
|
+
};
|
|
9029
9053
|
return block.content.length > marker.length ? { ...block, content: marker } : block;
|
|
9030
9054
|
})
|
|
9031
9055
|
}));
|
|
@@ -9167,10 +9191,11 @@ export {
|
|
|
9167
9191
|
streamAIWithTools,
|
|
9168
9192
|
streamWithAIContext,
|
|
9169
9193
|
tableCard,
|
|
9194
|
+
toolOutputText,
|
|
9170
9195
|
verifyOpenRouterWebhookSignature,
|
|
9171
9196
|
withResilience,
|
|
9172
9197
|
xai
|
|
9173
9198
|
};
|
|
9174
9199
|
|
|
9175
|
-
//# debugId=
|
|
9200
|
+
//# debugId=94F48F9A5CBF872A64756E2164756E21
|
|
9176
9201
|
//# sourceMappingURL=index.js.map
|