@truefoundry/ai-gateway-sdk 0.0.9 → 0.0.11

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.
Files changed (33) hide show
  1. package/dist/autogen/models.d.ts +5 -0
  2. package/dist/autogen/schema.json +4 -0
  3. package/dist/constants.d.ts +8 -0
  4. package/dist/constants.js +6 -0
  5. package/dist/constants.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/services/buildExecutionReport.d.ts +36 -11
  10. package/dist/services/buildExecutionReport.js +876 -2
  11. package/dist/services/buildExecutionReport.js.map +1 -1
  12. package/dist/services/execute.js +4 -2
  13. package/dist/services/execute.js.map +1 -1
  14. package/dist/services/handleProviderResponse.d.ts +3 -1
  15. package/dist/services/handleProviderResponse.js +27 -5
  16. package/dist/services/handleProviderResponse.js.map +1 -1
  17. package/dist/stream/classifyAwsStreamEventKey.d.ts +30 -0
  18. package/dist/stream/classifyAwsStreamEventKey.js +67 -0
  19. package/dist/stream/classifyAwsStreamEventKey.js.map +1 -0
  20. package/dist/stream/index.d.ts +1 -0
  21. package/dist/stream/index.js +1 -0
  22. package/dist/stream/index.js.map +1 -1
  23. package/dist/stream/pumpProviderAwsEventStream.js +13 -1
  24. package/dist/stream/pumpProviderAwsEventStream.js.map +1 -1
  25. package/dist/stream/pumpProviderSseStream.js +6 -1
  26. package/dist/stream/pumpProviderSseStream.js.map +1 -1
  27. package/dist/stream/resolveStreamEndReason.d.ts +2 -0
  28. package/dist/stream/resolveStreamEndReason.js +18 -0
  29. package/dist/stream/resolveStreamEndReason.js.map +1 -1
  30. package/dist/stream/types.d.ts +4 -1
  31. package/dist/types/contracts.d.ts +2 -0
  32. package/dist/types/executionReport.d.ts +8 -0
  33. package/package.json +1 -1
@@ -1,6 +1,15 @@
1
- import { resolveStreamEndReason } from '../stream/resolveStreamEndReason.js';
1
+ /**
2
+ * Settle {@link ExecutionReport} for SDK execute (P7.1).
3
+ *
4
+ * Token facts: CLONE of live loggers (`*UsageFrom*`) + NEW routers (`liveUsageFrom*`).
5
+ * No USD / OTel / Model-span close — gateway egress owns that later.
6
+ */
7
+ import { VERTEX_MODALITY } from '../providers/google-vertex-ai/types.js';
8
+ import { isStreamingTerminatedError, resolveStreamEndReason } from '../stream/resolveStreamEndReason.js';
2
9
  import { billedUsageFromRequestResponse, extractSpeechToTextBilledSeconds, } from '../utils/billedUsage.js';
3
10
  import { asNumber, asRecord, nestedRecord, usageRecord } from '../utils/jsonValue.js';
11
+ import { providerReportedCostInUsd } from '../utils/providerReportedCost.js';
12
+ import { getSseDataLines, parseSseDataJson } from '../utils/sseUtils.js';
4
13
  export function usageFromJson(json) {
5
14
  const usage = usageRecord(json);
6
15
  if (!usage)
@@ -182,15 +191,33 @@ export function createStreamingReportDeferred(args) {
182
191
  return {
183
192
  promise,
184
193
  settle(pumpResult, extra) {
194
+ const pumpError = pumpResult.error;
195
+ let endError;
196
+ if (pumpError) {
197
+ const socket = new TypeError(pumpError.message || 'terminated', { cause: pumpError.cause });
198
+ if (isStreamingTerminatedError(socket)) {
199
+ socket.status = pumpError.status ?? 500;
200
+ endError = socket;
201
+ }
202
+ else {
203
+ endError = new Error(String(pumpError.message ?? 'stream error'));
204
+ }
205
+ }
185
206
  finish({
186
207
  ...args,
187
208
  streaming: true,
188
209
  usage: extra?.usage,
189
210
  iterations: extra?.iterations,
211
+ tokensEstimated: extra?.tokensEstimated,
212
+ serviceTier: extra?.serviceTier,
213
+ toolUsage: extra?.toolUsage,
214
+ providerReportedCostInUsd: extra?.providerReportedCostInUsd, // NEW report field (live usage.cost)
215
+ streamFrames: pumpResult.frames, // NEW: same single buffer live keeps as chunks[]
216
+ streamEventCounts: pumpResult.eventCounts,
190
217
  endReason: resolveStreamEndReason({
191
218
  truncated: pumpResult.truncated,
192
219
  finishReason: extra?.finishReason,
193
- error: pumpResult.error ? new Error(String(pumpResult.error.message ?? 'stream error')) : undefined,
220
+ error: endError,
194
221
  }),
195
222
  });
196
223
  },
@@ -203,4 +230,851 @@ export function createStreamingReportDeferred(args) {
203
230
  },
204
231
  };
205
232
  }
233
+ /** CLONE: live `estimateTokenCountFromText` (~3.5 chars/token). */
234
+ function createTokenEstimator() {
235
+ const estimator = {
236
+ used: false,
237
+ fromText(text) {
238
+ estimator.used = true;
239
+ if (!text || typeof text !== 'string')
240
+ return 0;
241
+ return Math.ceil(text.length / 3.5);
242
+ },
243
+ };
244
+ return estimator;
245
+ }
246
+ /** CLONE: live `getCacheWriteTokens`. */
247
+ function getCacheWriteTokens(usage) {
248
+ return (usage?.cache_creation_input_tokens ??
249
+ usage?.prompt_tokens_details?.cache_write_tokens ??
250
+ usage?.input_tokens_details?.cache_write_tokens ??
251
+ 0);
252
+ }
253
+ /** CLONE: live `getCacheReadImageTokens`. */
254
+ function getCacheReadImageTokens(usage) {
255
+ return (usage?.prompt_tokens_details?.cached_tokens_details?.image_tokens ??
256
+ usage?.input_tokens_details?.cached_tokens_details?.image_tokens ??
257
+ 0);
258
+ }
259
+ /** CLONE: live `getToolUsageFromResponsesOutput` (completed search web_search_call). */
260
+ function getToolUsageFromResponsesOutput(output) {
261
+ if (!Array.isArray(output))
262
+ return {};
263
+ let webSearchCount = 0;
264
+ for (const outputItem of output) {
265
+ if (outputItem?.type !== 'web_search_call' || outputItem?.status !== 'completed')
266
+ continue;
267
+ const action = outputItem?.action?.type;
268
+ if (action && action !== 'search')
269
+ continue;
270
+ webSearchCount += 1;
271
+ }
272
+ return webSearchCount > 0 ? { web_search: webSearchCount } : {};
273
+ }
274
+ /** CLONE: live `logChatCompleteResponse` (non-stream chat + estimate fallback). */
275
+ function chatUsageFromResponse(response, request, est) {
276
+ if ('error' in response)
277
+ return { usage: {} };
278
+ const usage = response.usage;
279
+ let promptTokenCount = usage?.prompt_tokens || 0;
280
+ let completionTokenCount = usage?.completion_tokens || 0;
281
+ const cacheReadTokenCount = usage?.cache_read_input_tokens || usage?.prompt_tokens_details?.cached_tokens || 0;
282
+ if (!promptTokenCount) {
283
+ // CLONE: live pushes tool_call functions into request.functions before estimating.
284
+ const toolCallFunctions = [];
285
+ request.messages?.forEach((message) => {
286
+ message.tool_calls?.forEach((toolCall) => {
287
+ if (toolCall.function)
288
+ toolCallFunctions.push(toolCall.function);
289
+ });
290
+ });
291
+ for (const message of request.messages ?? []) {
292
+ if (typeof message.content === 'string') {
293
+ promptTokenCount += est.fromText(message.content);
294
+ }
295
+ else if (message.content && typeof message.content === 'object' && 'type' in message.content) {
296
+ promptTokenCount += est.fromText(JSON.stringify(message.content));
297
+ }
298
+ promptTokenCount += 5;
299
+ }
300
+ if (request.functions) {
301
+ promptTokenCount += est.fromText(JSON.stringify([...request.functions, ...toolCallFunctions])) + 10;
302
+ }
303
+ if (request.function_call) {
304
+ promptTokenCount += est.fromText(JSON.stringify(request.function_call)) + 5;
305
+ }
306
+ completionTokenCount = est.fromText(response.choices?.map((choice) => choice.message.content)?.join('') || '');
307
+ }
308
+ return {
309
+ usage: {
310
+ input: promptTokenCount,
311
+ output: completionTokenCount,
312
+ cache_read: cacheReadTokenCount,
313
+ cache_write: getCacheWriteTokens(usage),
314
+ cache_write_1h: usage?.cache_creation?.ephemeral_1h_input_tokens || 0,
315
+ input_image: usage?.prompt_tokens_details?.image_tokens ?? 0,
316
+ cache_read_image: getCacheReadImageTokens(usage),
317
+ output_image: usage?.completion_tokens_details?.image_tokens ?? 0,
318
+ },
319
+ tokensEstimated: est.used,
320
+ finishReason: response.choices?.[0]?.finish_reason,
321
+ serviceTier: response.service_tier,
322
+ providerReportedCostInUsd: providerReportedCostInUsd(usage),
323
+ };
324
+ }
325
+ /** CLONE: live `estimateTokenCounts` (streaming chat fallback). */
326
+ function estimateChatTokenCounts(request, messages, est) {
327
+ let promptTokenCount = 0;
328
+ for (const message of request.messages ?? []) {
329
+ if (typeof message.content === 'string') {
330
+ promptTokenCount += est.fromText(message.content);
331
+ }
332
+ else if (Array.isArray(message.content)) {
333
+ for (const part of message.content) {
334
+ if (part && typeof part === 'object' && 'text' in part && typeof part.text === 'string') {
335
+ promptTokenCount += est.fromText(part.text);
336
+ }
337
+ }
338
+ }
339
+ else if (message.content && typeof message.content === 'object' && 'type' in message.content) {
340
+ promptTokenCount += est.fromText(JSON.stringify(message.content));
341
+ }
342
+ promptTokenCount += 5;
343
+ }
344
+ if (request.functions) {
345
+ promptTokenCount += est.fromText(JSON.stringify(request.functions)) + 10;
346
+ }
347
+ if (request.function_call) {
348
+ promptTokenCount += est.fromText(JSON.stringify(request.function_call)) + 5;
349
+ }
350
+ const completionText = messages
351
+ ?.map(message => {
352
+ if (typeof message.content === 'string')
353
+ return message.content;
354
+ if (Array.isArray(message.content)) {
355
+ return message.content
356
+ .map((part) => (part && typeof part === 'object' && 'text' in part ? (part.text ?? '') : ''))
357
+ .join('');
358
+ }
359
+ return '';
360
+ })
361
+ .join('') || '';
362
+ return { promptTokenCount, completionTokenCount: est.fromText(completionText) };
363
+ }
364
+ /** CLONE: live `logChatCompleteResponseForStreaming` + `parseChunksAndExtractData`. */
365
+ function chatUsageFromStream(chunks, request, streamState, est) {
366
+ const messages = [];
367
+ const extracted = {};
368
+ let finishReason;
369
+ let serviceTier;
370
+ let reportedCostInUsd;
371
+ chunks.forEach(chunk => {
372
+ const parsedChunk = parseSseDataJson(chunk);
373
+ if (!parsedChunk)
374
+ return;
375
+ for (const choice of parsedChunk.choices ?? []) {
376
+ if (choice.finish_reason)
377
+ finishReason = choice.finish_reason;
378
+ messages.push(choice.delta);
379
+ }
380
+ if (parsedChunk.service_tier)
381
+ serviceTier = parsedChunk.service_tier;
382
+ const usage = parsedChunk.usage;
383
+ if (usage) {
384
+ if (usage.prompt_tokens)
385
+ extracted.prompt_tokens = usage.prompt_tokens;
386
+ if (usage.completion_tokens)
387
+ extracted.completion_tokens = usage.completion_tokens;
388
+ if (usage.cache_read_input_tokens)
389
+ extracted.cache_read_input_tokens = usage.cache_read_input_tokens;
390
+ const cacheWriteTokens = getCacheWriteTokens(usage);
391
+ if (cacheWriteTokens)
392
+ extracted.cache_creation_input_tokens = cacheWriteTokens;
393
+ if (usage.cache_creation?.ephemeral_1h_input_tokens) {
394
+ extracted.cache_creation_input_tokens_1h = usage.cache_creation.ephemeral_1h_input_tokens;
395
+ }
396
+ if (usage.prompt_tokens_details?.cached_tokens) {
397
+ extracted.cache_read_input_tokens = usage.prompt_tokens_details.cached_tokens;
398
+ }
399
+ if (usage.prompt_tokens_details?.image_tokens !== undefined) {
400
+ extracted.input_image_tokens = usage.prompt_tokens_details.image_tokens;
401
+ }
402
+ const cacheReadImageTokens = getCacheReadImageTokens(usage);
403
+ if (cacheReadImageTokens)
404
+ extracted.cache_read_image_tokens = cacheReadImageTokens;
405
+ if (usage.completion_tokens_details?.image_tokens !== undefined) {
406
+ extracted.output_image_tokens = usage.completion_tokens_details.image_tokens;
407
+ }
408
+ const reported = providerReportedCostInUsd(usage);
409
+ if (reported !== undefined)
410
+ reportedCostInUsd = reported;
411
+ }
412
+ });
413
+ let promptTokenCount;
414
+ let completionTokenCount;
415
+ let cacheReadTokenCount;
416
+ let cacheWriteTokenCount;
417
+ if (extracted.prompt_tokens !== undefined && extracted.completion_tokens !== undefined) {
418
+ promptTokenCount = extracted.prompt_tokens;
419
+ completionTokenCount = extracted.completion_tokens;
420
+ cacheReadTokenCount = extracted.cache_read_input_tokens ?? 0;
421
+ cacheWriteTokenCount = extracted.cache_creation_input_tokens ?? 0;
422
+ }
423
+ else {
424
+ const estimated = estimateChatTokenCounts(request, messages, est);
425
+ promptTokenCount = extracted.prompt_tokens ?? streamState?.promptTokensWithCache ?? estimated.promptTokenCount;
426
+ completionTokenCount = extracted.completion_tokens ?? estimated.completionTokenCount;
427
+ cacheReadTokenCount = extracted.cache_read_input_tokens ?? streamState?.usage?.cache_read_input_tokens ?? 0;
428
+ cacheWriteTokenCount =
429
+ extracted.cache_creation_input_tokens ?? streamState?.usage?.cache_creation_input_tokens ?? 0;
430
+ }
431
+ return {
432
+ usage: {
433
+ input: promptTokenCount,
434
+ output: completionTokenCount,
435
+ cache_read: cacheReadTokenCount,
436
+ cache_write: cacheWriteTokenCount,
437
+ cache_write_1h: extracted.cache_creation_input_tokens_1h ?? 0,
438
+ input_image: extracted.input_image_tokens ?? 0,
439
+ cache_read_image: extracted.cache_read_image_tokens ?? 0,
440
+ output_image: extracted.output_image_tokens ?? 0,
441
+ },
442
+ tokensEstimated: est.used,
443
+ finishReason,
444
+ serviceTier,
445
+ providerReportedCostInUsd: reportedCostInUsd,
446
+ };
447
+ }
448
+ /** CLONE: live `logCompleteResponse` (non-stream completions). */
449
+ function completeUsageFromResponse(response, request, est) {
450
+ if ('error' in response)
451
+ return { usage: {} };
452
+ const concatenatedPrompt = Array.isArray(request.prompt) ? request.prompt.join('\n\n') : request.prompt;
453
+ const usage = response.usage;
454
+ let promptTokenCount = usage?.prompt_tokens || 0;
455
+ let completionTokenCount = usage?.completion_tokens || 0;
456
+ if (!promptTokenCount) {
457
+ promptTokenCount = est.fromText(concatenatedPrompt || '');
458
+ completionTokenCount = est.fromText(response.choices?.map((choice) => choice.text)?.join('\n\n') || '');
459
+ }
460
+ return {
461
+ usage: {
462
+ input: promptTokenCount,
463
+ output: completionTokenCount,
464
+ cache_read: usage?.cache_read_input_tokens || usage?.prompt_tokens_details?.cached_tokens || 0,
465
+ cache_write: getCacheWriteTokens(usage),
466
+ },
467
+ tokensEstimated: est.used,
468
+ };
469
+ }
470
+ /** CLONE: live `logCompleteResponseForStreaming` / assembleStreamingCompletion token half. */
471
+ function completeUsageFromStream(chunks, request, est) {
472
+ const concatenatedPrompt = Array.isArray(request.prompt) ? request.prompt.join('\n\n') : request.prompt;
473
+ let completeResponse = '';
474
+ const extracted = {};
475
+ chunks.forEach(chunk => {
476
+ const parsedChunk = parseSseDataJson(chunk);
477
+ if (!parsedChunk)
478
+ return;
479
+ parsedChunk.choices?.forEach((choice) => {
480
+ completeResponse += choice.text;
481
+ });
482
+ const usage = parsedChunk.usage;
483
+ if (usage) {
484
+ if (usage.prompt_tokens)
485
+ extracted.prompt_tokens = usage.prompt_tokens;
486
+ if (usage.completion_tokens)
487
+ extracted.completion_tokens = usage.completion_tokens;
488
+ if (usage.cache_read_input_tokens)
489
+ extracted.cache_read_input_tokens = usage.cache_read_input_tokens;
490
+ const cacheWriteTokens = getCacheWriteTokens(usage);
491
+ if (cacheWriteTokens)
492
+ extracted.cache_creation_input_tokens = cacheWriteTokens;
493
+ if (usage.prompt_tokens_details?.cached_tokens)
494
+ extracted.prompt_tokens_details = usage.prompt_tokens_details;
495
+ }
496
+ });
497
+ let promptTokenCount;
498
+ let completionTokenCount;
499
+ if (extracted.prompt_tokens !== undefined && extracted.completion_tokens !== undefined) {
500
+ promptTokenCount = extracted.prompt_tokens;
501
+ completionTokenCount = extracted.completion_tokens;
502
+ }
503
+ else {
504
+ promptTokenCount = extracted.prompt_tokens ?? est.fromText(concatenatedPrompt || '');
505
+ completionTokenCount = extracted.completion_tokens ?? est.fromText(completeResponse);
506
+ }
507
+ return {
508
+ usage: {
509
+ input: promptTokenCount,
510
+ output: completionTokenCount,
511
+ cache_read: extracted.cache_read_input_tokens || extracted.prompt_tokens_details?.cached_tokens || 0,
512
+ cache_write: extracted.cache_creation_input_tokens || 0,
513
+ },
514
+ tokensEstimated: est.used,
515
+ };
516
+ }
517
+ /** CLONE: live `numTokensInEmbeddingInput` (always flags estimation). */
518
+ function numTokensInEmbeddingInput(embeddingInput, est) {
519
+ est.used = true;
520
+ if (typeof embeddingInput === 'string')
521
+ return est.fromText(embeddingInput);
522
+ if (Array.isArray(embeddingInput) && embeddingInput.length > 0) {
523
+ if (typeof embeddingInput[0] === 'string') {
524
+ return embeddingInput.reduce((acc, inp) => acc + est.fromText(inp), 0);
525
+ }
526
+ if (typeof embeddingInput[0] === 'number')
527
+ return embeddingInput.length;
528
+ if (Array.isArray(embeddingInput[0])) {
529
+ return embeddingInput.reduce((acc, inp) => acc + inp.length, 0);
530
+ }
531
+ }
532
+ return 0;
533
+ }
534
+ /** CLONE: live `logEmbedResponse` (input tokens; estimate from request.input when missing). */
535
+ function embedUsageFromResponse(response, request, est) {
536
+ if ('error' in response)
537
+ return { usage: {} };
538
+ const promptTokenCount = response.usage?.prompt_tokens || numTokensInEmbeddingInput(request.input, est);
539
+ return { usage: { input: promptTokenCount }, tokensEstimated: est.used };
540
+ }
541
+ /** CLONE: live `logRerankResponse` (`isError` true for /ranking proxy detail→error). */
542
+ function rerankUsageFromResponse(response, request, est, isError) {
543
+ if (isError)
544
+ return { usage: {} };
545
+ let promptTokenCount = response.usage?.prompt_tokens || 0;
546
+ let completionTokenCount = 0;
547
+ if (!promptTokenCount) {
548
+ promptTokenCount = est.fromText(JSON.stringify(request.documents) || '');
549
+ completionTokenCount = est.fromText(response.results?.map((result) => result.document ?? '').join('\n\n'));
550
+ }
551
+ return { usage: { input: promptTokenCount, output: completionTokenCount }, tokensEstimated: est.used };
552
+ }
553
+ /** CLONE: live `logDecisionResponse` (estimate from evidence + questions when usage missing). */
554
+ function decisionUsageFromResponse(response, request, est) {
555
+ if ('error' in response)
556
+ return { usage: {} };
557
+ const usage = response.usage;
558
+ let promptTokenCount = usage?.prompt_tokens ?? usage?.input_tokens ?? 0;
559
+ let completionTokenCount = usage?.completion_tokens ?? usage?.output_tokens ?? 0;
560
+ if (!promptTokenCount) {
561
+ const evidence = request.input ?? request.state;
562
+ promptTokenCount = est.fromText(`${JSON.stringify(evidence ?? '')}${JSON.stringify(request.questions ?? '')}`);
563
+ completionTokenCount = est.fromText(JSON.stringify(response.answers ?? ''));
564
+ }
565
+ return { usage: { input: promptTokenCount, output: completionTokenCount }, tokensEstimated: est.used };
566
+ }
567
+ /** CLONE: live `logResponsesApi` (non-stream; error:null = success). */
568
+ function responsesUsageFromResponse(response) {
569
+ if (response.error !== null && response.error !== undefined)
570
+ return { usage: {} };
571
+ const usage = response.usage;
572
+ return {
573
+ usage: {
574
+ input: usage?.input_tokens || 0,
575
+ output: usage?.output_tokens || 0,
576
+ cache_read: usage?.input_tokens_details?.cached_tokens || 0,
577
+ cache_write: getCacheWriteTokens(usage),
578
+ },
579
+ toolUsage: getToolUsageFromResponsesOutput(response.output),
580
+ providerReportedCostInUsd: providerReportedCostInUsd(usage),
581
+ };
582
+ }
583
+ /** CLONE: live `logResponsesApiForStreaming` (usage/tools from response.completed only). */
584
+ function responsesUsageFromStream(chunks) {
585
+ let completeResponse = {};
586
+ // Per data: line — completed + [DONE] in one frame must not be joined (parseSseDataJson would).
587
+ for (const chunk of chunks) {
588
+ for (const dataLine of getSseDataLines(chunk)) {
589
+ if (dataLine === '[DONE]')
590
+ continue;
591
+ let parsedChunk;
592
+ try {
593
+ parsedChunk = JSON.parse(dataLine);
594
+ }
595
+ catch {
596
+ continue;
597
+ }
598
+ if (parsedChunk.type === 'response.completed' && parsedChunk.response) {
599
+ completeResponse = parsedChunk.response;
600
+ }
601
+ }
602
+ }
603
+ const usage = completeResponse.usage;
604
+ return {
605
+ usage: {
606
+ input: usage?.input_tokens || 0,
607
+ output: usage?.output_tokens || 0,
608
+ cache_read: usage?.input_tokens_details?.cached_tokens || 0,
609
+ cache_write: getCacheWriteTokens(usage),
610
+ },
611
+ toolUsage: getToolUsageFromResponsesOutput(completeResponse.output),
612
+ providerReportedCostInUsd: providerReportedCostInUsd(usage),
613
+ };
614
+ }
615
+ /** CLONE: live `logCreateModelResponseCompactResponse`. */
616
+ function compactUsageFromResponse(response) {
617
+ if ('error' in response && response.error != null)
618
+ return { usage: {} };
619
+ const usage = response.usage;
620
+ return {
621
+ usage: {
622
+ input: usage?.input_tokens ?? 0,
623
+ output: usage?.output_tokens ?? 0,
624
+ cache_read: usage?.input_tokens_details?.cached_tokens ?? 0,
625
+ cache_write: getCacheWriteTokens(usage),
626
+ },
627
+ };
628
+ }
629
+ /** CLONE: live `logAnthropicMessageResponse` (input = base + cache read + cache write). */
630
+ function anthropicUsageFromResponse(response) {
631
+ if ('error' in response)
632
+ return { usage: {} };
633
+ const cacheReadTokenCount = response.usage?.cache_read_input_tokens || 0;
634
+ const cacheWriteTokenCount = response.usage?.cache_creation_input_tokens || 0;
635
+ return {
636
+ usage: {
637
+ input: (response.usage?.input_tokens || 0) + cacheReadTokenCount + cacheWriteTokenCount,
638
+ output: response.usage?.output_tokens || 0,
639
+ cache_read: cacheReadTokenCount,
640
+ cache_write: cacheWriteTokenCount,
641
+ cache_write_1h: response.usage?.cache_creation?.ephemeral_1h_input_tokens || 0,
642
+ },
643
+ finishReason: response.stop_reason ?? undefined,
644
+ };
645
+ }
646
+ /** CLONE: live Anthropic stream token half (message_start input / message_delta output). */
647
+ function anthropicUsageFromStream(chunks, cancelledMidwayByClient, est) {
648
+ let promptTokenCount = 0;
649
+ let completionTokenCount = 0;
650
+ let cacheReadTokenCount = 0;
651
+ let cacheWriteTokenCount = 0;
652
+ let cacheWrite1hTokenCount = 0;
653
+ let stopReason;
654
+ const generatedTextByIndex = new Map();
655
+ for (const chunk of chunks) {
656
+ const parsedData = parseSseDataJson(chunk);
657
+ if (!parsedData || parsedData.type === 'ping')
658
+ continue;
659
+ if (parsedData.type === 'message_start' && parsedData.message) {
660
+ const usage = parsedData.message.usage;
661
+ if (usage?.input_tokens)
662
+ promptTokenCount = usage.input_tokens;
663
+ if (usage?.cache_read_input_tokens)
664
+ cacheReadTokenCount = usage.cache_read_input_tokens;
665
+ if (usage?.cache_creation_input_tokens)
666
+ cacheWriteTokenCount = usage.cache_creation_input_tokens;
667
+ if (usage?.cache_creation?.ephemeral_1h_input_tokens) {
668
+ cacheWrite1hTokenCount = usage.cache_creation.ephemeral_1h_input_tokens;
669
+ }
670
+ }
671
+ if (parsedData.type === 'content_block_start' && parsedData.content_block) {
672
+ const block = parsedData.content_block;
673
+ if (block.type === 'text') {
674
+ generatedTextByIndex.set(parsedData.index, { kind: 'text', text: block.text ?? '' });
675
+ }
676
+ else if (block.type === 'tool_use' || block.type === 'server_tool_use') {
677
+ generatedTextByIndex.set(parsedData.index, { kind: 'tool', text: '' });
678
+ }
679
+ else if (block.type === 'thinking') {
680
+ generatedTextByIndex.set(parsedData.index, { kind: 'thinking', text: block.thinking ?? '' });
681
+ }
682
+ }
683
+ if (parsedData.type === 'content_block_delta' && parsedData.delta) {
684
+ const state = generatedTextByIndex.get(parsedData.index);
685
+ if (state?.kind === 'text' && parsedData.delta.type === 'text_delta') {
686
+ state.text += parsedData.delta.text || '';
687
+ }
688
+ else if (state?.kind === 'thinking' && parsedData.delta.type === 'thinking_delta') {
689
+ state.text += parsedData.delta.thinking || '';
690
+ }
691
+ else if (state?.kind === 'tool' && parsedData.delta.type === 'input_json_delta') {
692
+ state.text += parsedData.delta.partial_json ?? '';
693
+ }
694
+ }
695
+ if (parsedData.type === 'message_delta') {
696
+ if (parsedData.delta?.stop_reason)
697
+ stopReason = parsedData.delta.stop_reason;
698
+ const usage = parsedData.usage;
699
+ if (usage?.output_tokens)
700
+ completionTokenCount = usage.output_tokens;
701
+ if (usage?.input_tokens)
702
+ promptTokenCount = usage.input_tokens;
703
+ if (usage?.cache_creation?.ephemeral_1h_input_tokens) {
704
+ cacheWrite1hTokenCount = usage.cache_creation.ephemeral_1h_input_tokens;
705
+ }
706
+ }
707
+ }
708
+ if (completionTokenCount === 0 && cancelledMidwayByClient) {
709
+ const streamedText = [...generatedTextByIndex.values()].map(state => state.text).join('');
710
+ if (streamedText.length > 0)
711
+ completionTokenCount = est.fromText(streamedText);
712
+ }
713
+ return {
714
+ usage: {
715
+ input: promptTokenCount + cacheReadTokenCount + cacheWriteTokenCount,
716
+ output: completionTokenCount,
717
+ cache_read: cacheReadTokenCount,
718
+ cache_write: cacheWriteTokenCount,
719
+ cache_write_1h: cacheWrite1hTokenCount,
720
+ },
721
+ tokensEstimated: est.used,
722
+ finishReason: stopReason,
723
+ };
724
+ }
725
+ /** CLONE: live `parseGeminiUsageMetadata` (output = candidates + thoughts). */
726
+ function geminiUsageFromMetadata(metadata) {
727
+ const imageTokens = (details) => (details ?? []).reduce((acc, curr) => (curr.modality === VERTEX_MODALITY.IMAGE ? acc + curr.tokenCount : acc), 0);
728
+ const cachedContentTokenCount = typeof metadata.cachedContentTokenCount === 'number' ? metadata.cachedContentTokenCount : 0;
729
+ const usage = {
730
+ input: typeof metadata.promptTokenCount === 'number' ? metadata.promptTokenCount : 0,
731
+ output: (typeof metadata.candidatesTokenCount === 'number' ? metadata.candidatesTokenCount : 0) +
732
+ (typeof metadata.thoughtsTokenCount === 'number' ? metadata.thoughtsTokenCount : 0),
733
+ input_image: imageTokens(metadata.promptTokensDetails),
734
+ output_image: imageTokens(metadata.candidatesTokensDetails),
735
+ };
736
+ if (cachedContentTokenCount > 0)
737
+ usage.cache_read = cachedContentTokenCount;
738
+ return usage;
739
+ }
740
+ /** CLONE: live `logGeminiProxyResponse` (no usageMetadata → empty usage). */
741
+ function geminiUsageFromResponse(response) {
742
+ if (response.error)
743
+ return { usage: {} };
744
+ const metadata = response.usageMetadata;
745
+ return { usage: metadata && typeof metadata === 'object' ? geminiUsageFromMetadata(metadata) : {} };
746
+ }
747
+ /** CLONE: live `parseUsageFromGeminiStreamChunks` (last usageMetadata wins). */
748
+ function geminiUsageFromStream(chunks) {
749
+ let usage = {};
750
+ chunks.forEach(chunk => {
751
+ if (!chunk)
752
+ return;
753
+ const trimmed = chunk
754
+ .trim()
755
+ .replace(/\r\n+$/, '')
756
+ .trim();
757
+ if (!trimmed)
758
+ return;
759
+ const dataMatch = trimmed.match(/^data:\s*(.+)$/m);
760
+ const raw = dataMatch ? dataMatch[1].trim() : trimmed;
761
+ if (!raw || raw === '[DONE]')
762
+ return;
763
+ try {
764
+ const parsed = JSON.parse(raw);
765
+ if (parsed?.usageMetadata && typeof parsed.usageMetadata === 'object') {
766
+ usage = geminiUsageFromMetadata(parsed.usageMetadata);
767
+ }
768
+ }
769
+ catch {
770
+ return;
771
+ }
772
+ });
773
+ return { usage };
774
+ }
775
+ /** CLONE helper: BedrockCounts → TokenUsage (prompt already includes cache when live does). */
776
+ function bedrockCountsToUsage(counts) {
777
+ if (!counts)
778
+ return { input: 0, output: 0 };
779
+ const usage = { input: counts.promptTokenCount, output: counts.completionTokenCount };
780
+ if (counts.cacheReadTokenCount !== undefined)
781
+ usage.cache_read = counts.cacheReadTokenCount;
782
+ if (counts.cacheWriteTokenCount !== undefined)
783
+ usage.cache_write = counts.cacheWriteTokenCount;
784
+ if (counts.cacheWrite1hTokenCount !== undefined)
785
+ usage.cache_write_1h = counts.cacheWrite1hTokenCount;
786
+ return usage;
787
+ }
788
+ /** CLONE: live `sumConverseCacheDetails1h`. */
789
+ function sumConverseCacheDetails1h(cacheDetails) {
790
+ if (!Array.isArray(cacheDetails) || cacheDetails.length === 0)
791
+ return undefined;
792
+ let total = 0;
793
+ let saw1h = false;
794
+ for (const entry of cacheDetails) {
795
+ if (entry?.ttl === '1h') {
796
+ saw1h = true;
797
+ total += entry.inputTokens ?? 0;
798
+ }
799
+ }
800
+ return saw1h ? total : 0;
801
+ }
802
+ /** CLONE: live `logBedrockProxyResponse` (Invoke headers / Converse camelCase usage). */
803
+ function bedrockUsageFromResponse(response, headers) {
804
+ if (typeof response.message === 'string')
805
+ return { usage: {} };
806
+ const isInvokeBody = Array.isArray(response.generations) || Array.isArray(response.completions);
807
+ if (isInvokeBody) {
808
+ return {
809
+ usage: bedrockCountsToUsage({
810
+ promptTokenCount: Number(headers.get('x-amzn-bedrock-input-token-count')) || 0,
811
+ completionTokenCount: Number(headers.get('x-amzn-bedrock-output-token-count')) || 0,
812
+ }),
813
+ };
814
+ }
815
+ if (!('output' in response && 'usage' in response))
816
+ return { usage: {} };
817
+ const u = response.usage;
818
+ const cacheRead = u.cacheReadInputTokens ?? 0;
819
+ const cacheWrite = u.cacheWriteInputTokens ?? 0;
820
+ return {
821
+ usage: bedrockCountsToUsage({
822
+ promptTokenCount: u.inputTokens + cacheRead + cacheWrite,
823
+ completionTokenCount: u.outputTokens,
824
+ cacheReadTokenCount: u.cacheReadInputTokens,
825
+ cacheWriteTokenCount: u.cacheWriteInputTokens,
826
+ cacheWrite1hTokenCount: sumConverseCacheDetails1h(u.cacheDetails),
827
+ }),
828
+ };
829
+ }
830
+ /** CLONE: live bedrock `parseChunkJson` (raw JSON or AWS bytes envelope). */
831
+ function parseBedrockChunkJson(chunk) {
832
+ if (!chunk?.trim())
833
+ return null;
834
+ // SSE frames via parseSseDataJson (data:/event: order-safe); else raw JSON (AWS event stream).
835
+ let parsed = parseSseDataJson(chunk);
836
+ if (parsed === undefined) {
837
+ const raw = chunk.trim();
838
+ if (!raw || raw === '[DONE]')
839
+ return null;
840
+ try {
841
+ parsed = JSON.parse(raw);
842
+ }
843
+ catch {
844
+ return null;
845
+ }
846
+ }
847
+ if (parsed && typeof parsed === 'object' && typeof parsed.bytes === 'string') {
848
+ try {
849
+ const inner = Buffer.from(parsed.bytes, 'base64').toString('utf8');
850
+ if (inner)
851
+ parsed = JSON.parse(inner);
852
+ }
853
+ catch {
854
+ // keep outer
855
+ }
856
+ }
857
+ return parsed && typeof parsed === 'object' ? parsed : null;
858
+ }
859
+ /** CLONE: live bedrock flat `usage` block (camelCase + cache tokens). */
860
+ function bedrockCountsFromFlatUsage(u) {
861
+ if (!u || typeof u !== 'object')
862
+ return null;
863
+ const inN = typeof u.inputTokens === 'number' ? u.inputTokens : typeof u.inputTokenCount === 'number' ? u.inputTokenCount : 0;
864
+ const outN = typeof u.outputTokens === 'number'
865
+ ? u.outputTokens
866
+ : typeof u.outputTokenCount === 'number'
867
+ ? u.outputTokenCount
868
+ : 0;
869
+ if (!(inN > 0 || outN > 0))
870
+ return null;
871
+ const cacheRead = typeof u.cacheReadInputTokens === 'number' ? u.cacheReadInputTokens : 0;
872
+ const cacheWrite = typeof u.cacheWriteInputTokens === 'number' ? u.cacheWriteInputTokens : 0;
873
+ return {
874
+ promptTokenCount: inN + cacheRead + cacheWrite,
875
+ completionTokenCount: outN,
876
+ cacheReadTokenCount: cacheRead || undefined,
877
+ cacheWriteTokenCount: cacheWrite || undefined,
878
+ cacheWrite1hTokenCount: sumConverseCacheDetails1h(u.cacheDetails),
879
+ };
880
+ }
881
+ /** CLONE: live `parseUsageFromConverseChunks`. */
882
+ function bedrockCountsFromConverseChunks(chunks) {
883
+ for (const chunk of chunks) {
884
+ const p = parseBedrockChunkJson(chunk);
885
+ if (!p)
886
+ continue;
887
+ const flat = bedrockCountsFromFlatUsage(p.usage);
888
+ if (flat)
889
+ return flat;
890
+ const m = p.metrics;
891
+ if (m && (typeof m.inputTokenCount === 'number' || typeof m.outputTokenCount === 'number')) {
892
+ const cacheRead = m.cacheReadInputTokens ?? 0;
893
+ const cacheWrite = m.cacheWriteInputTokens ?? 0;
894
+ return {
895
+ promptTokenCount: (m.inputTokenCount ?? 0) + cacheRead + cacheWrite,
896
+ completionTokenCount: m.outputTokenCount ?? 0,
897
+ cacheReadTokenCount: m.cacheReadInputTokens,
898
+ cacheWriteTokenCount: m.cacheWriteInputTokens,
899
+ cacheWrite1hTokenCount: sumConverseCacheDetails1h(m.cacheDetails),
900
+ };
901
+ }
902
+ }
903
+ return null;
904
+ }
905
+ /** CLONE: live `parseUsageFromInvokeChunks`. */
906
+ function bedrockCountsFromInvokeChunks(chunks) {
907
+ let inputTokens = 0;
908
+ let outputTokens = 0;
909
+ let cacheReadTokens;
910
+ let cacheWriteTokens;
911
+ let cacheWrite1hTokens;
912
+ for (const chunk of chunks) {
913
+ const p = parseBedrockChunkJson(chunk);
914
+ if (!p)
915
+ continue;
916
+ const flat = bedrockCountsFromFlatUsage(p.usage);
917
+ if (flat)
918
+ return flat;
919
+ const m = p.metrics;
920
+ if (m && (typeof m.inputTokenCount === 'number' || typeof m.outputTokenCount === 'number')) {
921
+ inputTokens = m.inputTokenCount ?? inputTokens;
922
+ outputTokens = m.outputTokenCount ?? outputTokens;
923
+ if (typeof m.cacheReadInputTokens === 'number')
924
+ cacheReadTokens = m.cacheReadInputTokens;
925
+ if (typeof m.cacheWriteInputTokens === 'number')
926
+ cacheWriteTokens = m.cacheWriteInputTokens;
927
+ continue;
928
+ }
929
+ const metaUsage = p.metadata?.usage;
930
+ if (metaUsage && (typeof metaUsage.inputTokens === 'number' || typeof metaUsage.outputTokens === 'number')) {
931
+ const cacheRead = metaUsage.cacheReadInputTokens ?? 0;
932
+ const cacheWrite = metaUsage.cacheWriteInputTokens ?? 0;
933
+ return {
934
+ promptTokenCount: (metaUsage.inputTokens ?? 0) + cacheRead + cacheWrite,
935
+ completionTokenCount: metaUsage.outputTokens ?? 0,
936
+ cacheReadTokenCount: metaUsage.cacheReadInputTokens,
937
+ cacheWriteTokenCount: metaUsage.cacheWriteInputTokens,
938
+ cacheWrite1hTokenCount: sumConverseCacheDetails1h(metaUsage.cacheDetails),
939
+ };
940
+ }
941
+ const topMetrics = p['amazon-bedrock-invocationMetrics'];
942
+ if (topMetrics &&
943
+ (typeof topMetrics.inputTokenCount === 'number' || typeof topMetrics.outputTokenCount === 'number')) {
944
+ const cacheRead = topMetrics.cacheReadInputTokens ?? 0;
945
+ const cacheWrite = topMetrics.cacheWriteInputTokens ?? 0;
946
+ return {
947
+ promptTokenCount: (topMetrics.inputTokenCount ?? 0) + cacheRead + cacheWrite,
948
+ completionTokenCount: topMetrics.outputTokenCount ?? 0,
949
+ cacheReadTokenCount: topMetrics.cacheReadInputTokens,
950
+ cacheWriteTokenCount: topMetrics.cacheWriteInputTokens,
951
+ cacheWrite1hTokenCount: cacheWrite1hTokens,
952
+ };
953
+ }
954
+ const nestedMetrics = p.metadata?.['amazon-bedrock-invocationMetrics'];
955
+ if (nestedMetrics &&
956
+ (typeof nestedMetrics.inputTokenCount === 'number' || typeof nestedMetrics.outputTokenCount === 'number')) {
957
+ inputTokens = nestedMetrics.inputTokenCount ?? inputTokens;
958
+ outputTokens = nestedMetrics.outputTokenCount ?? outputTokens;
959
+ if (typeof nestedMetrics.cacheReadInputTokens === 'number')
960
+ cacheReadTokens = nestedMetrics.cacheReadInputTokens;
961
+ if (typeof nestedMetrics.cacheWriteInputTokens === 'number') {
962
+ cacheWriteTokens = nestedMetrics.cacheWriteInputTokens;
963
+ }
964
+ continue;
965
+ }
966
+ const msgStart = p.messageStart ?? p.message;
967
+ if (msgStart?.usage && typeof msgStart.usage.input_tokens === 'number') {
968
+ inputTokens = msgStart.usage.input_tokens;
969
+ if (typeof msgStart.usage.cache_read_input_tokens === 'number') {
970
+ cacheReadTokens = msgStart.usage.cache_read_input_tokens;
971
+ }
972
+ if (typeof msgStart.usage.cache_creation_input_tokens === 'number') {
973
+ cacheWriteTokens = msgStart.usage.cache_creation_input_tokens;
974
+ }
975
+ if (typeof msgStart.usage.cache_creation?.ephemeral_1h_input_tokens === 'number') {
976
+ cacheWrite1hTokens = msgStart.usage.cache_creation.ephemeral_1h_input_tokens;
977
+ }
978
+ }
979
+ const deltaUsage = (p.messageDelta ?? p)?.usage;
980
+ if (deltaUsage) {
981
+ if (typeof deltaUsage.output_tokens === 'number')
982
+ outputTokens = deltaUsage.output_tokens;
983
+ if (typeof deltaUsage.input_tokens === 'number')
984
+ inputTokens = deltaUsage.input_tokens;
985
+ if (typeof deltaUsage.cache_creation?.ephemeral_1h_input_tokens === 'number') {
986
+ cacheWrite1hTokens = deltaUsage.cache_creation.ephemeral_1h_input_tokens;
987
+ }
988
+ }
989
+ }
990
+ return inputTokens > 0 || outputTokens > 0
991
+ ? {
992
+ promptTokenCount: inputTokens + (cacheReadTokens ?? 0) + (cacheWriteTokens ?? 0),
993
+ completionTokenCount: outputTokens,
994
+ cacheReadTokenCount: cacheReadTokens,
995
+ cacheWriteTokenCount: cacheWriteTokens,
996
+ cacheWrite1hTokenCount: cacheWrite1hTokens,
997
+ }
998
+ : null;
999
+ }
1000
+ function isAnthropicMessagesProxy(scope) {
1001
+ return scope.endpoint === 'proxy' && scope.inboundPath.includes('/v1/messages');
1002
+ }
1003
+ /**
1004
+ * NEW router over CLONE parsers: non-stream counts live `handleNonStreamingMode` would price.
1005
+ * `undefined` = no live token logger (caller keeps usageFromJson).
1006
+ */
1007
+ export function liveUsageFromResponse(scope, json, headers) {
1008
+ const response = asRecord(json);
1009
+ if (!response)
1010
+ return undefined;
1011
+ const { endpoint, request } = scope;
1012
+ const est = createTokenEstimator();
1013
+ if (endpoint === 'chatComplete')
1014
+ return chatUsageFromResponse(response, request, est);
1015
+ if (endpoint === 'complete')
1016
+ return completeUsageFromResponse(response, request, est);
1017
+ if (endpoint === 'embed')
1018
+ return embedUsageFromResponse(response, request, est);
1019
+ if (endpoint === 'rerank')
1020
+ return rerankUsageFromResponse(response, request, est, 'error' in response);
1021
+ if (endpoint === 'decision')
1022
+ return decisionUsageFromResponse(response, request, est);
1023
+ if (endpoint === 'createModelResponse')
1024
+ return responsesUsageFromResponse(response);
1025
+ if (endpoint === 'createModelResponseCompact')
1026
+ return compactUsageFromResponse(response);
1027
+ if (endpoint === 'messages' || isAnthropicMessagesProxy(scope))
1028
+ return anthropicUsageFromResponse(response);
1029
+ if (endpoint === 'proxy' && scope.inboundPath.includes('/ranking')) {
1030
+ return rerankUsageFromResponse(response, request, est, Boolean(response.detail) || 'error' in response);
1031
+ }
1032
+ if (endpoint === 'generateContent')
1033
+ return geminiUsageFromResponse(response);
1034
+ if (endpoint === 'bedrockConverse')
1035
+ return bedrockUsageFromResponse(response, headers);
1036
+ if (endpoint === 'proxy' || endpoint === 'tts-proxy' || endpoint === 'stt-proxy') {
1037
+ if (scope.isGeminiRouter)
1038
+ return geminiUsageFromResponse(response);
1039
+ if (scope.isBedrockRouter)
1040
+ return bedrockUsageFromResponse(response, headers);
1041
+ // CLONE: live generic proxy spans carry no token counts.
1042
+ return { usage: {} };
1043
+ }
1044
+ return undefined;
1045
+ }
1046
+ /**
1047
+ * NEW router over CLONE parsers: stream counts live `logStreamingResponse` would price from frames.
1048
+ * `undefined` = no live stream logger for this endpoint.
1049
+ */
1050
+ export function liveUsageFromStreamFrames(scope, frames, streamState, cancelledMidwayByClient) {
1051
+ const { endpoint, inboundPath, request } = scope;
1052
+ const est = createTokenEstimator();
1053
+ if (endpoint === 'chatComplete')
1054
+ return chatUsageFromStream(frames, request, streamState, est);
1055
+ if (endpoint === 'complete')
1056
+ return completeUsageFromStream(frames, request, est);
1057
+ if (endpoint === 'createModelResponse')
1058
+ return responsesUsageFromStream(frames);
1059
+ if (endpoint === 'messages' ||
1060
+ isAnthropicMessagesProxy(scope) ||
1061
+ (endpoint === 'proxy' &&
1062
+ inboundPath.includes('/publishers/anthropic/models/') &&
1063
+ inboundPath.includes(':streamRawPredict'))) {
1064
+ return anthropicUsageFromStream(frames, cancelledMidwayByClient, est);
1065
+ }
1066
+ if (endpoint === 'generateContent' || (endpoint === 'proxy' && scope.isGeminiRouter)) {
1067
+ return geminiUsageFromStream(frames);
1068
+ }
1069
+ if (endpoint === 'bedrockConverse' || (endpoint === 'proxy' && scope.isBedrockRouter)) {
1070
+ const counts = inboundPath.includes('converse-stream')
1071
+ ? bedrockCountsFromConverseChunks(frames)
1072
+ : bedrockCountsFromInvokeChunks(frames);
1073
+ return { usage: bedrockCountsToUsage(counts) };
1074
+ }
1075
+ // CLONE: live logs no tokens for other proxy streams.
1076
+ if (endpoint === 'proxy')
1077
+ return { usage: {} };
1078
+ return undefined;
1079
+ }
206
1080
  //# sourceMappingURL=buildExecutionReport.js.map