@assistant-ui/react-ai-sdk 1.3.10 → 1.3.12
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/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +51 -104
- package/dist/usage.js.map +1 -1
- package/package.json +7 -8
- package/src/ui/utils/convertMessage.test.ts +53 -0
- package/src/usage.ts +71 -111
package/dist/usage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,WAAW,4BAA4B;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAmGD,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,4BAA4B,GAAG,SAAS,GAChD,gBAAgB,GAAG,SAAS,CAa9B;AAED,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,SAAS,CAKlE"}
|
package/dist/usage.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { useAuiState } from "@assistant-ui/react";
|
|
2
|
+
const USAGE_KEYS = [
|
|
3
|
+
"inputTokens",
|
|
4
|
+
"outputTokens",
|
|
5
|
+
"reasoningTokens",
|
|
6
|
+
"cachedInputTokens",
|
|
7
|
+
"totalTokens",
|
|
8
|
+
];
|
|
2
9
|
function asRecord(value) {
|
|
3
10
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4
11
|
return undefined;
|
|
@@ -10,71 +17,37 @@ function asPositiveTokenCount(value) {
|
|
|
10
17
|
}
|
|
11
18
|
return value;
|
|
12
19
|
}
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
function computeTotalTokens(usage) {
|
|
21
|
+
if (usage.totalTokens !== undefined)
|
|
22
|
+
return usage.totalTokens;
|
|
23
|
+
if (usage.inputTokens !== undefined && usage.outputTokens !== undefined) {
|
|
24
|
+
return usage.inputTokens + usage.outputTokens;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
19
27
|
}
|
|
20
28
|
function normalizeUsage(value) {
|
|
21
|
-
const
|
|
22
|
-
if (!
|
|
29
|
+
const record = asRecord(value);
|
|
30
|
+
if (!record)
|
|
23
31
|
return undefined;
|
|
24
|
-
const inputTokens = asPositiveTokenCount(usage.inputTokens);
|
|
25
|
-
const outputTokens = asPositiveTokenCount(usage.outputTokens);
|
|
26
|
-
const reasoningTokens = asPositiveTokenCount(usage.reasoningTokens);
|
|
27
|
-
const cachedInputTokens = asPositiveTokenCount(usage.cachedInputTokens);
|
|
28
|
-
const totalTokens = asPositiveTokenCount(usage.totalTokens);
|
|
29
32
|
const result = {};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
result.cachedInputTokens = cachedInputTokens;
|
|
38
|
-
if (totalTokens !== undefined)
|
|
39
|
-
result.totalTokens = totalTokens;
|
|
40
|
-
if (!hasAnyUsageField(result)) {
|
|
41
|
-
return undefined;
|
|
33
|
+
let hasFields = false;
|
|
34
|
+
for (const key of USAGE_KEYS) {
|
|
35
|
+
const count = asPositiveTokenCount(record[key]);
|
|
36
|
+
if (count !== undefined) {
|
|
37
|
+
result[key] = count;
|
|
38
|
+
hasFields = true;
|
|
39
|
+
}
|
|
42
40
|
}
|
|
43
|
-
return result;
|
|
41
|
+
return hasFields ? result : undefined;
|
|
44
42
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
const hasBothInputAndOutput = parsed.inputTokens !== undefined && parsed.outputTokens !== undefined;
|
|
50
|
-
const totalTokens = parsed.totalTokens ??
|
|
51
|
-
(hasBothInputAndOutput
|
|
52
|
-
? (parsed.inputTokens ?? 0) + (parsed.outputTokens ?? 0)
|
|
53
|
-
: undefined);
|
|
54
|
-
const result = {};
|
|
55
|
-
if (totalTokens !== undefined)
|
|
56
|
-
result.totalTokens = totalTokens;
|
|
57
|
-
if (parsed.inputTokens !== undefined)
|
|
58
|
-
result.inputTokens = parsed.inputTokens;
|
|
59
|
-
if (parsed.outputTokens !== undefined)
|
|
60
|
-
result.outputTokens = parsed.outputTokens;
|
|
61
|
-
if (parsed.reasoningTokens !== undefined)
|
|
62
|
-
result.reasoningTokens = parsed.reasoningTokens;
|
|
63
|
-
if (parsed.cachedInputTokens !== undefined)
|
|
64
|
-
result.cachedInputTokens = parsed.cachedInputTokens;
|
|
65
|
-
return result;
|
|
43
|
+
function withComputedTotal(usage) {
|
|
44
|
+
const totalTokens = computeTotalTokens(usage);
|
|
45
|
+
return { ...usage, ...(totalTokens !== undefined && { totalTokens }) };
|
|
66
46
|
}
|
|
67
47
|
function usageFromSteps(value) {
|
|
68
48
|
const steps = Array.isArray(value) ? value : [];
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
let reasoningTokens = 0;
|
|
72
|
-
let cachedInputTokens = 0;
|
|
73
|
-
let totalTokens = 0;
|
|
74
|
-
let hasInput = false;
|
|
75
|
-
let hasOutput = false;
|
|
76
|
-
let hasReasoning = false;
|
|
77
|
-
let hasCachedInput = false;
|
|
49
|
+
const sums = {};
|
|
50
|
+
const present = {};
|
|
78
51
|
let stepsWithUsage = 0;
|
|
79
52
|
let stepsWithComputableTotal = 0;
|
|
80
53
|
for (const step of steps) {
|
|
@@ -82,57 +55,33 @@ function usageFromSteps(value) {
|
|
|
82
55
|
if (!usage)
|
|
83
56
|
continue;
|
|
84
57
|
stepsWithUsage++;
|
|
85
|
-
const
|
|
86
|
-
const stepTotal = usage.totalTokens ??
|
|
87
|
-
(stepHasBothInputAndOutput
|
|
88
|
-
? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0)
|
|
89
|
-
: undefined);
|
|
58
|
+
const stepTotal = computeTotalTokens(usage);
|
|
90
59
|
if (stepTotal !== undefined) {
|
|
91
|
-
totalTokens
|
|
60
|
+
sums.totalTokens = (sums.totalTokens ?? 0) + stepTotal;
|
|
92
61
|
stepsWithComputableTotal++;
|
|
93
62
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
if (usage.reasoningTokens !== undefined) {
|
|
103
|
-
reasoningTokens += usage.reasoningTokens;
|
|
104
|
-
hasReasoning = true;
|
|
105
|
-
}
|
|
106
|
-
if (usage.cachedInputTokens !== undefined) {
|
|
107
|
-
cachedInputTokens += usage.cachedInputTokens;
|
|
108
|
-
hasCachedInput = true;
|
|
63
|
+
for (const key of USAGE_KEYS) {
|
|
64
|
+
if (key === "totalTokens")
|
|
65
|
+
continue;
|
|
66
|
+
if (usage[key] !== undefined) {
|
|
67
|
+
sums[key] = (sums[key] ?? 0) + usage[key];
|
|
68
|
+
present[key] = true;
|
|
69
|
+
}
|
|
109
70
|
}
|
|
110
71
|
}
|
|
111
72
|
if (stepsWithUsage === 0)
|
|
112
73
|
return undefined;
|
|
113
|
-
const
|
|
114
|
-
if (hasInput)
|
|
115
|
-
parsed.inputTokens = inputTokens;
|
|
116
|
-
if (hasOutput)
|
|
117
|
-
parsed.outputTokens = outputTokens;
|
|
118
|
-
if (hasReasoning)
|
|
119
|
-
parsed.reasoningTokens = reasoningTokens;
|
|
120
|
-
if (hasCachedInput)
|
|
121
|
-
parsed.cachedInputTokens = cachedInputTokens;
|
|
74
|
+
const result = {};
|
|
122
75
|
if (stepsWithComputableTotal === stepsWithUsage) {
|
|
123
|
-
|
|
76
|
+
result.totalTokens = sums.totalTokens;
|
|
77
|
+
}
|
|
78
|
+
for (const key of USAGE_KEYS) {
|
|
79
|
+
if (key === "totalTokens")
|
|
80
|
+
continue;
|
|
81
|
+
if (present[key]) {
|
|
82
|
+
result[key] = sums[key];
|
|
83
|
+
}
|
|
124
84
|
}
|
|
125
|
-
const result = {};
|
|
126
|
-
if (parsed.totalTokens !== undefined)
|
|
127
|
-
result.totalTokens = parsed.totalTokens;
|
|
128
|
-
if (parsed.inputTokens !== undefined)
|
|
129
|
-
result.inputTokens = parsed.inputTokens;
|
|
130
|
-
if (parsed.outputTokens !== undefined)
|
|
131
|
-
result.outputTokens = parsed.outputTokens;
|
|
132
|
-
if (parsed.reasoningTokens !== undefined)
|
|
133
|
-
result.reasoningTokens = parsed.reasoningTokens;
|
|
134
|
-
if (parsed.cachedInputTokens !== undefined)
|
|
135
|
-
result.cachedInputTokens = parsed.cachedInputTokens;
|
|
136
85
|
return result;
|
|
137
86
|
}
|
|
138
87
|
export function getThreadMessageTokenUsage(message) {
|
|
@@ -142,13 +91,11 @@ export function getThreadMessageTokenUsage(message) {
|
|
|
142
91
|
if (!metadata)
|
|
143
92
|
return undefined;
|
|
144
93
|
const topLevelUsage = normalizeUsage(metadata.usage);
|
|
145
|
-
if (topLevelUsage)
|
|
146
|
-
return
|
|
147
|
-
}
|
|
94
|
+
if (topLevelUsage)
|
|
95
|
+
return withComputedTotal(topLevelUsage);
|
|
148
96
|
const legacyUsage = normalizeUsage(asRecord(metadata.custom)?.usage);
|
|
149
|
-
if (legacyUsage)
|
|
150
|
-
return
|
|
151
|
-
}
|
|
97
|
+
if (legacyUsage)
|
|
98
|
+
return withComputedTotal(legacyUsage);
|
|
152
99
|
return usageFromSteps(metadata.steps);
|
|
153
100
|
}
|
|
154
101
|
export function useThreadTokenUsage() {
|
package/dist/usage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-ai-sdk",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.12",
|
|
4
4
|
"description": "Vercel AI SDK adapter for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-sdk",
|
|
@@ -31,12 +31,11 @@
|
|
|
31
31
|
],
|
|
32
32
|
"sideEffects": false,
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@ai-sdk/react": "^3.0.
|
|
35
|
-
"ai": "^6.0.
|
|
36
|
-
"zod": "^4.3.6"
|
|
34
|
+
"@ai-sdk/react": "^3.0.118",
|
|
35
|
+
"ai": "^6.0.116"
|
|
37
36
|
},
|
|
38
37
|
"peerDependencies": {
|
|
39
|
-
"@assistant-ui/react": "^0.12.
|
|
38
|
+
"@assistant-ui/react": "^0.12.15",
|
|
40
39
|
"@types/react": "*",
|
|
41
40
|
"assistant-cloud": "*",
|
|
42
41
|
"react": "^18 || ^19"
|
|
@@ -57,9 +56,9 @@
|
|
|
57
56
|
"jsdom": "^28.1.0",
|
|
58
57
|
"react": "^19.2.4",
|
|
59
58
|
"vitest": "^4.0.18",
|
|
60
|
-
"@assistant-ui/react": "0.12.
|
|
61
|
-
"
|
|
62
|
-
"assistant-
|
|
59
|
+
"@assistant-ui/react": "0.12.15",
|
|
60
|
+
"assistant-stream": "0.3.4",
|
|
61
|
+
"@assistant-ui/x-buildutils": "0.0.1"
|
|
63
62
|
},
|
|
64
63
|
"publishConfig": {
|
|
65
64
|
"access": "public",
|
|
@@ -194,4 +194,57 @@ describe("AISDKMessageConverter", () => {
|
|
|
194
194
|
'{"type":"high_stock_model","limit":5,"filters":{"region":"us","sector":"tech"}}',
|
|
195
195
|
);
|
|
196
196
|
});
|
|
197
|
+
|
|
198
|
+
it("merges duplicate toolCallId across assistant snapshots", () => {
|
|
199
|
+
const metadata = {
|
|
200
|
+
toolArgsKeyOrderCache: new Map<string, Map<string, string[]>>(),
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const converted = AISDKMessageConverter.toThreadMessages(
|
|
204
|
+
[
|
|
205
|
+
{
|
|
206
|
+
id: "a1",
|
|
207
|
+
role: "assistant",
|
|
208
|
+
parts: [
|
|
209
|
+
{
|
|
210
|
+
type: "tool-stocks",
|
|
211
|
+
toolCallId: "tc-order-1",
|
|
212
|
+
state: "input-streaming",
|
|
213
|
+
input: {
|
|
214
|
+
type: "high_stock_model",
|
|
215
|
+
limit: 5,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
} as any,
|
|
220
|
+
{
|
|
221
|
+
id: "a2",
|
|
222
|
+
role: "assistant",
|
|
223
|
+
parts: [
|
|
224
|
+
{
|
|
225
|
+
type: "tool-stocks",
|
|
226
|
+
toolCallId: "tc-order-1",
|
|
227
|
+
state: "input-available",
|
|
228
|
+
input: {
|
|
229
|
+
limit: 5,
|
|
230
|
+
type: "high_stock_model",
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
} as any,
|
|
235
|
+
],
|
|
236
|
+
false,
|
|
237
|
+
metadata,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const toolCalls = converted[0]?.content.filter(
|
|
241
|
+
(part): part is any => part.type === "tool-call",
|
|
242
|
+
);
|
|
243
|
+
expect(toolCalls).toHaveLength(1);
|
|
244
|
+
expect(toolCalls?.[0]?.toolCallId).toBe("tc-order-1");
|
|
245
|
+
expect(JSON.parse(toolCalls?.[0]?.argsText ?? "{}")).toEqual({
|
|
246
|
+
type: "high_stock_model",
|
|
247
|
+
limit: 5,
|
|
248
|
+
});
|
|
249
|
+
});
|
|
197
250
|
});
|
package/src/usage.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useAuiState } from "@assistant-ui/react";
|
|
2
|
+
|
|
2
3
|
export type ThreadTokenUsage = {
|
|
3
4
|
totalTokens?: number;
|
|
4
5
|
inputTokens?: number;
|
|
@@ -6,167 +7,126 @@ export type ThreadTokenUsage = {
|
|
|
6
7
|
reasoningTokens?: number;
|
|
7
8
|
cachedInputTokens?: number;
|
|
8
9
|
};
|
|
10
|
+
|
|
9
11
|
export interface TokenUsageExtractableMessage {
|
|
10
12
|
role?: string;
|
|
11
13
|
metadata?: unknown;
|
|
12
14
|
}
|
|
13
|
-
|
|
15
|
+
|
|
14
16
|
type UsageRecord = Record<string, unknown>;
|
|
17
|
+
|
|
18
|
+
const USAGE_KEYS = [
|
|
19
|
+
"inputTokens",
|
|
20
|
+
"outputTokens",
|
|
21
|
+
"reasoningTokens",
|
|
22
|
+
"cachedInputTokens",
|
|
23
|
+
"totalTokens",
|
|
24
|
+
] as const satisfies (keyof ThreadTokenUsage)[];
|
|
25
|
+
|
|
15
26
|
function asRecord(value: unknown): UsageRecord | undefined {
|
|
16
27
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
17
28
|
return undefined;
|
|
18
29
|
return value as UsageRecord;
|
|
19
30
|
}
|
|
31
|
+
|
|
20
32
|
function asPositiveTokenCount(value: unknown): number | undefined {
|
|
21
33
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
22
34
|
return undefined;
|
|
23
35
|
}
|
|
24
36
|
return value;
|
|
25
37
|
}
|
|
26
|
-
// Internal type that makes everything optional for the intermediate parsing steps
|
|
27
|
-
type ParsedUsage = {
|
|
28
|
-
inputTokens?: number;
|
|
29
|
-
outputTokens?: number;
|
|
30
|
-
reasoningTokens?: number;
|
|
31
|
-
cachedInputTokens?: number;
|
|
32
|
-
totalTokens?: number;
|
|
33
|
-
};
|
|
34
38
|
|
|
35
|
-
function
|
|
36
|
-
return
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
parsed.totalTokens !== undefined
|
|
42
|
-
);
|
|
39
|
+
function computeTotalTokens(usage: ThreadTokenUsage): number | undefined {
|
|
40
|
+
if (usage.totalTokens !== undefined) return usage.totalTokens;
|
|
41
|
+
if (usage.inputTokens !== undefined && usage.outputTokens !== undefined) {
|
|
42
|
+
return usage.inputTokens + usage.outputTokens;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
function normalizeUsage(value: unknown):
|
|
46
|
-
const
|
|
47
|
-
if (!
|
|
48
|
-
const inputTokens = asPositiveTokenCount(usage.inputTokens);
|
|
49
|
-
const outputTokens = asPositiveTokenCount(usage.outputTokens);
|
|
50
|
-
const reasoningTokens = asPositiveTokenCount(usage.reasoningTokens);
|
|
51
|
-
const cachedInputTokens = asPositiveTokenCount(usage.cachedInputTokens);
|
|
52
|
-
const totalTokens = asPositiveTokenCount(usage.totalTokens);
|
|
53
|
-
const result: ParsedUsage = {};
|
|
54
|
-
if (inputTokens !== undefined) result.inputTokens = inputTokens;
|
|
55
|
-
if (outputTokens !== undefined) result.outputTokens = outputTokens;
|
|
56
|
-
if (reasoningTokens !== undefined) result.reasoningTokens = reasoningTokens;
|
|
57
|
-
if (cachedInputTokens !== undefined)
|
|
58
|
-
result.cachedInputTokens = cachedInputTokens;
|
|
59
|
-
if (totalTokens !== undefined) result.totalTokens = totalTokens;
|
|
60
|
-
|
|
61
|
-
if (!hasAnyUsageField(result)) {
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
47
|
+
function normalizeUsage(value: unknown): ThreadTokenUsage | undefined {
|
|
48
|
+
const record = asRecord(value);
|
|
49
|
+
if (!record) return undefined;
|
|
64
50
|
|
|
65
|
-
return result;
|
|
66
|
-
}
|
|
67
|
-
function buildUsageResult(parsed: ParsedUsage): ThreadTokenUsage | undefined {
|
|
68
|
-
if (!hasAnyUsageField(parsed)) {
|
|
69
|
-
return undefined;
|
|
70
|
-
}
|
|
71
|
-
const hasBothInputAndOutput =
|
|
72
|
-
parsed.inputTokens !== undefined && parsed.outputTokens !== undefined;
|
|
73
|
-
const totalTokens =
|
|
74
|
-
parsed.totalTokens ??
|
|
75
|
-
(hasBothInputAndOutput
|
|
76
|
-
? (parsed.inputTokens ?? 0) + (parsed.outputTokens ?? 0)
|
|
77
|
-
: undefined);
|
|
78
51
|
const result: ThreadTokenUsage = {};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return result;
|
|
52
|
+
let hasFields = false;
|
|
53
|
+
for (const key of USAGE_KEYS) {
|
|
54
|
+
const count = asPositiveTokenCount(record[key]);
|
|
55
|
+
if (count !== undefined) {
|
|
56
|
+
result[key] = count;
|
|
57
|
+
hasFields = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return hasFields ? result : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function withComputedTotal(
|
|
64
|
+
usage: ThreadTokenUsage,
|
|
65
|
+
): ThreadTokenUsage | undefined {
|
|
66
|
+
const totalTokens = computeTotalTokens(usage);
|
|
67
|
+
return { ...usage, ...(totalTokens !== undefined && { totalTokens }) };
|
|
88
68
|
}
|
|
69
|
+
|
|
89
70
|
function usageFromSteps(value: unknown): ThreadTokenUsage | undefined {
|
|
90
71
|
const steps = Array.isArray(value) ? value : [];
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
let cachedInputTokens = 0;
|
|
95
|
-
let totalTokens = 0;
|
|
96
|
-
let hasInput = false;
|
|
97
|
-
let hasOutput = false;
|
|
98
|
-
let hasReasoning = false;
|
|
99
|
-
let hasCachedInput = false;
|
|
72
|
+
|
|
73
|
+
const sums: Record<string, number> = {};
|
|
74
|
+
const present: Record<string, boolean> = {};
|
|
100
75
|
let stepsWithUsage = 0;
|
|
101
76
|
let stepsWithComputableTotal = 0;
|
|
77
|
+
|
|
102
78
|
for (const step of steps) {
|
|
103
79
|
const usage = normalizeUsage(asRecord(step)?.usage);
|
|
104
80
|
if (!usage) continue;
|
|
105
81
|
stepsWithUsage++;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const stepTotal =
|
|
109
|
-
usage.totalTokens ??
|
|
110
|
-
(stepHasBothInputAndOutput
|
|
111
|
-
? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0)
|
|
112
|
-
: undefined);
|
|
82
|
+
|
|
83
|
+
const stepTotal = computeTotalTokens(usage);
|
|
113
84
|
if (stepTotal !== undefined) {
|
|
114
|
-
totalTokens
|
|
85
|
+
sums.totalTokens = (sums.totalTokens ?? 0) + stepTotal;
|
|
115
86
|
stepsWithComputableTotal++;
|
|
116
87
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
if (usage.reasoningTokens !== undefined) {
|
|
126
|
-
reasoningTokens += usage.reasoningTokens;
|
|
127
|
-
hasReasoning = true;
|
|
128
|
-
}
|
|
129
|
-
if (usage.cachedInputTokens !== undefined) {
|
|
130
|
-
cachedInputTokens += usage.cachedInputTokens;
|
|
131
|
-
hasCachedInput = true;
|
|
88
|
+
|
|
89
|
+
for (const key of USAGE_KEYS) {
|
|
90
|
+
if (key === "totalTokens") continue;
|
|
91
|
+
if (usage[key] !== undefined) {
|
|
92
|
+
sums[key] = (sums[key] ?? 0) + usage[key];
|
|
93
|
+
present[key] = true;
|
|
94
|
+
}
|
|
132
95
|
}
|
|
133
96
|
}
|
|
97
|
+
|
|
134
98
|
if (stepsWithUsage === 0) return undefined;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (hasOutput) parsed.outputTokens = outputTokens;
|
|
138
|
-
if (hasReasoning) parsed.reasoningTokens = reasoningTokens;
|
|
139
|
-
if (hasCachedInput) parsed.cachedInputTokens = cachedInputTokens;
|
|
99
|
+
|
|
100
|
+
const result: ThreadTokenUsage = {};
|
|
140
101
|
if (stepsWithComputableTotal === stepsWithUsage) {
|
|
141
|
-
|
|
102
|
+
result.totalTokens = sums.totalTokens!;
|
|
103
|
+
}
|
|
104
|
+
for (const key of USAGE_KEYS) {
|
|
105
|
+
if (key === "totalTokens") continue;
|
|
106
|
+
if (present[key]) {
|
|
107
|
+
result[key] = sums[key]!;
|
|
108
|
+
}
|
|
142
109
|
}
|
|
143
|
-
const result: ThreadTokenUsage = {};
|
|
144
|
-
if (parsed.totalTokens !== undefined) result.totalTokens = parsed.totalTokens;
|
|
145
|
-
if (parsed.inputTokens !== undefined) result.inputTokens = parsed.inputTokens;
|
|
146
|
-
if (parsed.outputTokens !== undefined)
|
|
147
|
-
result.outputTokens = parsed.outputTokens;
|
|
148
|
-
if (parsed.reasoningTokens !== undefined)
|
|
149
|
-
result.reasoningTokens = parsed.reasoningTokens;
|
|
150
|
-
if (parsed.cachedInputTokens !== undefined)
|
|
151
|
-
result.cachedInputTokens = parsed.cachedInputTokens;
|
|
152
110
|
return result;
|
|
153
111
|
}
|
|
112
|
+
|
|
154
113
|
export function getThreadMessageTokenUsage(
|
|
155
114
|
message: TokenUsageExtractableMessage | undefined,
|
|
156
115
|
): ThreadTokenUsage | undefined {
|
|
157
116
|
if (!message || message.role !== "assistant") return undefined;
|
|
117
|
+
|
|
158
118
|
const metadata = asRecord(message.metadata);
|
|
159
119
|
if (!metadata) return undefined;
|
|
120
|
+
|
|
160
121
|
const topLevelUsage = normalizeUsage(metadata.usage);
|
|
161
|
-
if (topLevelUsage)
|
|
162
|
-
|
|
163
|
-
}
|
|
122
|
+
if (topLevelUsage) return withComputedTotal(topLevelUsage);
|
|
123
|
+
|
|
164
124
|
const legacyUsage = normalizeUsage(asRecord(metadata.custom)?.usage);
|
|
165
|
-
if (legacyUsage)
|
|
166
|
-
|
|
167
|
-
}
|
|
125
|
+
if (legacyUsage) return withComputedTotal(legacyUsage);
|
|
126
|
+
|
|
168
127
|
return usageFromSteps(metadata.steps);
|
|
169
128
|
}
|
|
129
|
+
|
|
170
130
|
export function useThreadTokenUsage(): ThreadTokenUsage | undefined {
|
|
171
131
|
const lastAssistant = useAuiState((s) =>
|
|
172
132
|
s.thread.messages.findLast((m) => m.role === "assistant"),
|