@absolutejs/ai 0.1.2 → 0.2.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 +13 -0
- package/README.md +18 -0
- package/changelog.json +35 -0
- package/dist/ai/index.js +51 -8
- package/dist/ai/index.js.map +8 -7
- package/dist/ai/providers/openai.js +7 -5
- package/dist/ai/providers/openai.js.map +4 -4
- package/dist/ai/providers/openaiCompatible.js +7 -5
- package/dist/ai/providers/openaiCompatible.js.map +4 -4
- package/dist/ai/providers/openaiResponses.js +4 -3
- package/dist/ai/providers/openaiResponses.js.map +3 -3
- package/dist/ai/providers/openrouter.js +7 -5
- package/dist/ai/providers/openrouter.js.map +4 -4
- package/dist/src/ai/index.d.ts +1 -0
- package/dist/src/ai/providerRouter.d.ts +4 -0
- package/dist/src/ai/streamAIWithTools.d.ts +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ 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.0 — 2026-09-16
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Expose each tool-loop turn response metadata so consumers can price the actual served processing tier** (`streamAIWithTools`, `StreamAIWithToolsEvent`)
|
|
14
|
+
- **Route exact model IDs to the same provider for token counting, capacity checks, preparation and generation** (`createAIProviderRouter`)
|
|
15
|
+
|
|
16
|
+
## 0.1.3 — 2026-09-16
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **Exclude OpenAI cache-write tokens from ordinary input so cache-aware billing never charges the same tokens twice** (`openai`, `openaiResponses`)
|
|
21
|
+
|
|
9
22
|
## 0.1.2 — 2026-09-16
|
|
10
23
|
|
|
11
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -496,3 +496,21 @@ its explicit `false` remains an opt-out unless `contextPolicy` is supplied.
|
|
|
496
496
|
Applications should surface typed failures with saved-input retry UI. These
|
|
497
497
|
changes do not guarantee detection of every provider-side accounting discrepancy
|
|
498
498
|
or provide exactly-once external execution after a process crash.
|
|
499
|
+
|
|
500
|
+
### Models from different providers
|
|
501
|
+
|
|
502
|
+
`createAIProviderRouter` routes exact model IDs for generation, token counting and
|
|
503
|
+
capacity checks. It snapshots the route map and fails explicitly for unknown
|
|
504
|
+
models or a route without counting support. It does not choose a model or add a
|
|
505
|
+
fallback. For example, preparation and final answering can use different vendors:
|
|
506
|
+
|
|
507
|
+
```ts
|
|
508
|
+
const provider = createAIProviderRouter({
|
|
509
|
+
"claude-haiku-4-5-20251001": anthropicProvider,
|
|
510
|
+
"gpt-5.6-luna": openaiResponsesProvider,
|
|
511
|
+
});
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
`streamAIWithTools` includes each completed turn's provider response `metadata`
|
|
515
|
+
on its `turn` event. Price that turn using the returned `serviceTier`, when
|
|
516
|
+
available, rather than assuming that the requested processing tier was served.
|
package/changelog.json
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
"contract": 1,
|
|
3
3
|
"name": "@absolutejs/ai",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"changes": [
|
|
7
|
+
{
|
|
8
|
+
"kind": "added",
|
|
9
|
+
"summary": "Expose each tool-loop turn response metadata so consumers can price the actual served processing tier",
|
|
10
|
+
"symbols": [
|
|
11
|
+
"streamAIWithTools",
|
|
12
|
+
"StreamAIWithToolsEvent"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"kind": "added",
|
|
17
|
+
"summary": "Route exact model IDs to the same provider for token counting, capacity checks, preparation and generation",
|
|
18
|
+
"symbols": [
|
|
19
|
+
"createAIProviderRouter"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"date": "2026-09-16",
|
|
24
|
+
"version": "0.2.0"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"changes": [
|
|
28
|
+
{
|
|
29
|
+
"kind": "fixed",
|
|
30
|
+
"summary": "Exclude OpenAI cache-write tokens from ordinary input so cache-aware billing never charges the same tokens twice",
|
|
31
|
+
"symbols": [
|
|
32
|
+
"openai",
|
|
33
|
+
"openaiResponses"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"date": "2026-09-16",
|
|
38
|
+
"version": "0.1.3"
|
|
39
|
+
},
|
|
5
40
|
{
|
|
6
41
|
"changes": [
|
|
7
42
|
{
|
package/dist/ai/index.js
CHANGED
|
@@ -877,11 +877,12 @@ var extractUsage = (response) => {
|
|
|
877
877
|
const outputDetails = isRecord(usage.output_tokens_details) ? usage.output_tokens_details : undefined;
|
|
878
878
|
const inputDetails = isRecord(usage.input_tokens_details) ? usage.input_tokens_details : undefined;
|
|
879
879
|
const costDetails = isRecord(usage.cost_details) ? usage.cost_details : undefined;
|
|
880
|
+
const written = inputDetails && typeof inputDetails.cache_write_tokens === "number" ? inputDetails.cache_write_tokens : 0;
|
|
880
881
|
const normalized = {
|
|
881
882
|
cacheReadInputTokens: cached,
|
|
882
|
-
cacheWriteInputTokens:
|
|
883
|
+
cacheWriteInputTokens: written || undefined,
|
|
883
884
|
costCredits: typeof usage.cost === "number" ? usage.cost : undefined,
|
|
884
|
-
inputTokens: Math.max(0, input - cached),
|
|
885
|
+
inputTokens: Math.max(0, input - cached - written),
|
|
885
886
|
outputTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0,
|
|
886
887
|
reasoningTokens: outputDetails && typeof outputDetails.reasoning_tokens === "number" ? outputDetails.reasoning_tokens : undefined,
|
|
887
888
|
upstreamInferenceCostCredits: costDetails && typeof costDetails.upstream_inference_cost === "number" ? costDetails.upstream_inference_cost : undefined
|
|
@@ -1410,11 +1411,12 @@ var flushPendingToolCalls = function* (pendingToolCalls) {
|
|
|
1410
1411
|
var extractUsage2 = (parsedUsage) => {
|
|
1411
1412
|
const prompt = parsedUsage.prompt_tokens ?? 0;
|
|
1412
1413
|
const cached = parsedUsage.cached_tokens ?? 0;
|
|
1414
|
+
const written = parsedUsage.cache_write_tokens ?? 0;
|
|
1413
1415
|
return {
|
|
1414
1416
|
cacheReadInputTokens: cached,
|
|
1415
|
-
cacheWriteInputTokens:
|
|
1417
|
+
cacheWriteInputTokens: written || undefined,
|
|
1416
1418
|
costCredits: parsedUsage.cost,
|
|
1417
|
-
inputTokens: Math.max(0, prompt - cached),
|
|
1419
|
+
inputTokens: Math.max(0, prompt - cached - written),
|
|
1418
1420
|
outputTokens: parsedUsage.completion_tokens ?? 0,
|
|
1419
1421
|
reasoningTokens: parsedUsage.reasoning_tokens || undefined,
|
|
1420
1422
|
upstreamInferenceCostCredits: parsedUsage.upstream_inference_cost || undefined
|
|
@@ -5830,6 +5832,7 @@ var streamAIWithTools = async function* (options) {
|
|
|
5830
5832
|
let thinking = null;
|
|
5831
5833
|
let stopReason;
|
|
5832
5834
|
let turnUsage;
|
|
5835
|
+
let turnMetadata;
|
|
5833
5836
|
let completed = false;
|
|
5834
5837
|
for await (const chunk of stream) {
|
|
5835
5838
|
if (base.signal?.aborted)
|
|
@@ -5875,19 +5878,30 @@ var streamAIWithTools = async function* (options) {
|
|
|
5875
5878
|
thinking = flushThinking3(blocks, thinking);
|
|
5876
5879
|
stopReason = chunk.stopReason;
|
|
5877
5880
|
turnUsage = chunk.usage;
|
|
5881
|
+
turnMetadata = chunk.metadata;
|
|
5878
5882
|
completed = true;
|
|
5879
5883
|
}
|
|
5880
5884
|
}
|
|
5881
5885
|
thinking = flushThinking3(blocks, thinking);
|
|
5882
5886
|
if (policy !== false && !completed)
|
|
5883
5887
|
throw new AIInputError("capacity_unavailable", "The model response was interrupted before completion. Preserve the original input for retry.");
|
|
5884
|
-
return {
|
|
5888
|
+
return {
|
|
5889
|
+
blocks,
|
|
5890
|
+
pending,
|
|
5891
|
+
stopReason,
|
|
5892
|
+
usage: turnUsage,
|
|
5893
|
+
metadata: turnMetadata
|
|
5894
|
+
};
|
|
5885
5895
|
};
|
|
5886
5896
|
while (turn < maxTurns) {
|
|
5887
5897
|
turn += 1;
|
|
5888
5898
|
const outcome = yield* streamOneTurn();
|
|
5889
5899
|
usage = mergeUsage(usage, outcome.usage);
|
|
5890
|
-
yield {
|
|
5900
|
+
yield {
|
|
5901
|
+
type: "turn",
|
|
5902
|
+
usage: outcome.usage,
|
|
5903
|
+
...outcome.metadata ? { metadata: outcome.metadata } : {}
|
|
5904
|
+
};
|
|
5891
5905
|
const { blocks, pending } = outcome;
|
|
5892
5906
|
allToolCalls.push(...pending);
|
|
5893
5907
|
if (outcome.stopReason === "pause_turn" && turn < maxTurns && base.signal?.aborted !== true) {
|
|
@@ -5937,7 +5951,11 @@ var streamAIWithTools = async function* (options) {
|
|
|
5937
5951
|
turn += 1;
|
|
5938
5952
|
const final = yield* streamOneTurn("none");
|
|
5939
5953
|
usage = mergeUsage(usage, final.usage);
|
|
5940
|
-
yield {
|
|
5954
|
+
yield {
|
|
5955
|
+
type: "turn",
|
|
5956
|
+
usage: final.usage,
|
|
5957
|
+
...final.metadata ? { metadata: final.metadata } : {}
|
|
5958
|
+
};
|
|
5941
5959
|
allToolCalls.push(...final.pending);
|
|
5942
5960
|
break;
|
|
5943
5961
|
}
|
|
@@ -8595,6 +8613,30 @@ var createAIStoredToolResultRecovery = (toolNames) => async ({ params }) => {
|
|
|
8595
8613
|
})
|
|
8596
8614
|
}));
|
|
8597
8615
|
};
|
|
8616
|
+
// src/ai/providerRouter.ts
|
|
8617
|
+
var createAIProviderRouter = (routes) => {
|
|
8618
|
+
const providers = new Map(Object.entries(routes));
|
|
8619
|
+
const resolve = (params) => {
|
|
8620
|
+
params.signal?.throwIfAborted();
|
|
8621
|
+
const provider = providers.get(params.model);
|
|
8622
|
+
if (!provider)
|
|
8623
|
+
throw new AIInputError("capacity_unavailable", `No provider route for model ${params.model}.`);
|
|
8624
|
+
return provider;
|
|
8625
|
+
};
|
|
8626
|
+
const capacity = (params) => {
|
|
8627
|
+
const result = resolve(params).inputCapacity;
|
|
8628
|
+
if (!result)
|
|
8629
|
+
throw new AIInputError("capacity_unavailable", `The provider for ${params.model} does not expose model capacity and token counting.`);
|
|
8630
|
+
return result;
|
|
8631
|
+
};
|
|
8632
|
+
return {
|
|
8633
|
+
inputCapacity: {
|
|
8634
|
+
getLimits: async (params) => capacity(params).getLimits(params),
|
|
8635
|
+
countTokens: async (params) => capacity(params).countTokens(params)
|
|
8636
|
+
},
|
|
8637
|
+
stream: (params) => resolve(params).stream(params)
|
|
8638
|
+
};
|
|
8639
|
+
};
|
|
8598
8640
|
export {
|
|
8599
8641
|
AIInputError,
|
|
8600
8642
|
BUILTIN_UI_CARDS,
|
|
@@ -8628,6 +8670,7 @@ export {
|
|
|
8628
8670
|
configureProviderResilience,
|
|
8629
8671
|
confirmCard,
|
|
8630
8672
|
createAIConnection,
|
|
8673
|
+
createAIProviderRouter,
|
|
8631
8674
|
createAIStoredToolResultRecovery,
|
|
8632
8675
|
createAIStream,
|
|
8633
8676
|
createAITextSource,
|
|
@@ -8704,5 +8747,5 @@ export {
|
|
|
8704
8747
|
xai
|
|
8705
8748
|
};
|
|
8706
8749
|
|
|
8707
|
-
//# debugId=
|
|
8750
|
+
//# debugId=0CA4631EC8525EB564756E2164756E21
|
|
8708
8751
|
//# sourceMappingURL=index.js.map
|