@usagetap/sdk 1.4.0 → 1.7.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.
Files changed (52) hide show
  1. package/README.md +156 -18
  2. package/dist/adapters/anthropic.cjs +195 -235
  3. package/dist/adapters/anthropic.cjs.map +1 -1
  4. package/dist/adapters/anthropic.d.cts +1 -1
  5. package/dist/adapters/anthropic.d.ts +1 -1
  6. package/dist/adapters/anthropic.mjs +195 -235
  7. package/dist/adapters/anthropic.mjs.map +1 -1
  8. package/dist/adapters/openai.cjs +256 -274
  9. package/dist/adapters/openai.cjs.map +1 -1
  10. package/dist/adapters/openai.d.cts +1 -1
  11. package/dist/adapters/openai.d.ts +1 -1
  12. package/dist/adapters/openai.mjs +256 -274
  13. package/dist/adapters/openai.mjs.map +1 -1
  14. package/dist/adapters/openrouter.cjs +256 -274
  15. package/dist/adapters/openrouter.cjs.map +1 -1
  16. package/dist/adapters/openrouter.d.cts +1 -1
  17. package/dist/adapters/openrouter.d.ts +1 -1
  18. package/dist/adapters/openrouter.mjs +256 -274
  19. package/dist/adapters/openrouter.mjs.map +1 -1
  20. package/dist/anthropic/index.cjs +195 -235
  21. package/dist/anthropic/index.cjs.map +1 -1
  22. package/dist/anthropic/index.d.cts +1 -1
  23. package/dist/anthropic/index.d.ts +1 -1
  24. package/dist/anthropic/index.mjs +195 -235
  25. package/dist/anthropic/index.mjs.map +1 -1
  26. package/dist/{client-CExQ8e1T.d.cts → client-C0UiaqVB.d.cts} +141 -61
  27. package/dist/{client-CExQ8e1T.d.ts → client-C0UiaqVB.d.ts} +141 -61
  28. package/dist/express/index.cjs +189 -246
  29. package/dist/express/index.cjs.map +1 -1
  30. package/dist/express/index.d.cts +1 -1
  31. package/dist/express/index.d.ts +1 -1
  32. package/dist/express/index.mjs +189 -246
  33. package/dist/express/index.mjs.map +1 -1
  34. package/dist/index.cjs +391 -175
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +13 -4
  37. package/dist/index.d.ts +13 -4
  38. package/dist/index.mjs +391 -175
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/openai/index.cjs +256 -274
  41. package/dist/openai/index.cjs.map +1 -1
  42. package/dist/openai/index.d.cts +1 -1
  43. package/dist/openai/index.d.ts +1 -1
  44. package/dist/openai/index.mjs +256 -274
  45. package/dist/openai/index.mjs.map +1 -1
  46. package/dist/openrouter/index.cjs +256 -274
  47. package/dist/openrouter/index.cjs.map +1 -1
  48. package/dist/openrouter/index.d.cts +1 -1
  49. package/dist/openrouter/index.d.ts +1 -1
  50. package/dist/openrouter/index.mjs +256 -274
  51. package/dist/openrouter/index.mjs.map +1 -1
  52. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  import OpenAI from 'openai';
2
2
  import { Request, Response, NextFunction } from 'express';
3
- import { U as UsageTapClient } from '../client-CExQ8e1T.cjs';
3
+ import { U as UsageTapClient } from '../client-C0UiaqVB.cjs';
4
4
  import { WrapOpenAIContext, WrapOpenAIOptions, wrapOpenAI, pipeToResponse } from '../adapters/openai.cjs';
5
5
 
6
6
  interface ExpressUsageTapContext {
@@ -1,6 +1,6 @@
1
1
  import OpenAI from 'openai';
2
2
  import { Request, Response, NextFunction } from 'express';
3
- import { U as UsageTapClient } from '../client-CExQ8e1T.js';
3
+ import { U as UsageTapClient } from '../client-C0UiaqVB.js';
4
4
  import { WrapOpenAIContext, WrapOpenAIOptions, wrapOpenAI, pipeToResponse } from '../adapters/openai.js';
5
5
 
6
6
  interface ExpressUsageTapContext {
@@ -334,13 +334,13 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
334
334
  withUsage: withUsage2,
335
335
  promptCompression
336
336
  } = splitUsageOptions(options);
337
- const beginRequest = withRuntimeCompressionContext(
337
+ const beginRequest = responsesBeginRequest(
338
338
  resolveBeginRequest(defaultContext, usageContext),
339
- "openai",
340
- String(params.model)
339
+ params
341
340
  );
342
341
  const wantsStream = isStreamingRequest(params);
343
342
  return usageTap.withUsage(beginRequest, async (ctx) => {
343
+ const settle = wantsStream ? deferUsageFinalization(ctx) : void 0;
344
344
  const sampleStartedAt = Date.now();
345
345
  const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
346
346
  usageTap,
@@ -365,12 +365,31 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
365
365
  const apiPromise2 = originalCreate(finalParams, request);
366
366
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
367
367
  ensureAsyncIterable(rawStream, "responses.create");
368
- const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
369
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
370
- if (usage) {
371
- ctx.setUsage(usage);
368
+ const wrappedStream = wrapStreamForUsageTap(rawStream, async (termination) => {
369
+ try {
370
+ if (termination === "complete") {
371
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
372
+ if (usage) {
373
+ ctx.setUsage(usage);
374
+ }
375
+ }
376
+ } catch (error) {
377
+ ctx.setError({
378
+ code: "USAGE_FINALIZE_ERROR",
379
+ message: error instanceof Error ? error.message : String(error)
380
+ });
381
+ throw error;
382
+ } finally {
383
+ await settle?.();
372
384
  }
373
- }, ctx);
385
+ }, ctx, (chunk) => {
386
+ tryInferUsageFromStreamChunk(
387
+ chunk,
388
+ ctx.begin.data.vendorHints,
389
+ ctx,
390
+ provider
391
+ );
392
+ });
374
393
  return wrappedStream;
375
394
  });
376
395
  return wrappedPromise2;
@@ -426,13 +445,10 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
426
445
  withUsage: withUsage2,
427
446
  promptCompression
428
447
  } = splitUsageOptions(options);
429
- const beginRequest = withRuntimeCompressionContext(
430
- resolveBeginRequest(defaultContext, usageContext),
431
- "openai",
432
- String(params.model)
433
- );
448
+ const beginRequest = resolveBeginRequest(defaultContext, usageContext);
434
449
  const wantsStream = isStreamingRequest(params);
435
450
  return usageTap.withUsage(beginRequest, async (ctx) => {
451
+ const settle = wantsStream ? deferUsageFinalization(ctx) : void 0;
436
452
  const sampleStartedAt = Date.now();
437
453
  const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
438
454
  usageTap,
@@ -441,7 +457,7 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
441
457
  input: params
442
458
  });
443
459
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
444
- const finalParams = await compressChatParamsForCall({
460
+ const compressedParams = await compressChatParamsForCall({
445
461
  params: hintedParams,
446
462
  usageTap,
447
463
  ctx,
@@ -451,18 +467,38 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
451
467
  withUsage: withUsage2,
452
468
  operation: "chat.completions.create"
453
469
  });
470
+ const finalParams = wantsStream ? ensureOpenAIStreamUsage(compressedParams) : compressedParams;
454
471
  ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
455
472
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
456
473
  if (wantsStream) {
457
474
  const apiPromise2 = originalCreate(finalParams, request);
458
475
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
459
476
  ensureAsyncIterable(rawStream, "chat.completions.create");
460
- const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
461
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
462
- if (usage) {
463
- ctx.setUsage(usage);
477
+ const wrappedStream2 = wrapStreamForUsageTap(rawStream, async (termination) => {
478
+ try {
479
+ if (termination === "complete") {
480
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
481
+ if (usage) {
482
+ ctx.setUsage(usage);
483
+ }
484
+ }
485
+ } catch (error) {
486
+ ctx.setError({
487
+ code: "USAGE_FINALIZE_ERROR",
488
+ message: error instanceof Error ? error.message : String(error)
489
+ });
490
+ throw error;
491
+ } finally {
492
+ await settle?.();
464
493
  }
465
- }, ctx);
494
+ }, ctx, (chunk) => {
495
+ tryInferUsageFromStreamChunk(
496
+ chunk,
497
+ ctx.begin.data.vendorHints,
498
+ ctx,
499
+ provider
500
+ );
501
+ });
466
502
  return wrappedStream2;
467
503
  });
468
504
  return wrappedPromise2;
@@ -504,14 +540,11 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
504
540
  withUsage: withUsage2,
505
541
  promptCompression
506
542
  } = splitUsageOptions(options);
507
- const beginRequest = withRuntimeCompressionContext(
508
- resolveBeginRequest(defaultContext, usageContext),
509
- "openai",
510
- String(params.model)
511
- );
543
+ const beginRequest = resolveBeginRequest(defaultContext, usageContext);
512
544
  return usageTap.withUsage(beginRequest, async (ctx) => {
545
+ const settle = deferUsageFinalization(ctx);
513
546
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
514
- const finalParams = await compressChatParamsForCall({
547
+ const compressedParams = await compressChatParamsForCall({
515
548
  params: hintedParams,
516
549
  usageTap,
517
550
  ctx,
@@ -521,21 +554,41 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
521
554
  withUsage: withUsage2,
522
555
  operation: "chat.completions.stream"
523
556
  });
557
+ const finalParams = ensureOpenAIStreamUsage(compressedParams);
524
558
  ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
525
559
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
526
560
  const apiPromise = originalStream(finalParams, request);
527
561
  const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
528
562
  ensureAsyncIterable(rawStream, "chat.completions.stream");
529
- const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
530
- const usage = await extractUsageFromStream(
531
- rawStream,
563
+ const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async (termination) => {
564
+ try {
565
+ if (termination === "complete") {
566
+ const usage = await extractUsageFromStream(
567
+ rawStream,
568
+ ctx.begin.data.vendorHints,
569
+ provider
570
+ );
571
+ if (usage) {
572
+ ctx.setUsage(usage);
573
+ }
574
+ }
575
+ } catch (error) {
576
+ ctx.setError({
577
+ code: "USAGE_FINALIZE_ERROR",
578
+ message: error instanceof Error ? error.message : String(error)
579
+ });
580
+ throw error;
581
+ } finally {
582
+ await settle();
583
+ }
584
+ }, ctx, (chunk) => {
585
+ tryInferUsageFromStreamChunk(
586
+ chunk,
532
587
  ctx.begin.data.vendorHints,
588
+ ctx,
533
589
  provider
534
590
  );
535
- if (usage) {
536
- ctx.setUsage(usage);
537
- }
538
- }, ctx);
591
+ });
539
592
  return wrappedStreamInner;
540
593
  });
541
594
  return wrappedPromise;
@@ -555,70 +608,19 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
555
608
  return new Proxy(resource, handler);
556
609
  }
557
610
  async function compressChatParamsForCall(args) {
558
- let compression = resolveEffectivePromptCompressionOptions(
611
+ const compression = resolveEffectivePromptCompressionOptions(
559
612
  args.defaultPromptCompression,
560
613
  args.callPromptCompression
561
614
  );
562
- const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
563
- const policyDriven = !compression && Boolean(runtimePolicy);
564
- if (policyDriven && runtimePolicy) {
565
- if (!runtimePolicy.selected) {
566
- args.ctx.setUsage({
567
- runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
568
- });
569
- return args.params;
570
- }
571
- compression = openAIPolicyCompressionOptions(runtimePolicy);
572
- if (runtimePolicy.mode === "SHADOW") {
573
- const startedAt2 = Date.now();
574
- void compressChatParams(
575
- args.params,
576
- args.usageTap,
577
- compression,
578
- args.withUsage?.signal
579
- ).then((outcome2) => {
580
- args.ctx.setUsage({
581
- runtimeCompression: openAIRuntimeMeasurement({
582
- policy: runtimePolicy,
583
- telemetry: buildPromptCompressionTelemetry(outcome2.segments),
584
- latencyMs: Date.now() - startedAt2,
585
- shadow: true,
586
- originalPayload: args.params,
587
- resultPayload: outcome2.params
588
- })
589
- });
590
- }).catch(() => {
591
- args.ctx.setUsage({
592
- runtimeCompression: failedRuntimeMeasurement(
593
- runtimePolicy,
594
- Date.now() - startedAt2
595
- )
596
- });
597
- });
598
- return args.params;
599
- }
600
- }
601
615
  if (!compression) {
602
616
  return args.params;
603
617
  }
604
- const startedAt = Date.now();
605
618
  const outcome = await compressChatParams(
606
619
  args.params,
607
620
  args.usageTap,
608
621
  compression,
609
622
  args.withUsage?.signal
610
623
  );
611
- if (policyDriven && runtimePolicy) {
612
- const runtimeMeasurement = openAIRuntimeMeasurement({
613
- policy: runtimePolicy,
614
- telemetry: buildPromptCompressionTelemetry(outcome.segments),
615
- latencyMs: Date.now() - startedAt,
616
- originalPayload: args.params,
617
- resultPayload: outcome.params
618
- });
619
- args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
620
- if (runtimeMeasurement.decision !== "compressed") return args.params;
621
- }
622
624
  await recordCompressionOutcome({
623
625
  outcome,
624
626
  compression,
@@ -631,70 +633,19 @@ async function compressChatParamsForCall(args) {
631
633
  return outcome.params;
632
634
  }
633
635
  async function compressResponsesParamsForCall(args) {
634
- let compression = resolveEffectivePromptCompressionOptions(
636
+ const compression = resolveEffectivePromptCompressionOptions(
635
637
  args.defaultPromptCompression,
636
638
  args.callPromptCompression
637
639
  );
638
- const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
639
- const policyDriven = !compression && Boolean(runtimePolicy);
640
- if (policyDriven && runtimePolicy) {
641
- if (!runtimePolicy.selected) {
642
- args.ctx.setUsage({
643
- runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
644
- });
645
- return args.params;
646
- }
647
- compression = openAIPolicyCompressionOptions(runtimePolicy);
648
- if (runtimePolicy.mode === "SHADOW") {
649
- const startedAt2 = Date.now();
650
- void compressResponsesParams(
651
- args.params,
652
- args.usageTap,
653
- compression,
654
- args.withUsage?.signal
655
- ).then((outcome2) => {
656
- args.ctx.setUsage({
657
- runtimeCompression: openAIRuntimeMeasurement({
658
- policy: runtimePolicy,
659
- telemetry: buildPromptCompressionTelemetry(outcome2.segments),
660
- latencyMs: Date.now() - startedAt2,
661
- shadow: true,
662
- originalPayload: args.params,
663
- resultPayload: outcome2.params
664
- })
665
- });
666
- }).catch(() => {
667
- args.ctx.setUsage({
668
- runtimeCompression: failedRuntimeMeasurement(
669
- runtimePolicy,
670
- Date.now() - startedAt2
671
- )
672
- });
673
- });
674
- return args.params;
675
- }
676
- }
677
640
  if (!compression) {
678
641
  return args.params;
679
642
  }
680
- const startedAt = Date.now();
681
643
  const outcome = await compressResponsesParams(
682
644
  args.params,
683
645
  args.usageTap,
684
646
  compression,
685
647
  args.withUsage?.signal
686
648
  );
687
- if (policyDriven && runtimePolicy) {
688
- const runtimeMeasurement = openAIRuntimeMeasurement({
689
- policy: runtimePolicy,
690
- telemetry: buildPromptCompressionTelemetry(outcome.segments),
691
- latencyMs: Date.now() - startedAt,
692
- originalPayload: args.params,
693
- resultPayload: outcome.params
694
- });
695
- args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
696
- if (runtimeMeasurement.decision !== "compressed") return args.params;
697
- }
698
649
  await recordCompressionOutcome({
699
650
  outcome,
700
651
  compression,
@@ -706,79 +657,6 @@ async function compressResponsesParamsForCall(args) {
706
657
  });
707
658
  return outcome.params;
708
659
  }
709
- function openAIPolicyCompressionOptions(policy) {
710
- const roles = Object.fromEntries(
711
- ["system", "user", "tool", "assistant"].filter((role) => policy.snapshot.scope.messageRoles.includes(role)).map((role) => [role, true])
712
- );
713
- return {
714
- provider: "usagetap",
715
- roles,
716
- minContextTokens: policy.snapshot.scope.minimumTokens,
717
- failOpen: true
718
- };
719
- }
720
- function bypassedRuntimeMeasurement(policy) {
721
- return {
722
- policyId: policy.policyId,
723
- policyVersionId: policy.policyVersionId,
724
- policyVersion: policy.version,
725
- rolloutMode: policy.mode,
726
- decision: "bypassed",
727
- reasonCode: "not_in_rollout_sample",
728
- methodsAttempted: [],
729
- methodsApplied: [],
730
- originalTokens: 0,
731
- resultTokens: 0,
732
- savedTokens: 0,
733
- reductionPercentage: 0,
734
- compressionLatencyMs: 0,
735
- compressionComputeCostUsd: 0,
736
- financialsEstimated: true
737
- };
738
- }
739
- function failedRuntimeMeasurement(policy, latencyMs) {
740
- return {
741
- ...bypassedRuntimeMeasurement(policy),
742
- decision: "fallback",
743
- reasonCode: "transformation_failed",
744
- methodsAttempted: ["deterministic"],
745
- compressionLatencyMs: latencyMs
746
- };
747
- }
748
- function openAIRuntimeMeasurement(input) {
749
- const telemetry = input.telemetry;
750
- const originalTokens = telemetry?.originalTokens ?? estimatePromptTokens(JSON.stringify(input.originalPayload));
751
- const resultTokens = telemetry?.compressedTokens ?? originalTokens;
752
- const savedTokens = Math.max(0, telemetry?.savedTokens ?? 0);
753
- const reductionPercentage = originalTokens > 0 ? savedTokens / originalTokens * 100 : 0;
754
- const belowMinimumSize = originalTokens < input.policy.snapshot.scope.minimumTokens;
755
- const latencyExceeded = input.latencyMs > input.policy.snapshot.gates.maximumLatencyMs;
756
- const belowSavings = savedTokens < input.policy.snapshot.gates.minimumTokensRemoved || reductionPercentage < input.policy.snapshot.gates.minimumReductionPercentage;
757
- const accepted = !belowMinimumSize && !latencyExceeded && !belowSavings;
758
- const deepText = input.policy.snapshot.methods.deepText.enabled && !input.policy.snapshot.rollout.deterministicOnly && (input.policy.mode !== "CANARY" || input.policy.snapshot.rollout.deepTextCanaryAllowed);
759
- const reasonCode = belowMinimumSize ? "below_minimum_size" : latencyExceeded ? "latency_budget_exceeded" : belowSavings ? "below_minimum_savings" : void 0;
760
- const measurement = {
761
- policyId: input.policy.policyId,
762
- policyVersionId: input.policy.policyVersionId,
763
- policyVersion: input.policy.version,
764
- rolloutMode: input.policy.mode,
765
- decision: accepted ? input.shadow ? "bypassed" : "compressed" : latencyExceeded ? "fallback" : "skipped",
766
- ...reasonCode ? { reasonCode } : {},
767
- methodsAttempted: [
768
- "deterministic",
769
- ...deepText ? ["deep_text"] : []
770
- ],
771
- methodsApplied: accepted ? ["deterministic"] : [],
772
- originalTokens,
773
- resultTokens: accepted ? resultTokens : originalTokens,
774
- savedTokens: accepted ? savedTokens : 0,
775
- reductionPercentage: accepted ? reductionPercentage : 0,
776
- compressionLatencyMs: input.latencyMs,
777
- compressionComputeCostUsd: 0,
778
- financialsEstimated: true
779
- };
780
- return measurement;
781
- }
782
660
  async function recordCompressionOutcome(args) {
783
661
  const telemetry = buildPromptCompressionTelemetry(args.outcome.segments);
784
662
  if (!telemetry) {
@@ -1301,10 +1179,6 @@ function resolveBeginRequest(defaults, override) {
1301
1179
  }
1302
1180
  const tags = mergeTags(base.tags, current.tags);
1303
1181
  const begin = { customerId };
1304
- const runtimeCompressionContext = current.runtimeCompressionContext ?? base.runtimeCompressionContext;
1305
- if (runtimeCompressionContext) {
1306
- begin.runtimeCompressionContext = runtimeCompressionContext;
1307
- }
1308
1182
  const requested = current.requested ?? base.requested;
1309
1183
  if (requested) begin.requested = requested;
1310
1184
  const feature = current.feature ?? base.feature;
@@ -1325,8 +1199,6 @@ function resolveBeginRequest(defaults, override) {
1325
1199
  if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
1326
1200
  const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
1327
1201
  if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
1328
- const holdUsd = current.holdUsd ?? base.holdUsd;
1329
- if (typeof holdUsd === "number") begin.holdUsd = holdUsd;
1330
1202
  const batch = current.batch ?? base.batch;
1331
1203
  if (typeof batch === "boolean") begin.batch = batch;
1332
1204
  const pricingMode = current.pricingMode ?? base.pricingMode;
@@ -1336,16 +1208,25 @@ function resolveBeginRequest(defaults, override) {
1336
1208
  }
1337
1209
  return begin;
1338
1210
  }
1339
- function withRuntimeCompressionContext(begin, provider, model) {
1211
+ function responsesBeginRequest(begin, params) {
1212
+ if (!responsesRequestUsesWebSearch(params)) return begin;
1340
1213
  return {
1341
1214
  ...begin,
1342
- runtimeCompressionContext: {
1343
- ...begin.runtimeCompressionContext ?? {},
1344
- provider,
1345
- model
1215
+ requested: {
1216
+ ...begin.requested ?? {},
1217
+ search: true
1346
1218
  }
1347
1219
  };
1348
1220
  }
1221
+ function responsesRequestUsesWebSearch(params) {
1222
+ if (!params || typeof params !== "object") return false;
1223
+ const tools = params.tools;
1224
+ return Array.isArray(tools) && tools.some((tool) => {
1225
+ if (!tool || typeof tool !== "object") return false;
1226
+ const type = tool.type;
1227
+ return type === "web_search" || type === "web_search_preview";
1228
+ });
1229
+ }
1349
1230
  function transformApiPromise(apiPromise, onResolve, onReject) {
1350
1231
  const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
1351
1232
  if (isObjectRecord(apiPromise)) {
@@ -1575,6 +1456,11 @@ function inferUsageFromResponse(response, hints, provider = "openai") {
1575
1456
  return void 0;
1576
1457
  }
1577
1458
  const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
1459
+ const cacheWriteInputTokens = candidate.usage.prompt_tokens_details?.cache_write_tokens ?? candidate.usage.prompt_tokens_details?.cache_creation_tokens ?? candidate.usage.input_tokens_details?.cache_write_tokens ?? candidate.usage.input_tokens_details?.cache_creation_tokens ?? candidate.usage.cache_creation_input_tokens ?? candidate.usage.cache_write_input_tokens ?? candidate.usage.cache_write_tokens;
1460
+ const cacheWrite5mInputTokens = candidate.usage.cache_write_5m_input_tokens ?? candidate.usage.cache_creation?.ephemeral_5m_input_tokens;
1461
+ const cacheWrite1hInputTokens = candidate.usage.cache_write_1h_input_tokens ?? candidate.usage.cache_creation?.ephemeral_1h_input_tokens;
1462
+ const outputSearches = Array.isArray(candidate.output) ? candidate.output.filter((item) => item?.type === "web_search_call").length : 0;
1463
+ const searches = candidate.usage.searches ?? candidate.usage.web_search_queries ?? candidate.usage.server_tool_use?.web_search_requests ?? outputSearches;
1578
1464
  const responseEffort = normalizeExecutionReasoningEffort(
1579
1465
  candidate.reasoning?.effort
1580
1466
  );
@@ -1584,12 +1470,21 @@ function inferUsageFromResponse(response, hints, provider = "openai") {
1584
1470
  inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
1585
1471
  responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
1586
1472
  cachedInputTokens,
1473
+ cacheWriteInputTokens,
1474
+ cacheWrite5mInputTokens,
1475
+ cacheWrite1hInputTokens,
1476
+ audioInputTokens: candidate.usage.prompt_tokens_details?.audio_tokens ?? candidate.usage.input_tokens_details?.audio_tokens,
1477
+ cachedAudioInputTokens: candidate.usage.prompt_tokens_details?.cached_audio_tokens ?? candidate.usage.input_tokens_details?.cached_audio_tokens ?? candidate.usage.prompt_tokens_details?.cached_tokens_details?.audio_tokens ?? candidate.usage.input_tokens_details?.cached_tokens_details?.audio_tokens,
1478
+ imageInputTokens: candidate.usage.prompt_tokens_details?.image_tokens ?? candidate.usage.input_tokens_details?.image_tokens,
1479
+ imageOutputTokens: candidate.usage.completion_tokens_details?.image_tokens ?? candidate.usage.output_tokens_details?.image_tokens,
1480
+ audioOutputTokens: candidate.usage.completion_tokens_details?.audio_tokens ?? candidate.usage.output_tokens_details?.audio_tokens,
1587
1481
  reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
1588
1482
  ...responseEffort ? {
1589
1483
  reasoningEffort: responseEffort,
1590
1484
  reasoningEffortSource: "provider_response"
1591
1485
  } : {},
1592
- ...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {}
1486
+ ...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {},
1487
+ ...typeof searches === "number" && searches > 0 ? { searches } : {}
1593
1488
  };
1594
1489
  }
1595
1490
  function normalizeExecutionReasoningEffort(value) {
@@ -1606,29 +1501,64 @@ function openAIRequestExecutionMetadata(params, provider) {
1606
1501
  const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
1607
1502
  return {
1608
1503
  providerUsed: provider,
1504
+ ...typeof record.model === "string" ? { modelUsed: record.model } : {},
1609
1505
  ...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
1610
1506
  ...mode ? { reasoningMode: mode } : {},
1611
1507
  ...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
1612
1508
  };
1613
1509
  }
1614
- function wrapStreamForUsageTap(source, finalize, ctx) {
1510
+ function tryInferUsageFromStreamChunk(chunk, hints, ctx, provider) {
1511
+ const payload = isObjectRecord(chunk) && isObjectRecord(chunk.response) ? chunk.response : chunk;
1512
+ const inferred = inferUsageFromResponse(payload, hints, provider);
1513
+ if (inferred) {
1514
+ ctx.setUsage(inferred);
1515
+ }
1516
+ }
1517
+ function ensureOpenAIStreamUsage(params) {
1518
+ if (!isObjectRecord(params)) {
1519
+ return params;
1520
+ }
1521
+ const streamOptions = isObjectRecord(params.stream_options) ? params.stream_options : {};
1522
+ return {
1523
+ ...params,
1524
+ stream_options: {
1525
+ ...streamOptions,
1526
+ include_usage: true
1527
+ }
1528
+ };
1529
+ }
1530
+ function deferUsageFinalization(ctx) {
1531
+ return ctx.deferFinalization?.() ?? (() => Promise.resolve());
1532
+ }
1533
+ function wrapStreamForUsageTap(source, finalize, ctx, onChunk) {
1615
1534
  const getIterator = source[Symbol.asyncIterator];
1616
1535
  if (typeof getIterator !== "function") {
1617
1536
  throw new TypeError("Stream is not async iterable");
1618
1537
  }
1619
1538
  const iterator = getIterator.call(source);
1620
1539
  let completed = false;
1621
- const invokeFinalize = async () => {
1540
+ const invokeFinalize = async (termination, error) => {
1622
1541
  if (completed) return;
1623
1542
  completed = true;
1543
+ if (termination === "cancel" || termination === "manual") {
1544
+ ctx.setError({
1545
+ code: "STREAM_ABORTED",
1546
+ message: termination === "cancel" ? "Provider stream consumption was cancelled before completion" : "Provider stream was finalized before completion"
1547
+ });
1548
+ } else if (termination === "error") {
1549
+ ctx.setError({
1550
+ code: "VENDOR_ERROR",
1551
+ message: error instanceof Error ? error.message : String(error)
1552
+ });
1553
+ }
1624
1554
  try {
1625
- await finalize();
1626
- } catch (error) {
1555
+ await finalize(termination);
1556
+ } catch (error2) {
1627
1557
  ctx.setError({
1628
1558
  code: "USAGE_FINALIZE_ERROR",
1629
- message: error instanceof Error ? error.message : String(error)
1559
+ message: error2 instanceof Error ? error2.message : String(error2)
1630
1560
  });
1631
- throw error;
1561
+ throw error2;
1632
1562
  }
1633
1563
  };
1634
1564
  const prototype = Object.getPrototypeOf(source) ?? Object.prototype;
@@ -1652,12 +1582,15 @@ function wrapStreamForUsageTap(source, finalize, ctx) {
1652
1582
  value: async (...args) => {
1653
1583
  try {
1654
1584
  const result = await iterator.next(...args);
1585
+ if (!result.done) {
1586
+ onChunk?.(result.value);
1587
+ }
1655
1588
  if (result.done) {
1656
- await invokeFinalize();
1589
+ await invokeFinalize("complete");
1657
1590
  }
1658
1591
  return result;
1659
1592
  } catch (error) {
1660
- await invokeFinalize().catch(() => void 0);
1593
+ await invokeFinalize("error", error).catch(() => void 0);
1661
1594
  throw error;
1662
1595
  }
1663
1596
  },
@@ -1666,39 +1599,49 @@ function wrapStreamForUsageTap(source, finalize, ctx) {
1666
1599
  });
1667
1600
  Object.defineProperty(wrapped, "return", {
1668
1601
  value: async (value) => {
1669
- if (typeof iterator.return === "function") {
1670
- const rawResult = await iterator.return(value);
1671
- if (!isIteratorResult(rawResult)) {
1672
- throw new TypeError("Iterator.return() returned an invalid result");
1602
+ try {
1603
+ if (typeof iterator.return === "function") {
1604
+ const rawResult = await iterator.return(value);
1605
+ if (!isIteratorResult(rawResult)) {
1606
+ throw new TypeError("Iterator.return() returned an invalid result");
1607
+ }
1608
+ await invokeFinalize("cancel");
1609
+ return rawResult;
1673
1610
  }
1674
- await invokeFinalize();
1675
- return rawResult;
1611
+ await invokeFinalize("cancel");
1612
+ return { done: true, value };
1613
+ } catch (error) {
1614
+ await invokeFinalize("error", error).catch(() => void 0);
1615
+ throw error;
1676
1616
  }
1677
- await invokeFinalize();
1678
- return { done: true, value };
1679
1617
  },
1680
1618
  configurable: true,
1681
1619
  writable: true
1682
1620
  });
1683
1621
  Object.defineProperty(wrapped, "throw", {
1684
1622
  value: async (error) => {
1685
- if (typeof iterator.throw === "function") {
1686
- const rawResult = await iterator.throw(error);
1687
- if (!isIteratorResult(rawResult)) {
1688
- throw new TypeError("Iterator.throw() returned an invalid result");
1623
+ try {
1624
+ if (typeof iterator.throw === "function") {
1625
+ const rawResult = await iterator.throw(error);
1626
+ if (!isIteratorResult(rawResult)) {
1627
+ throw new TypeError("Iterator.throw() returned an invalid result");
1628
+ }
1629
+ await invokeFinalize("error", error);
1630
+ return rawResult;
1689
1631
  }
1690
- await invokeFinalize();
1691
- return rawResult;
1632
+ await invokeFinalize("error", error);
1633
+ throw error;
1634
+ } catch (thrownError) {
1635
+ await invokeFinalize("error", thrownError).catch(() => void 0);
1636
+ throw thrownError;
1692
1637
  }
1693
- await invokeFinalize();
1694
- throw error;
1695
1638
  },
1696
1639
  configurable: true,
1697
1640
  writable: true
1698
1641
  });
1699
1642
  Object.defineProperty(wrapped, "__usageTapFinalize", {
1700
1643
  value: async () => {
1701
- await invokeFinalize();
1644
+ await invokeFinalize("manual");
1702
1645
  },
1703
1646
  configurable: true
1704
1647
  });