@opengeni/runtime 0.11.0 → 0.13.3
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/{chunk-YUAFQEFY.js → chunk-JGUQSAG5.js} +2605 -279
- package/dist/chunk-JGUQSAG5.js.map +1 -0
- package/dist/chunk-SSB7VOLE.js +301 -0
- package/dist/chunk-SSB7VOLE.js.map +1 -0
- package/dist/{index-D6yi-JcU.d.ts → index-DU-KXVlx.d.ts} +364 -31
- package/dist/index.d.ts +103 -10
- package/dist/index.js +756 -761
- package/dist/index.js.map +1 -1
- package/dist/mcp-network.d.ts +80 -0
- package/dist/mcp-network.js +66 -0
- package/dist/mcp-network.js.map +1 -0
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/sandbox/index.js +39 -1
- package/package.json +9 -4
- package/src/codex-tool-search.ts +20 -1
- package/src/index.ts +502 -85
- package/src/mcp-network.ts +343 -0
- package/src/sandbox/channel-a.ts +92 -71
- package/src/sandbox/display-stack.ts +860 -23
- package/src/sandbox/exec-banner.ts +82 -0
- package/src/sandbox/index.ts +367 -145
- package/src/sandbox/provider-errors.ts +175 -0
- package/src/sandbox/providers/modal.ts +29 -2
- package/src/sandbox/routing/backend-resolver.ts +15 -0
- package/src/sandbox/routing/routing-session.ts +730 -36
- package/src/sandbox/select.ts +8 -0
- package/src/sandbox/turn-tool-cancellation.ts +366 -92
- package/src/sandbox/workspace-archive.ts +289 -0
- package/src/sandbox-computer.ts +3 -1
- package/src/usage-telemetry.ts +495 -51
- package/dist/chunk-YUAFQEFY.js.map +0 -1
package/src/usage-telemetry.ts
CHANGED
|
@@ -1,73 +1,517 @@
|
|
|
1
1
|
type UsageDetails = Record<string, unknown> | Array<Record<string, unknown>>;
|
|
2
2
|
|
|
3
|
+
type RequestUsageEntry = {
|
|
4
|
+
inputTokens?: unknown;
|
|
5
|
+
input_tokens?: unknown;
|
|
6
|
+
outputTokens?: unknown;
|
|
7
|
+
output_tokens?: unknown;
|
|
8
|
+
totalTokens?: unknown;
|
|
9
|
+
total_tokens?: unknown;
|
|
10
|
+
inputTokensDetails?: UsageDetails | undefined;
|
|
11
|
+
input_tokens_details?: UsageDetails | undefined;
|
|
12
|
+
outputTokensDetails?: UsageDetails | undefined;
|
|
13
|
+
output_tokens_details?: UsageDetails | undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type ModelCallUsageInput = {
|
|
17
|
+
inputTokens?: unknown;
|
|
18
|
+
outputTokens?: unknown;
|
|
19
|
+
totalTokens?: unknown;
|
|
20
|
+
inputTokensDetails?: UsageDetails | undefined;
|
|
21
|
+
outputTokensDetails?: UsageDetails | undefined;
|
|
22
|
+
requestUsageEntries?: RequestUsageEntry[] | undefined;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A single provider usage frame above one billion tokens is outside every
|
|
27
|
+
* supported OpenGeni model contract. Keeping the ceiling explicit also prevents
|
|
28
|
+
* malformed-but-finite provider values from corrupting durable accounting or a
|
|
29
|
+
* process-lifetime Prometheus counter.
|
|
30
|
+
*/
|
|
31
|
+
export const MAX_MODEL_USAGE_TOKEN_COUNT = 1_000_000_000;
|
|
32
|
+
|
|
3
33
|
export type ModelCallUsageTelemetry = {
|
|
4
34
|
inputTokens: number | null;
|
|
5
35
|
outputTokens: number | null;
|
|
6
36
|
cachedTokens: number | null;
|
|
37
|
+
cacheWriteTokens: number | null;
|
|
7
38
|
reasoningTokens: number | null;
|
|
8
39
|
};
|
|
9
40
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
41
|
+
export type ModelCallUsageNormalization = {
|
|
42
|
+
telemetry: ModelCallUsageTelemetry;
|
|
43
|
+
totalTokens: number | null;
|
|
44
|
+
/** Bounded field paths only; raw provider values are never retained. */
|
|
45
|
+
rejectedFields: string[];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export function modelUsageTokenCountOrNull(value: unknown): number | null {
|
|
49
|
+
return typeof value === "number" &&
|
|
50
|
+
Number.isSafeInteger(value) &&
|
|
51
|
+
value >= 0 &&
|
|
52
|
+
value <= MAX_MODEL_USAGE_TOKEN_COUNT
|
|
53
|
+
? value
|
|
54
|
+
: null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function normalizeModelCallUsage(
|
|
58
|
+
usage: ModelCallUsageInput | null | undefined,
|
|
59
|
+
): ModelCallUsageNormalization {
|
|
60
|
+
const rejectedFields = new Set<string>();
|
|
61
|
+
if (!usage) {
|
|
62
|
+
return {
|
|
63
|
+
telemetry: {
|
|
64
|
+
inputTokens: null,
|
|
65
|
+
outputTokens: null,
|
|
66
|
+
cachedTokens: null,
|
|
67
|
+
cacheWriteTokens: null,
|
|
68
|
+
reasoningTokens: null,
|
|
69
|
+
},
|
|
70
|
+
totalTokens: null,
|
|
71
|
+
rejectedFields: [],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const requestEntries = requestUsageEntries(usage.requestUsageEntries, rejectedFields);
|
|
76
|
+
const topLevelInputTokens = reportedTokenCount(usage.inputTokens, "inputTokens", rejectedFields);
|
|
77
|
+
const topLevelOutputTokens = reportedTokenCount(
|
|
78
|
+
usage.outputTokens,
|
|
79
|
+
"outputTokens",
|
|
80
|
+
rejectedFields,
|
|
81
|
+
);
|
|
82
|
+
const topLevelTotalTokens = reportedTokenCount(usage.totalTokens, "totalTokens", rejectedFields);
|
|
83
|
+
const requestInputTokens = completeRequestTokenAggregate(
|
|
84
|
+
requestEntries,
|
|
85
|
+
["inputTokens", "input_tokens"],
|
|
86
|
+
"inputTokens.requestUsageEntries",
|
|
87
|
+
rejectedFields,
|
|
88
|
+
);
|
|
89
|
+
const requestOutputTokens = completeRequestTokenAggregate(
|
|
90
|
+
requestEntries,
|
|
91
|
+
["outputTokens", "output_tokens"],
|
|
92
|
+
"outputTokens.requestUsageEntries",
|
|
93
|
+
rejectedFields,
|
|
94
|
+
);
|
|
95
|
+
let requestTotalTokens = completeRequestTokenAggregate(
|
|
96
|
+
requestEntries,
|
|
97
|
+
["totalTokens", "total_tokens"],
|
|
98
|
+
"totalTokens.requestUsageEntries",
|
|
99
|
+
rejectedFields,
|
|
100
|
+
);
|
|
101
|
+
if (rejectInconsistentRequestTotals(requestEntries, rejectedFields)) {
|
|
102
|
+
requestTotalTokens = { ...requestTotalTokens, status: "invalid", value: null };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Complete per-request aggregates are the installed SDK's request-level
|
|
106
|
+
// truth. A conflicting top-level field is diagnostic only; it must never
|
|
107
|
+
// override the request sum with a smaller billable/context value. Invalid
|
|
108
|
+
// complete request aggregates fail closed rather than falling back to a
|
|
109
|
+
// potentially undercounting top-level number.
|
|
110
|
+
const inputTokens = canonicalTokenDimension({
|
|
111
|
+
topLevel: topLevelInputTokens,
|
|
112
|
+
topLevelReported: isReported(usage.inputTokens),
|
|
113
|
+
topLevelPath: "inputTokens",
|
|
114
|
+
requestAggregate: requestInputTokens,
|
|
115
|
+
rejectedFields,
|
|
116
|
+
});
|
|
117
|
+
const outputTokens = canonicalTokenDimension({
|
|
118
|
+
topLevel: topLevelOutputTokens,
|
|
119
|
+
topLevelReported: isReported(usage.outputTokens),
|
|
120
|
+
topLevelPath: "outputTokens",
|
|
121
|
+
requestAggregate: requestOutputTokens,
|
|
122
|
+
rejectedFields,
|
|
123
|
+
});
|
|
124
|
+
const totalTokens = canonicalTotalTokens({
|
|
125
|
+
inputTokens,
|
|
126
|
+
outputTokens,
|
|
127
|
+
topLevel: topLevelTotalTokens,
|
|
128
|
+
topLevelReported: isReported(usage.totalTokens),
|
|
129
|
+
requestAggregate: requestTotalTokens,
|
|
130
|
+
rejectedFields,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const inputDetailSource = preferredDetailSource(
|
|
134
|
+
usage.inputTokensDetails,
|
|
135
|
+
"inputTokensDetails",
|
|
136
|
+
requestEntries,
|
|
137
|
+
["inputTokensDetails", "input_tokens_details"],
|
|
138
|
+
);
|
|
139
|
+
const outputDetailSource = preferredDetailSource(
|
|
140
|
+
usage.outputTokensDetails,
|
|
141
|
+
"outputTokensDetails",
|
|
142
|
+
requestEntries,
|
|
143
|
+
["outputTokensDetails", "output_tokens_details"],
|
|
144
|
+
);
|
|
145
|
+
|
|
21
146
|
return {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
:
|
|
147
|
+
telemetry: {
|
|
148
|
+
inputTokens,
|
|
149
|
+
outputTokens,
|
|
150
|
+
cachedTokens: aggregateDetailTokenCount(
|
|
151
|
+
inputDetailSource,
|
|
152
|
+
["cached_tokens", "cachedInputTokens", "cached_input_tokens"],
|
|
153
|
+
"cachedTokens",
|
|
154
|
+
rejectedFields,
|
|
155
|
+
),
|
|
156
|
+
cacheWriteTokens: aggregateDetailTokenCount(
|
|
157
|
+
inputDetailSource,
|
|
158
|
+
["cache_write_tokens", "cacheWriteTokens"],
|
|
159
|
+
"cacheWriteTokens",
|
|
160
|
+
rejectedFields,
|
|
161
|
+
),
|
|
162
|
+
reasoningTokens: aggregateDetailTokenCount(
|
|
163
|
+
outputDetailSource,
|
|
164
|
+
["reasoning_tokens", "reasoningTokens", "reasoning_output_tokens"],
|
|
165
|
+
"reasoningTokens",
|
|
166
|
+
rejectedFields,
|
|
167
|
+
),
|
|
168
|
+
},
|
|
169
|
+
totalTokens,
|
|
170
|
+
rejectedFields: [...rejectedFields].slice(0, 32),
|
|
38
171
|
};
|
|
39
172
|
}
|
|
40
173
|
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)
|
|
45
|
-
|
|
174
|
+
export function modelCallUsageTelemetry(
|
|
175
|
+
usage: ModelCallUsageInput | null | undefined,
|
|
176
|
+
): ModelCallUsageTelemetry {
|
|
177
|
+
return normalizeModelCallUsage(usage).telemetry;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function requestUsageEntries(
|
|
181
|
+
value: RequestUsageEntry[] | undefined,
|
|
182
|
+
rejectedFields: Set<string>,
|
|
183
|
+
): RequestUsageEntry[] {
|
|
184
|
+
if (value === undefined) {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
if (!Array.isArray(value)) {
|
|
188
|
+
rejectField(rejectedFields, "requestUsageEntries");
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
const entries: RequestUsageEntry[] = [];
|
|
192
|
+
for (const [index, entry] of value.entries()) {
|
|
193
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
194
|
+
rejectField(rejectedFields, `requestUsageEntries[${index}]`);
|
|
195
|
+
// Preserve the request slot as unknown. Dropping it would let fallback
|
|
196
|
+
// aggregation silently undercount only the well-formed requests.
|
|
197
|
+
entries.push({});
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
entries.push(entry);
|
|
201
|
+
}
|
|
202
|
+
return entries;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
type RequestTokenAggregate = {
|
|
206
|
+
status: "absent" | "incomplete" | "invalid" | "complete";
|
|
207
|
+
value: number | null;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
function completeRequestTokenAggregate(
|
|
211
|
+
requestEntries: RequestUsageEntry[],
|
|
212
|
+
requestKeys: string[],
|
|
213
|
+
aggregatePath: string,
|
|
214
|
+
rejectedFields: Set<string>,
|
|
215
|
+
): RequestTokenAggregate {
|
|
216
|
+
if (requestEntries.length === 0) {
|
|
217
|
+
return { status: "absent", value: null };
|
|
218
|
+
}
|
|
219
|
+
const values: Array<number | null> = [];
|
|
220
|
+
let incomplete = false;
|
|
221
|
+
let invalid = false;
|
|
222
|
+
for (const [index, entry] of requestEntries.entries()) {
|
|
223
|
+
const selected = firstPresentEntry(entry as Record<string, unknown>, requestKeys);
|
|
224
|
+
if (!selected || selected.value === null) {
|
|
225
|
+
incomplete = true;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const parsed = reportedTokenCount(
|
|
229
|
+
selected.value,
|
|
230
|
+
`requestUsageEntries[${index}].${selected.key}`,
|
|
231
|
+
rejectedFields,
|
|
232
|
+
);
|
|
233
|
+
if (parsed === null) invalid = true;
|
|
234
|
+
values.push(parsed);
|
|
235
|
+
}
|
|
236
|
+
if (invalid) {
|
|
237
|
+
return { status: "invalid", value: null };
|
|
238
|
+
}
|
|
239
|
+
if (incomplete) {
|
|
240
|
+
return { status: "incomplete", value: null };
|
|
241
|
+
}
|
|
242
|
+
const value = boundedTokenSum(values, aggregatePath, rejectedFields);
|
|
243
|
+
return value === null ? { status: "invalid", value: null } : { status: "complete", value };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function canonicalTokenDimension(input: {
|
|
247
|
+
topLevel: number | null;
|
|
248
|
+
topLevelReported: boolean;
|
|
249
|
+
topLevelPath: string;
|
|
250
|
+
requestAggregate: RequestTokenAggregate;
|
|
251
|
+
rejectedFields: Set<string>;
|
|
252
|
+
}): number | null {
|
|
253
|
+
if (input.requestAggregate.status === "complete") {
|
|
254
|
+
if (
|
|
255
|
+
input.topLevelReported &&
|
|
256
|
+
input.topLevel !== null &&
|
|
257
|
+
input.topLevel !== input.requestAggregate.value
|
|
258
|
+
) {
|
|
259
|
+
rejectField(input.rejectedFields, input.topLevelPath);
|
|
260
|
+
}
|
|
261
|
+
return input.requestAggregate.value;
|
|
262
|
+
}
|
|
263
|
+
if (input.requestAggregate.status === "invalid") {
|
|
264
|
+
if (input.topLevelReported) {
|
|
265
|
+
rejectField(input.rejectedFields, input.topLevelPath);
|
|
266
|
+
}
|
|
46
267
|
return null;
|
|
47
268
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
269
|
+
return input.topLevel;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function canonicalTotalTokens(input: {
|
|
273
|
+
inputTokens: number | null;
|
|
274
|
+
outputTokens: number | null;
|
|
275
|
+
topLevel: number | null;
|
|
276
|
+
topLevelReported: boolean;
|
|
277
|
+
requestAggregate: RequestTokenAggregate;
|
|
278
|
+
rejectedFields: Set<string>;
|
|
279
|
+
}): number | null {
|
|
280
|
+
if (input.inputTokens !== null && input.outputTokens !== null) {
|
|
281
|
+
const derived = boundedTokenSum(
|
|
282
|
+
[input.inputTokens, input.outputTokens],
|
|
283
|
+
"totalTokens.aggregate",
|
|
284
|
+
input.rejectedFields,
|
|
285
|
+
);
|
|
286
|
+
if (derived === null) {
|
|
287
|
+
// Both canonical components are known and exceed the supported aggregate
|
|
288
|
+
// ceiling. Any bounded reported total necessarily undercounts them.
|
|
289
|
+
if (input.topLevelReported) rejectField(input.rejectedFields, "totalTokens");
|
|
290
|
+
if (input.requestAggregate.status === "complete") {
|
|
291
|
+
rejectField(input.rejectedFields, "totalTokens.requestUsageEntries");
|
|
62
292
|
}
|
|
63
|
-
|
|
64
|
-
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
if (input.topLevelReported && input.topLevel !== null && input.topLevel !== derived) {
|
|
296
|
+
rejectField(input.rejectedFields, "totalTokens");
|
|
297
|
+
}
|
|
298
|
+
if (input.requestAggregate.status === "complete" && input.requestAggregate.value !== derived) {
|
|
299
|
+
rejectField(input.rejectedFields, "totalTokens.requestUsageEntries");
|
|
300
|
+
}
|
|
301
|
+
return derived;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (input.requestAggregate.status === "complete") {
|
|
305
|
+
const requestTotal = input.requestAggregate.value;
|
|
306
|
+
if (
|
|
307
|
+
requestTotal === null ||
|
|
308
|
+
!totalCoversKnownComponents(requestTotal, input.inputTokens, input.outputTokens)
|
|
309
|
+
) {
|
|
310
|
+
rejectField(input.rejectedFields, "totalTokens.requestUsageEntries");
|
|
311
|
+
if (input.topLevelReported) rejectField(input.rejectedFields, "totalTokens");
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
if (input.topLevelReported && input.topLevel !== null && input.topLevel !== requestTotal) {
|
|
315
|
+
rejectField(input.rejectedFields, "totalTokens");
|
|
316
|
+
}
|
|
317
|
+
return requestTotal;
|
|
318
|
+
}
|
|
319
|
+
if (input.requestAggregate.status === "invalid") {
|
|
320
|
+
if (input.topLevelReported) rejectField(input.rejectedFields, "totalTokens");
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
if (
|
|
324
|
+
input.topLevel !== null &&
|
|
325
|
+
!totalCoversKnownComponents(input.topLevel, input.inputTokens, input.outputTokens)
|
|
326
|
+
) {
|
|
327
|
+
rejectField(input.rejectedFields, "totalTokens");
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
return input.topLevel;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function totalCoversKnownComponents(
|
|
334
|
+
total: number,
|
|
335
|
+
inputTokens: number | null,
|
|
336
|
+
outputTokens: number | null,
|
|
337
|
+
): boolean {
|
|
338
|
+
return (
|
|
339
|
+
(inputTokens === null || total >= inputTokens) &&
|
|
340
|
+
(outputTokens === null || total >= outputTokens)
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function rejectInconsistentRequestTotals(
|
|
345
|
+
requestEntries: RequestUsageEntry[],
|
|
346
|
+
rejectedFields: Set<string>,
|
|
347
|
+
): boolean {
|
|
348
|
+
let inconsistent = false;
|
|
349
|
+
for (const [index, entry] of requestEntries.entries()) {
|
|
350
|
+
const record = entry as Record<string, unknown>;
|
|
351
|
+
const selectedInput = firstPresentEntry(record, ["inputTokens", "input_tokens"]);
|
|
352
|
+
const selectedOutput = firstPresentEntry(record, ["outputTokens", "output_tokens"]);
|
|
353
|
+
const selectedTotal = firstPresentEntry(record, ["totalTokens", "total_tokens"]);
|
|
354
|
+
if (!selectedInput || !selectedOutput || !selectedTotal) continue;
|
|
355
|
+
const inputTokens = modelUsageTokenCountOrNull(selectedInput.value);
|
|
356
|
+
const outputTokens = modelUsageTokenCountOrNull(selectedOutput.value);
|
|
357
|
+
const totalTokens = modelUsageTokenCountOrNull(selectedTotal.value);
|
|
358
|
+
if (inputTokens === null || outputTokens === null || totalTokens === null) continue;
|
|
359
|
+
const derived =
|
|
360
|
+
inputTokens <= MAX_MODEL_USAGE_TOKEN_COUNT - outputTokens ? inputTokens + outputTokens : null;
|
|
361
|
+
if (derived === null || totalTokens !== derived) {
|
|
362
|
+
rejectField(rejectedFields, `requestUsageEntries[${index}].${selectedTotal.key}`);
|
|
363
|
+
inconsistent = true;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return inconsistent;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function isReported(value: unknown): boolean {
|
|
370
|
+
return value !== undefined && value !== null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function preferredDetailSource(
|
|
374
|
+
aggregateDetails: UsageDetails | undefined,
|
|
375
|
+
aggregatePath: string,
|
|
376
|
+
requestEntries: RequestUsageEntry[],
|
|
377
|
+
requestKeys: string[],
|
|
378
|
+
): Array<{ details: unknown; path: string; missing?: boolean }> {
|
|
379
|
+
const requestDetails: Array<{ details: unknown; path: string; missing?: boolean }> = [];
|
|
380
|
+
let sawRequestDetails = false;
|
|
381
|
+
for (const [index, entry] of requestEntries.entries()) {
|
|
382
|
+
const selected = firstPresentEntry(entry as Record<string, unknown>, requestKeys);
|
|
383
|
+
if (!selected) {
|
|
384
|
+
requestDetails.push({
|
|
385
|
+
details: undefined,
|
|
386
|
+
path: `requestUsageEntries[${index}].${requestKeys[0]}`,
|
|
387
|
+
missing: true,
|
|
388
|
+
});
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
sawRequestDetails = true;
|
|
392
|
+
requestDetails.push({
|
|
393
|
+
details: selected.value,
|
|
394
|
+
path: `requestUsageEntries[${index}].${selected.key}`,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
// The installed Agents SDK explicitly carries per-request entries. Prefer
|
|
398
|
+
// them when they include detail records; otherwise fall back to the aggregate
|
|
399
|
+
// detail arrays that Usage.add() appends in request order.
|
|
400
|
+
return sawRequestDetails
|
|
401
|
+
? requestDetails
|
|
402
|
+
: aggregateDetails === undefined
|
|
403
|
+
? []
|
|
404
|
+
: [{ details: aggregateDetails, path: aggregatePath }];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function aggregateDetailTokenCount(
|
|
408
|
+
sources: Array<{ details: unknown; path: string; missing?: boolean }>,
|
|
409
|
+
keys: string[],
|
|
410
|
+
field: string,
|
|
411
|
+
rejectedFields: Set<string>,
|
|
412
|
+
): number | null {
|
|
413
|
+
const values: Array<number | null> = [];
|
|
414
|
+
let sawReportedValue = false;
|
|
415
|
+
let sawMissingValue = false;
|
|
416
|
+
for (const source of sources) {
|
|
417
|
+
if (source.missing) {
|
|
418
|
+
sawMissingValue = true;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const details = detailRecords(source.details, source.path, rejectedFields);
|
|
422
|
+
if (!details.complete) {
|
|
423
|
+
sawMissingValue = true;
|
|
424
|
+
}
|
|
425
|
+
for (const detail of details.records) {
|
|
426
|
+
const selected = firstPresentEntry(detail.record, keys);
|
|
427
|
+
if (!selected) {
|
|
428
|
+
sawMissingValue = true;
|
|
429
|
+
continue;
|
|
65
430
|
}
|
|
431
|
+
sawReportedValue = true;
|
|
432
|
+
values.push(
|
|
433
|
+
reportedTokenCount(selected.value, `${detail.path}.${selected.key}`, rejectedFields),
|
|
434
|
+
);
|
|
66
435
|
}
|
|
67
436
|
}
|
|
68
|
-
return
|
|
437
|
+
return sawReportedValue && !sawMissingValue
|
|
438
|
+
? boundedTokenSum(values, `${field}.aggregate`, rejectedFields)
|
|
439
|
+
: null;
|
|
69
440
|
}
|
|
70
441
|
|
|
71
|
-
function
|
|
72
|
-
|
|
442
|
+
function detailRecords(
|
|
443
|
+
details: unknown,
|
|
444
|
+
path: string,
|
|
445
|
+
rejectedFields: Set<string>,
|
|
446
|
+
): {
|
|
447
|
+
records: Array<{ record: Record<string, unknown>; path: string }>;
|
|
448
|
+
complete: boolean;
|
|
449
|
+
} {
|
|
450
|
+
const entries = Array.isArray(details) ? details : [details];
|
|
451
|
+
const records: Array<{ record: Record<string, unknown>; path: string }> = [];
|
|
452
|
+
let complete = true;
|
|
453
|
+
for (const [index, entry] of entries.entries()) {
|
|
454
|
+
const entryPath = Array.isArray(details) ? `${path}[${index}]` : path;
|
|
455
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
456
|
+
rejectField(rejectedFields, entryPath);
|
|
457
|
+
complete = false;
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
records.push({ record: entry as Record<string, unknown>, path: entryPath });
|
|
461
|
+
}
|
|
462
|
+
return { records, complete };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function firstPresentEntry(
|
|
466
|
+
record: Record<string, unknown>,
|
|
467
|
+
keys: string[],
|
|
468
|
+
): { key: string; value: unknown } | null {
|
|
469
|
+
for (const key of keys) {
|
|
470
|
+
if (Object.prototype.hasOwnProperty.call(record, key) && record[key] !== undefined) {
|
|
471
|
+
// One request can expose the same value under provider and SDK aliases.
|
|
472
|
+
// Pick the first canonical alias instead of adding aliases together.
|
|
473
|
+
return { key, value: record[key] };
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function reportedTokenCount(
|
|
480
|
+
value: unknown,
|
|
481
|
+
field: string,
|
|
482
|
+
rejectedFields: Set<string>,
|
|
483
|
+
): number | null {
|
|
484
|
+
if (value === undefined || value === null) {
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
const normalized = modelUsageTokenCountOrNull(value);
|
|
488
|
+
if (normalized === null) {
|
|
489
|
+
rejectField(rejectedFields, field);
|
|
490
|
+
}
|
|
491
|
+
return normalized;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function boundedTokenSum(
|
|
495
|
+
values: Array<number | null>,
|
|
496
|
+
field: string,
|
|
497
|
+
rejectedFields: Set<string>,
|
|
498
|
+
): number | null {
|
|
499
|
+
if (values.length === 0 || values.some((value) => value === null)) {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
let total = 0;
|
|
503
|
+
for (const value of values as number[]) {
|
|
504
|
+
if (value > MAX_MODEL_USAGE_TOKEN_COUNT - total) {
|
|
505
|
+
rejectField(rejectedFields, field);
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
total += value;
|
|
509
|
+
}
|
|
510
|
+
return total;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function rejectField(rejectedFields: Set<string>, field: string): void {
|
|
514
|
+
if (rejectedFields.size < 32) {
|
|
515
|
+
rejectedFields.add(field);
|
|
516
|
+
}
|
|
73
517
|
}
|