@absolutejs/ai 0.3.4 → 0.4.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 +12 -0
- package/changelog.json +24 -0
- package/dist/ai/index.js +89 -7
- package/dist/ai/index.js.map +4 -4
- package/dist/ai/providers/anthropic.js +71 -3
- package/dist/ai/providers/anthropic.js.map +3 -3
- package/dist/ai/providers/openrouter.js +71 -3
- package/dist/ai/providers/openrouter.js.map +3 -3
- package/dist/types/ai.d.ts +15 -1
- package/dist/types/anthropic.d.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ 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.4.0 — 2026-10-16
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Report a tool call's input while it streams: a `tool_input` chunk, and a `preparing` tools event from streamAIToSSE** (`AIChunk`, `AIToolInputChunk`, `streamAIToSSE`)
|
|
14
|
+
A tool call with a long input, such as a whole file, takes the model a minute or more to write, and until now nothing arrived meanwhile. The Anthropic provider now yields a `tool_input` chunk when a tool call starts and every 2 KB of its input, with the tool name, the characters received and the top-level string fields already complete (a path, a name; at most 200 characters each, never read from inside other values). With structured events, streamAIToSSE turns it into a `tools` event with status `preparing`. Consumers that do not know the chunk can ignore it.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **streamAIToSSE sends a turn's text back as one block, not one per streamed delta, so agent loops stop re-writing their prompt cache** (`streamAIToSSE`)
|
|
19
|
+
streamAIToSSE stored every streamed text delta as its own content block, so an agent loop sent each turn's text back as dozens of blocks. Anthropic looks back only 20 blocks for an earlier cache entry, and a turn with a few sentences of text pushed the previous request's entry out of reach: the next request re-wrote the whole history at the cache-write price. A run of text is now one block, as generateAI and streamAIWithTools already kept it.
|
|
20
|
+
|
|
9
21
|
## 0.3.4 — 2026-10-15
|
|
10
22
|
|
|
11
23
|
### Fixed
|
package/changelog.json
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
"contract": 1,
|
|
3
3
|
"name": "@absolutejs/ai",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"changes": [
|
|
7
|
+
{
|
|
8
|
+
"detail": "streamAIToSSE stored every streamed text delta as its own content block, so an agent loop sent each turn's text back as dozens of blocks. Anthropic looks back only 20 blocks for an earlier cache entry, and a turn with a few sentences of text pushed the previous request's entry out of reach: the next request re-wrote the whole history at the cache-write price. A run of text is now one block, as generateAI and streamAIWithTools already kept it.",
|
|
9
|
+
"kind": "fixed",
|
|
10
|
+
"summary": "streamAIToSSE sends a turn's text back as one block, not one per streamed delta, so agent loops stop re-writing their prompt cache",
|
|
11
|
+
"symbols": [
|
|
12
|
+
"streamAIToSSE"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"detail": "A tool call with a long input, such as a whole file, takes the model a minute or more to write, and until now nothing arrived meanwhile. The Anthropic provider now yields a `tool_input` chunk when a tool call starts and every 2 KB of its input, with the tool name, the characters received and the top-level string fields already complete (a path, a name; at most 200 characters each, never read from inside other values). With structured events, streamAIToSSE turns it into a `tools` event with status `preparing`. Consumers that do not know the chunk can ignore it.",
|
|
17
|
+
"kind": "added",
|
|
18
|
+
"summary": "Report a tool call's input while it streams: a `tool_input` chunk, and a `preparing` tools event from streamAIToSSE",
|
|
19
|
+
"symbols": [
|
|
20
|
+
"AIChunk",
|
|
21
|
+
"AIToolInputChunk",
|
|
22
|
+
"streamAIToSSE"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"date": "2026-10-16",
|
|
27
|
+
"version": "0.4.0"
|
|
28
|
+
},
|
|
5
29
|
{
|
|
6
30
|
"changes": [
|
|
7
31
|
{
|
package/dist/ai/index.js
CHANGED
|
@@ -999,10 +999,11 @@ var maybeFlushThinking = (chunkState) => {
|
|
|
999
999
|
};
|
|
1000
1000
|
var processTextChunk = function* (content, chunkState, renderers, fullResponse, options) {
|
|
1001
1001
|
maybeFlushThinking(chunkState);
|
|
1002
|
-
chunkState.contentBlocks.
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1002
|
+
const last = chunkState.contentBlocks.at(-1);
|
|
1003
|
+
if (last?.type === "text")
|
|
1004
|
+
last.content += content;
|
|
1005
|
+
else
|
|
1006
|
+
chunkState.contentBlocks.push({ content, type: "text" });
|
|
1006
1007
|
yield contentEvent(options, renderers, content, fullResponse + content);
|
|
1007
1008
|
};
|
|
1008
1009
|
var processImageChunk = function* (chunk, renderers, options) {
|
|
@@ -1056,6 +1057,19 @@ var processChunk = function* (chunk, chunkState, renderers, options, fullRespons
|
|
|
1056
1057
|
case "tool_use":
|
|
1057
1058
|
processToolUseChunk(chunk, chunkState);
|
|
1058
1059
|
break;
|
|
1060
|
+
case "tool_input":
|
|
1061
|
+
if (options.structuredEvents)
|
|
1062
|
+
yield {
|
|
1063
|
+
data: JSON.stringify({
|
|
1064
|
+
fields: chunk.fields,
|
|
1065
|
+
id: chunk.id,
|
|
1066
|
+
name: chunk.name,
|
|
1067
|
+
receivedChars: chunk.receivedChars,
|
|
1068
|
+
status: "preparing"
|
|
1069
|
+
}),
|
|
1070
|
+
event: "tools"
|
|
1071
|
+
};
|
|
1072
|
+
break;
|
|
1059
1073
|
case "provider_event":
|
|
1060
1074
|
maybeFlushThinking(chunkState);
|
|
1061
1075
|
chunkState.contentBlocks.push({
|
|
@@ -6878,14 +6892,79 @@ var getNumber = (obj, key) => {
|
|
|
6878
6892
|
}
|
|
6879
6893
|
return 0;
|
|
6880
6894
|
};
|
|
6895
|
+
var TOOL_INPUT_PROGRESS_CHARS = 2048;
|
|
6896
|
+
var TOOL_INPUT_FIELD_MAX_CHARS = 200;
|
|
6897
|
+
var readJsonString = (json, start) => {
|
|
6898
|
+
for (let index = start + 1;index < json.length; index += 1) {
|
|
6899
|
+
if (json[index] === "\\")
|
|
6900
|
+
index += 1;
|
|
6901
|
+
else if (json[index] === '"')
|
|
6902
|
+
return { end: index + 1, raw: json.slice(start + 1, index) };
|
|
6903
|
+
}
|
|
6904
|
+
return;
|
|
6905
|
+
};
|
|
6906
|
+
var decodeJsonString = (raw) => {
|
|
6907
|
+
try {
|
|
6908
|
+
const value = JSON.parse(`"${raw}"`);
|
|
6909
|
+
return typeof value === "string" ? value : undefined;
|
|
6910
|
+
} catch {
|
|
6911
|
+
return;
|
|
6912
|
+
}
|
|
6913
|
+
};
|
|
6914
|
+
var completeStringFields = (partialJson) => {
|
|
6915
|
+
const fields = {};
|
|
6916
|
+
let depth = 0;
|
|
6917
|
+
let key;
|
|
6918
|
+
let index = 0;
|
|
6919
|
+
while (index < partialJson.length) {
|
|
6920
|
+
const character = partialJson[index];
|
|
6921
|
+
if (character === '"') {
|
|
6922
|
+
const text = readJsonString(partialJson, index);
|
|
6923
|
+
if (!text)
|
|
6924
|
+
break;
|
|
6925
|
+
if (depth === 1 && key === undefined) {
|
|
6926
|
+
const after = partialJson.slice(text.end).trimStart();
|
|
6927
|
+
if (after.startsWith(":"))
|
|
6928
|
+
key = text.raw;
|
|
6929
|
+
} else if (depth === 1 && key !== undefined) {
|
|
6930
|
+
const value = decodeJsonString(text.raw);
|
|
6931
|
+
if (value !== undefined && value.length <= TOOL_INPUT_FIELD_MAX_CHARS)
|
|
6932
|
+
fields[key] = value;
|
|
6933
|
+
key = undefined;
|
|
6934
|
+
}
|
|
6935
|
+
index = text.end;
|
|
6936
|
+
continue;
|
|
6937
|
+
}
|
|
6938
|
+
if (character === "{" || character === "[")
|
|
6939
|
+
depth += 1;
|
|
6940
|
+
else if (character === "}" || character === "]")
|
|
6941
|
+
depth -= 1;
|
|
6942
|
+
else if (character === "," && depth === 1)
|
|
6943
|
+
key = undefined;
|
|
6944
|
+
index += 1;
|
|
6945
|
+
}
|
|
6946
|
+
return fields;
|
|
6947
|
+
};
|
|
6948
|
+
var toolInputProgress = (state) => {
|
|
6949
|
+
state.toolInputReported = state.toolInputJson.length;
|
|
6950
|
+
return {
|
|
6951
|
+
fields: completeStringFields(state.toolInputJson),
|
|
6952
|
+
id: state.currentToolId,
|
|
6953
|
+
name: state.currentToolName,
|
|
6954
|
+
receivedChars: state.toolInputJson.length,
|
|
6955
|
+
type: "tool_input"
|
|
6956
|
+
};
|
|
6957
|
+
};
|
|
6881
6958
|
var handleContentBlockStart = (parsed, state) => {
|
|
6882
6959
|
const block = getRecord(parsed, "content_block");
|
|
6883
6960
|
if (block && block.type === "tool_use") {
|
|
6884
6961
|
state.currentToolId = getString(block, "id");
|
|
6885
6962
|
state.currentToolName = getString(block, "name");
|
|
6886
6963
|
state.toolInputJson = "";
|
|
6964
|
+
state.toolInputReported = 0;
|
|
6887
6965
|
state.isThinkingBlock = false;
|
|
6888
6966
|
state.currentProviderBlock = undefined;
|
|
6967
|
+
return toolInputProgress(state);
|
|
6889
6968
|
} else if (block && block.type === "thinking") {
|
|
6890
6969
|
state.isThinkingBlock = true;
|
|
6891
6970
|
state.thinkingSignature = "";
|
|
@@ -6895,6 +6974,7 @@ var handleContentBlockStart = (parsed, state) => {
|
|
|
6895
6974
|
state.currentProviderBlock = block && block.type !== "text" ? { ...block } : undefined;
|
|
6896
6975
|
state.providerBlockInputJson = "";
|
|
6897
6976
|
}
|
|
6977
|
+
return;
|
|
6898
6978
|
};
|
|
6899
6979
|
var handleContentBlockDelta = (parsed, state) => {
|
|
6900
6980
|
const delta = getRecord(parsed, "delta");
|
|
@@ -6929,6 +7009,8 @@ var handleContentBlockDelta = (parsed, state) => {
|
|
|
6929
7009
|
state.providerBlockInputJson += getString(delta, "partial_json");
|
|
6930
7010
|
} else {
|
|
6931
7011
|
state.toolInputJson += getString(delta, "partial_json");
|
|
7012
|
+
if (state.toolInputJson.length - state.toolInputReported >= TOOL_INPUT_PROGRESS_CHARS)
|
|
7013
|
+
return toolInputProgress(state);
|
|
6932
7014
|
}
|
|
6933
7015
|
}
|
|
6934
7016
|
if (delta.type === "signature_delta") {
|
|
@@ -7048,8 +7130,7 @@ var handleError = (parsed, state) => {
|
|
|
7048
7130
|
var processEvent = (eventType, parsed, state) => {
|
|
7049
7131
|
switch (eventType) {
|
|
7050
7132
|
case "content_block_start": {
|
|
7051
|
-
handleContentBlockStart(parsed, state);
|
|
7052
|
-
return;
|
|
7133
|
+
return handleContentBlockStart(parsed, state);
|
|
7053
7134
|
}
|
|
7054
7135
|
case "content_block_delta": {
|
|
7055
7136
|
return handleContentBlockDelta(parsed, state);
|
|
@@ -7152,6 +7233,7 @@ async function* parseSSEStream4(body, providerName, signal) {
|
|
|
7152
7233
|
stopReason: "",
|
|
7153
7234
|
thinkingSignature: "",
|
|
7154
7235
|
toolInputJson: "",
|
|
7236
|
+
toolInputReported: 0,
|
|
7155
7237
|
usage: undefined,
|
|
7156
7238
|
providerName,
|
|
7157
7239
|
providerBlockInputJson: ""
|
|
@@ -9062,5 +9144,5 @@ export {
|
|
|
9062
9144
|
xai
|
|
9063
9145
|
};
|
|
9064
9146
|
|
|
9065
|
-
//# debugId=
|
|
9147
|
+
//# debugId=4ED2ED351363579D64756E2164756E21
|
|
9066
9148
|
//# sourceMappingURL=index.js.map
|