@absolutejs/ai 0.2.4 → 0.2.6
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/README.md +11 -0
- package/changelog.json +30 -0
- package/dist/ai/index.js +36 -24
- package/dist/ai/index.js.map +6 -6
- package/dist/ai/providers/anthropic.js +33 -14
- package/dist/ai/providers/anthropic.js.map +4 -4
- package/dist/ai/providers/gemini.js +2 -1
- package/dist/ai/providers/gemini.js.map +3 -3
- package/dist/ai/providers/openai.js +2 -1
- package/dist/ai/providers/openai.js.map +3 -3
- package/dist/ai/providers/openaiCompatible.js +2 -1
- package/dist/ai/providers/openaiCompatible.js.map +3 -3
- package/dist/ai/providers/openaiResponses.js +2 -1
- package/dist/ai/providers/openaiResponses.js.map +3 -3
- package/dist/ai/providers/openrouter.js +33 -14
- package/dist/ai/providers/openrouter.js.map +4 -4
- package/dist/src/ai/inputCapacity.d.ts +4 -0
- package/dist/types/ai.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.2.6 — 2026-09-20
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Count supported Anthropic caller input when hosted tools cannot be pre-counted, preserving full generation requests and exposing honest count scope** (`anthropic`, `inspectAIInput`, `AIInputCapacity`, `AIProviderConfig`, `createAIProviderRouter`)
|
|
14
|
+
|
|
15
|
+
## 0.2.5 — 2026-09-20
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **Allow SSE tool loops to reread changed files without silently completing; retain turn, time and token limits** (`streamAIToSSE`)
|
|
20
|
+
|
|
9
21
|
## 0.2.4 — 2026-09-20
|
|
10
22
|
|
|
11
23
|
### Added
|
package/README.md
CHANGED
|
@@ -388,6 +388,17 @@ Unknown models/providers fail with `AIInputError` (`capacity_unavailable`) rathe
|
|
|
388
388
|
than guessing. Custom providers can implement the optional `inputCapacity`
|
|
389
389
|
capability. Existing `stream` calls remain unchanged.
|
|
390
390
|
|
|
391
|
+
Anthropic cannot pre-count hosted web search, web fetch, code execution or tool
|
|
392
|
+
search definitions. The adapter omits only those definitions (and dependent tool
|
|
393
|
+
choice) from the counting request, preserving system text, messages and client
|
|
394
|
+
tools. `inspectAIInput(...).inputTokenScope` is `"caller-input"` in this case,
|
|
395
|
+
and `"request"` otherwise; provider routers preserve the scope. `fits` describes
|
|
396
|
+
the counted scope, not a guarantee that future hosted results fit. Generation
|
|
397
|
+
still sends the complete hosted-tool configuration, and Anthropic validates the
|
|
398
|
+
full request and reports actual usage. No guessed overhead or paid probe is used.
|
|
399
|
+
Custom providers can expose `inputCapacity.countScope(params)` without changing
|
|
400
|
+
the numeric `countTokens` contract. See the [Anthropic counting limitations](https://platform.claude.com/docs/en/build-with-claude/token-counting).
|
|
401
|
+
|
|
391
402
|
Automatic preparation supports text-only history. It rejects oversized tool or
|
|
392
403
|
multimodal histories rather than flattening them. Handle preparation errors in
|
|
393
404
|
the UI while preserving the original input; expose retry without clearing the
|
package/changelog.json
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
"contract": 1,
|
|
3
3
|
"name": "@absolutejs/ai",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"changes": [
|
|
7
|
+
{
|
|
8
|
+
"kind": "fixed",
|
|
9
|
+
"summary": "Count supported Anthropic caller input when hosted tools cannot be pre-counted, preserving full generation requests and exposing honest count scope",
|
|
10
|
+
"symbols": [
|
|
11
|
+
"anthropic",
|
|
12
|
+
"inspectAIInput",
|
|
13
|
+
"AIInputCapacity",
|
|
14
|
+
"AIProviderConfig",
|
|
15
|
+
"createAIProviderRouter"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"date": "2026-09-20",
|
|
20
|
+
"version": "0.2.6"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"changes": [
|
|
24
|
+
{
|
|
25
|
+
"kind": "fixed",
|
|
26
|
+
"summary": "Allow SSE tool loops to reread changed files without silently completing; retain turn, time and token limits",
|
|
27
|
+
"symbols": [
|
|
28
|
+
"streamAIToSSE"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"date": "2026-09-20",
|
|
33
|
+
"version": "0.2.5"
|
|
34
|
+
},
|
|
5
35
|
{
|
|
6
36
|
"changes": [
|
|
7
37
|
{
|
package/dist/ai/index.js
CHANGED
|
@@ -40,6 +40,7 @@ var inspectAIInput = async (provider, params) => {
|
|
|
40
40
|
return {
|
|
41
41
|
limits,
|
|
42
42
|
inputTokens,
|
|
43
|
+
inputTokenScope: provider.inputCapacity.countScope?.(params) ?? "request",
|
|
43
44
|
outputTokens,
|
|
44
45
|
availableInputTokens,
|
|
45
46
|
fits: outputTokens <= maxOutputTokens && inputTokens <= availableInputTokens
|
|
@@ -2886,6 +2887,35 @@ var anthropic = (config2) => {
|
|
|
2886
2887
|
signal: params.signal ? AbortSignal.any([params.signal, AbortSignal.timeout(15000)]) : AbortSignal.timeout(15000)
|
|
2887
2888
|
}));
|
|
2888
2889
|
};
|
|
2890
|
+
const countingRequest = (params) => {
|
|
2891
|
+
const built = buildRequestBody3(params, configuredMax, promptCaching);
|
|
2892
|
+
const body = config2.transformRequestBody ? config2.transformRequestBody(built, params) : built;
|
|
2893
|
+
const counted = {};
|
|
2894
|
+
for (const key of [
|
|
2895
|
+
"model",
|
|
2896
|
+
"messages",
|
|
2897
|
+
"system",
|
|
2898
|
+
"tools",
|
|
2899
|
+
"tool_choice",
|
|
2900
|
+
"thinking"
|
|
2901
|
+
])
|
|
2902
|
+
if (body[key] !== undefined)
|
|
2903
|
+
counted[key] = body[key];
|
|
2904
|
+
const tools = Array.isArray(body.tools) ? body.tools : [];
|
|
2905
|
+
const hosted = tools.filter((tool) => isRecord4(tool) && typeof tool.type === "string" && /^(web_search_|web_fetch_|code_execution_|bash_code_execution_|text_editor_code_execution_|tool_search_tool_)/u.test(tool.type));
|
|
2906
|
+
const scope = hosted.length > 0 || body.mcp_servers !== undefined ? "caller-input" : "request";
|
|
2907
|
+
if (hosted.length > 0) {
|
|
2908
|
+
const clientTools = tools.filter((tool) => !hosted.includes(tool));
|
|
2909
|
+
if (clientTools.length)
|
|
2910
|
+
counted.tools = clientTools;
|
|
2911
|
+
else
|
|
2912
|
+
delete counted.tools;
|
|
2913
|
+
const choice = counted.tool_choice;
|
|
2914
|
+
if (!clientTools.length || isRecord4(choice) && choice.type === "tool" && hosted.some((tool) => isRecord4(tool) && tool.name === choice.name))
|
|
2915
|
+
delete counted.tool_choice;
|
|
2916
|
+
}
|
|
2917
|
+
return { counted, scope };
|
|
2918
|
+
};
|
|
2889
2919
|
const inputCapacity = {
|
|
2890
2920
|
outputTokens: (params) => {
|
|
2891
2921
|
const built = buildRequestBody3(params, configuredMax, promptCaching);
|
|
@@ -2901,20 +2931,9 @@ var anthropic = (config2) => {
|
|
|
2901
2931
|
maxOutputTokens: positiveTokenLimit(data.max_tokens)
|
|
2902
2932
|
};
|
|
2903
2933
|
}, config2.tokenSource || typeof config2.headers === "function" ? 0 : 300000),
|
|
2934
|
+
countScope: (params) => countingRequest(params).scope,
|
|
2904
2935
|
countTokens: async (params) => {
|
|
2905
|
-
const
|
|
2906
|
-
const body = config2.transformRequestBody ? config2.transformRequestBody(built, params) : built;
|
|
2907
|
-
const counted = {};
|
|
2908
|
-
for (const key of [
|
|
2909
|
-
"model",
|
|
2910
|
-
"messages",
|
|
2911
|
-
"system",
|
|
2912
|
-
"tools",
|
|
2913
|
-
"tool_choice",
|
|
2914
|
-
"thinking"
|
|
2915
|
-
])
|
|
2916
|
-
if (body[key] !== undefined)
|
|
2917
|
-
counted[key] = body[key];
|
|
2936
|
+
const { counted } = countingRequest(params);
|
|
2918
2937
|
return inputTokenCount((await capacityRequest(params, "/v1/messages/count_tokens", counted)).input_tokens);
|
|
2919
2938
|
}
|
|
2920
2939
|
};
|
|
@@ -4892,7 +4911,6 @@ var executeTool2 = async (options, toolName, toolInput) => {
|
|
|
4892
4911
|
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
4893
4912
|
}
|
|
4894
4913
|
};
|
|
4895
|
-
var serializeToolCall2 = (name, input) => `${name}:${JSON.stringify(input)}`;
|
|
4896
4914
|
var contentEvent = (options, renderers, delta, full) => options.structuredEvents ? { data: JSON.stringify({ delta, full }), event: "content" } : { data: renderers.chunk(delta, full), event: "content" };
|
|
4897
4915
|
var thinkingEvent = (options, renderers, text) => options.structuredEvents ? { data: JSON.stringify({ text }), event: "thinking" } : { data: renderers.thinking(text), event: "thinking" };
|
|
4898
4916
|
var imageEvent = (options, renderers, data, format, revisedPrompt) => options.structuredEvents ? { data: JSON.stringify({ data, format, revisedPrompt }), event: "images" } : { data: renderers.image(data, format, revisedPrompt), event: "images" };
|
|
@@ -5074,7 +5092,6 @@ var executeToolCalls = async function* (pendingToolCalls, options, renderers, tu
|
|
|
5074
5092
|
tool_use_id: toolCall.id,
|
|
5075
5093
|
type: "tool_result"
|
|
5076
5094
|
});
|
|
5077
|
-
turnState.executedToolKeys.add(serializeToolCall2(toolCall.name, toolCall.input));
|
|
5078
5095
|
}
|
|
5079
5096
|
return toolResultBlocks;
|
|
5080
5097
|
};
|
|
@@ -5089,12 +5106,7 @@ var consumeStream2 = async function* (stream, chunkState, renderers, options, tu
|
|
|
5089
5106
|
turnState.fullResponse = prevResponse + chunk.content;
|
|
5090
5107
|
}
|
|
5091
5108
|
};
|
|
5092
|
-
var shouldStopToolLoop = (chunkState,
|
|
5093
|
-
if (chunkState.pendingToolCalls.length === 0 || signal.aborted) {
|
|
5094
|
-
return true;
|
|
5095
|
-
}
|
|
5096
|
-
return chunkState.pendingToolCalls.every((toolCall) => turnState.executedToolKeys.has(serializeToolCall2(toolCall.name, toolCall.input)));
|
|
5097
|
-
};
|
|
5109
|
+
var shouldStopToolLoop = (chunkState, signal) => chunkState.pendingToolCalls.length === 0 || signal.aborted;
|
|
5098
5110
|
var processTurn = async function* (chunkState, options, renderers, turnState) {
|
|
5099
5111
|
turnState.currentMessages.push({
|
|
5100
5112
|
content: chunkState.contentBlocks,
|
|
@@ -5110,7 +5122,6 @@ var streamTurns = async function* (options, renderers, messages, signal, startTi
|
|
|
5110
5122
|
const turnState = {
|
|
5111
5123
|
allToolsHtml: "",
|
|
5112
5124
|
currentMessages: [...messages],
|
|
5113
|
-
executedToolKeys: new Set,
|
|
5114
5125
|
fullResponse: "",
|
|
5115
5126
|
turn: 0
|
|
5116
5127
|
};
|
|
@@ -5172,7 +5183,7 @@ var streamTurns = async function* (options, renderers, messages, signal, startTi
|
|
|
5172
5183
|
yield stoppedEvent(options, renderers, "max_duration_ms", `Stopped: time budget reached (${Math.round((Date.now() - startTime) / 1000)}s over ` + `${turnState.turn} turns). Narrow the request or raise maxDurationMs.`);
|
|
5173
5184
|
return;
|
|
5174
5185
|
}
|
|
5175
|
-
if (shouldStopToolLoop(chunkState,
|
|
5186
|
+
if (shouldStopToolLoop(chunkState, signal)) {
|
|
5176
5187
|
if (signal.aborted) {
|
|
5177
5188
|
finishReason = "aborted";
|
|
5178
5189
|
yield stoppedEvent(options, renderers, "aborted", "Aborted by caller.");
|
|
@@ -8773,6 +8784,7 @@ var createAIProviderRouter = (routes) => {
|
|
|
8773
8784
|
outputTokens: (params) => capacity(params).outputTokens?.(params),
|
|
8774
8785
|
isContextError: (error) => [...new Set(providers.values())].some((provider) => provider.inputCapacity?.isContextError?.(error) === true),
|
|
8775
8786
|
getLimits: async (params) => capacity(params).getLimits(params),
|
|
8787
|
+
countScope: (params) => capacity(params).countScope?.(params) ?? "request",
|
|
8776
8788
|
countTokens: async (params) => capacity(params).countTokens(params)
|
|
8777
8789
|
},
|
|
8778
8790
|
stream: (params) => resolve(params).stream(params)
|
|
@@ -8888,5 +8900,5 @@ export {
|
|
|
8888
8900
|
xai
|
|
8889
8901
|
};
|
|
8890
8902
|
|
|
8891
|
-
//# debugId=
|
|
8903
|
+
//# debugId=33E15B35E923B1E064756E2164756E21
|
|
8892
8904
|
//# sourceMappingURL=index.js.map
|