@absolutejs/ai 0.2.5 → 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 CHANGED
@@ -6,6 +6,12 @@ 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
+
9
15
  ## 0.2.5 — 2026-09-20
10
16
 
11
17
  ### Fixed
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,23 @@
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
+ },
5
22
  {
6
23
  "changes": [
7
24
  {
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 built = buildRequestBody3(params, configuredMax, promptCaching);
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
  };
@@ -8765,6 +8784,7 @@ var createAIProviderRouter = (routes) => {
8765
8784
  outputTokens: (params) => capacity(params).outputTokens?.(params),
8766
8785
  isContextError: (error) => [...new Set(providers.values())].some((provider) => provider.inputCapacity?.isContextError?.(error) === true),
8767
8786
  getLimits: async (params) => capacity(params).getLimits(params),
8787
+ countScope: (params) => capacity(params).countScope?.(params) ?? "request",
8768
8788
  countTokens: async (params) => capacity(params).countTokens(params)
8769
8789
  },
8770
8790
  stream: (params) => resolve(params).stream(params)
@@ -8880,5 +8900,5 @@ export {
8880
8900
  xai
8881
8901
  };
8882
8902
 
8883
- //# debugId=3B9702E9B461551864756E2164756E21
8903
+ //# debugId=33E15B35E923B1E064756E2164756E21
8884
8904
  //# sourceMappingURL=index.js.map