@usagetap/sdk 1.3.2 → 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 (60) hide show
  1. package/README.md +372 -39
  2. package/dist/adapters/anthropic.cjs +995 -69
  3. package/dist/adapters/anthropic.cjs.map +1 -1
  4. package/dist/adapters/anthropic.d.cts +45 -3
  5. package/dist/adapters/anthropic.d.ts +45 -3
  6. package/dist/adapters/anthropic.mjs +995 -70
  7. package/dist/adapters/anthropic.mjs.map +1 -1
  8. package/dist/adapters/openai.cjs +1208 -106
  9. package/dist/adapters/openai.cjs.map +1 -1
  10. package/dist/adapters/openai.d.cts +46 -3
  11. package/dist/adapters/openai.d.ts +46 -3
  12. package/dist/adapters/openai.mjs +1208 -107
  13. package/dist/adapters/openai.mjs.map +1 -1
  14. package/dist/adapters/openrouter.cjs +3912 -53
  15. package/dist/adapters/openrouter.cjs.map +1 -1
  16. package/dist/adapters/openrouter.d.cts +6 -3
  17. package/dist/adapters/openrouter.d.ts +6 -3
  18. package/dist/adapters/openrouter.mjs +3910 -54
  19. package/dist/adapters/openrouter.mjs.map +1 -1
  20. package/dist/anthropic/index.cjs +995 -69
  21. package/dist/anthropic/index.cjs.map +1 -1
  22. package/dist/anthropic/index.d.cts +2 -2
  23. package/dist/anthropic/index.d.ts +2 -2
  24. package/dist/anthropic/index.mjs +995 -70
  25. package/dist/anthropic/index.mjs.map +1 -1
  26. package/dist/client-C0UiaqVB.d.cts +1305 -0
  27. package/dist/client-C0UiaqVB.d.ts +1305 -0
  28. package/dist/express/index.cjs +399 -64
  29. package/dist/express/index.cjs.map +1 -1
  30. package/dist/express/index.d.cts +2 -2
  31. package/dist/express/index.d.ts +2 -2
  32. package/dist/express/index.mjs +399 -64
  33. package/dist/express/index.mjs.map +1 -1
  34. package/dist/index.cjs +1044 -163
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +16 -5
  37. package/dist/index.d.ts +16 -5
  38. package/dist/index.mjs +1044 -163
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/openai/index.cjs +1209 -107
  41. package/dist/openai/index.cjs.map +1 -1
  42. package/dist/openai/index.d.cts +2 -2
  43. package/dist/openai/index.d.ts +2 -2
  44. package/dist/openai/index.mjs +1209 -108
  45. package/dist/openai/index.mjs.map +1 -1
  46. package/dist/openrouter/index.cjs +1226 -109
  47. package/dist/openrouter/index.cjs.map +1 -1
  48. package/dist/openrouter/index.d.cts +3 -3
  49. package/dist/openrouter/index.d.ts +3 -3
  50. package/dist/openrouter/index.mjs +1224 -108
  51. package/dist/openrouter/index.mjs.map +1 -1
  52. package/dist/react/index.cjs +19 -1
  53. package/dist/react/index.cjs.map +1 -1
  54. package/dist/react/index.d.cts +17 -4
  55. package/dist/react/index.d.ts +17 -4
  56. package/dist/react/index.mjs +19 -1
  57. package/dist/react/index.mjs.map +1 -1
  58. package/package.json +2 -2
  59. package/dist/client-BD8O2J8Z.d.cts +0 -668
  60. package/dist/client-BD8O2J8Z.d.ts +0 -668
@@ -242,6 +242,11 @@ async function compressMessagesWithUsageTap(options) {
242
242
  aggressiveness,
243
243
  "UsageTap prompt message compression"
244
244
  );
245
+ if (options.latencyBudgetMs !== void 0 && (!Number.isFinite(options.latencyBudgetMs) || options.latencyBudgetMs < 0)) {
246
+ throw new Error(
247
+ "UsageTap prompt message compression latencyBudgetMs must be a non-negative number"
248
+ );
249
+ }
245
250
  const original = stableStringifyInput(options.input);
246
251
  const headers = {
247
252
  "content-type": "application/json"
@@ -256,7 +261,15 @@ async function compressMessagesWithUsageTap(options) {
256
261
  headers,
257
262
  body: JSON.stringify({
258
263
  ...cloneInputRecord(options.input),
259
- compression_settings: { aggressiveness }
264
+ compression_settings: {
265
+ aggressiveness,
266
+ ...options.mode === void 0 ? {} : { mode: options.mode },
267
+ ...options.latencyBudgetMs === void 0 ? {} : { latency_budget_ms: options.latencyBudgetMs },
268
+ ...options.compactEmptyUserMessages === void 0 ? {} : { compact_empty_user_messages: options.compactEmptyUserMessages },
269
+ ...options.compactDuplicateUserTextParts === void 0 ? {} : {
270
+ compact_duplicate_user_text_parts: options.compactDuplicateUserTextParts
271
+ }
272
+ }
260
273
  }),
261
274
  signal: options.signal
262
275
  }
@@ -752,10 +765,369 @@ function scalarToToon(value) {
752
765
  return JSON.stringify(text);
753
766
  }
754
767
 
768
+ // src/resources.ts
769
+ var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
770
+ var DEFAULT_GATEWAY_BASE_URL = "https://gateway.usagetap.com";
771
+ function normalizedBaseUrl(value) {
772
+ return `${value.replace(/\/+$/, "")}/`;
773
+ }
774
+ function errorMessage(payload, status) {
775
+ if (payload && typeof payload === "object") {
776
+ const record = payload;
777
+ const error = record.error;
778
+ if (error && typeof error === "object") {
779
+ const message2 = error.message;
780
+ if (typeof message2 === "string" && message2) return message2;
781
+ }
782
+ const message = record.message;
783
+ if (typeof message === "string" && message) return message;
784
+ }
785
+ return `UsageTap request failed with HTTP ${status}`;
786
+ }
787
+ function errorCode(status) {
788
+ if (status === 401 || status === 403) return "USAGETAP_AUTH_ERROR";
789
+ if (status === 429) return "USAGETAP_RATE_LIMITED";
790
+ if (status >= 500) return "USAGETAP_SERVER_ERROR";
791
+ return "USAGETAP_BAD_REQUEST";
792
+ }
793
+ var ResourceTransport = class {
794
+ baseUrl;
795
+ apiKey;
796
+ fetchImpl;
797
+ defaultHeaders;
798
+ sdkVersion;
799
+ constructor(baseUrl, config) {
800
+ this.baseUrl = normalizedBaseUrl(baseUrl);
801
+ this.apiKey = config.apiKey;
802
+ this.fetchImpl = config.fetchImpl;
803
+ this.defaultHeaders = config.headers ?? {};
804
+ this.sdkVersion = config.sdkVersion;
805
+ }
806
+ async request(request) {
807
+ const body = request.body === void 0 ? void 0 : JSON.stringify(request.body);
808
+ const headers = {
809
+ ...this.defaultHeaders,
810
+ accept: request.response === "data" ? CANONICAL_MEDIA_TYPE : request.response === "ndjson" ? "application/x-ndjson" : "application/json",
811
+ authorization: `Bearer ${this.apiKey}`,
812
+ "x-usage-sdk": `js/${this.sdkVersion}`,
813
+ ...body ? { "content-type": "application/json" } : {},
814
+ ...request.options?.idempotencyKey ? { "idempotency-key": request.options.idempotencyKey } : {},
815
+ ...request.options?.headers
816
+ };
817
+ let response;
818
+ try {
819
+ response = await this.fetchImpl(
820
+ new URL(request.path.replace(/^\/+/, ""), this.baseUrl),
821
+ {
822
+ method: request.method,
823
+ headers,
824
+ body,
825
+ signal: request.options?.signal
826
+ }
827
+ );
828
+ } catch (error) {
829
+ throw new UsageTapError(
830
+ "USAGETAP_NETWORK_ERROR",
831
+ "Failed to reach UsageTap",
832
+ { retryable: true, cause: error }
833
+ );
834
+ }
835
+ const requestId = response.headers.get("x-request-id") ?? response.headers.get("x-usage-correlation-id") ?? void 0;
836
+ const text = await response.text();
837
+ let payload;
838
+ if (text && request.response !== "ndjson") {
839
+ try {
840
+ payload = JSON.parse(text);
841
+ } catch (error) {
842
+ throw new UsageTapError(
843
+ "USAGETAP_INVALID_RESPONSE",
844
+ "UsageTap returned invalid JSON",
845
+ { status: response.status, correlationId: requestId, cause: error }
846
+ );
847
+ }
848
+ }
849
+ if (!response.ok) {
850
+ throw new UsageTapError(
851
+ errorCode(response.status),
852
+ errorMessage(payload, response.status),
853
+ {
854
+ status: response.status,
855
+ retryable: response.status === 429 || response.status >= 500,
856
+ correlationId: requestId,
857
+ details: payload && typeof payload === "object" ? payload : void 0
858
+ }
859
+ );
860
+ }
861
+ if (request.response === "ndjson") {
862
+ if (!text.trim()) return [];
863
+ try {
864
+ return text.trim().split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
865
+ } catch (error) {
866
+ throw new UsageTapError(
867
+ "USAGETAP_INVALID_RESPONSE",
868
+ "UsageTap returned invalid NDJSON",
869
+ { status: response.status, correlationId: requestId, cause: error }
870
+ );
871
+ }
872
+ }
873
+ if (request.response === "data") {
874
+ if (!payload || typeof payload !== "object" || !("data" in payload)) {
875
+ throw new UsageTapError(
876
+ "USAGETAP_INVALID_RESPONSE",
877
+ "UsageTap response missing data",
878
+ { status: response.status, correlationId: requestId }
879
+ );
880
+ }
881
+ return payload.data;
882
+ }
883
+ if (payload === void 0) {
884
+ throw new UsageTapError(
885
+ "USAGETAP_INVALID_RESPONSE",
886
+ "UsageTap response was empty",
887
+ { status: response.status, correlationId: requestId }
888
+ );
889
+ }
890
+ return payload;
891
+ }
892
+ };
893
+ function resourceId(value, keys, label) {
894
+ const id = typeof value === "string" ? value : keys.map((key) => value[key]).find((candidate) => Boolean(candidate?.trim()));
895
+ if (!id?.trim()) {
896
+ throw new UsageTapError(
897
+ "USAGETAP_BAD_REQUEST",
898
+ `${label} requires a non-empty ID`
899
+ );
900
+ }
901
+ return id.trim();
902
+ }
903
+ function terminalSummary(status) {
904
+ return status === "COMPLETE" || status === "FAILED";
905
+ }
906
+ function terminalGatewayBatch(status) {
907
+ return ["completed", "failed", "expired", "cancelled"].includes(status);
908
+ }
909
+ function validateWaitOptions(options) {
910
+ const pollIntervalMs = options.pollIntervalMs ?? 1500;
911
+ const timeoutMs = options.timeoutMs ?? 3 * 6e4;
912
+ if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 0) {
913
+ throw new UsageTapError(
914
+ "USAGETAP_BAD_REQUEST",
915
+ "pollIntervalMs must be a non-negative number"
916
+ );
917
+ }
918
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
919
+ throw new UsageTapError(
920
+ "USAGETAP_BAD_REQUEST",
921
+ "timeoutMs must be a positive number"
922
+ );
923
+ }
924
+ return { pollIntervalMs, timeoutMs };
925
+ }
926
+ var SummarizationResource = class {
927
+ summaries;
928
+ batches;
929
+ profiles;
930
+ measurements;
931
+ transport;
932
+ constructor(config) {
933
+ this.transport = new ResourceTransport(config.apiBaseUrl, config);
934
+ this.summaries = {
935
+ create: (params, options) => this.transport.request({
936
+ method: "POST",
937
+ path: "/v1/compression/summaries",
938
+ body: params,
939
+ options,
940
+ response: "data"
941
+ }),
942
+ retrieve: (jobId, options) => this.transport.request({
943
+ method: "GET",
944
+ path: `/v1/compression/jobs/${encodeURIComponent(
945
+ resourceId(jobId, ["jobId"], "summaries.retrieve")
946
+ )}`,
947
+ options,
948
+ response: "data"
949
+ }),
950
+ wait: (job, options) => this.waitForSummary(job, options)
951
+ };
952
+ this.batches = {
953
+ create: (params, options) => this.transport.request({
954
+ method: "POST",
955
+ path: "/v1/compression/batches",
956
+ body: params,
957
+ options,
958
+ response: "data"
959
+ }),
960
+ retrieve: (batchId, options) => this.transport.request({
961
+ method: "GET",
962
+ path: `/v1/compression/batches/${encodeURIComponent(
963
+ resourceId(batchId, ["batchId"], "summarization.batches.retrieve")
964
+ )}`,
965
+ options,
966
+ response: "data"
967
+ }),
968
+ wait: (batch, options) => this.waitForBatch(batch, options)
969
+ };
970
+ this.profiles = {
971
+ retrieve: (profile, options) => this.transport.request({
972
+ method: "GET",
973
+ path: `/v1/compression/profiles/${encodeURIComponent(
974
+ resourceId(profile, [], "summarization.profiles.retrieve")
975
+ )}`,
976
+ options,
977
+ response: "data"
978
+ })
979
+ };
980
+ this.measurements = {
981
+ create: (params, options) => this.transport.request({
982
+ method: "POST",
983
+ path: "/v1/compression/measurements",
984
+ body: params,
985
+ options,
986
+ response: "data"
987
+ })
988
+ };
989
+ }
990
+ async waitForSummary(value, options = {}) {
991
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
992
+ const deadline = Date.now() + timeoutMs;
993
+ let job = typeof value === "string" ? await this.summaries.retrieve(value, options) : value;
994
+ while (!terminalSummary(job.status)) {
995
+ if (Date.now() >= deadline) {
996
+ throw new UsageTapError(
997
+ "USAGETAP_RETRY_EXHAUSTED",
998
+ `Summarization job ${job.jobId} did not finish before timeout`,
999
+ { retryable: true }
1000
+ );
1001
+ }
1002
+ await sleep(pollIntervalMs, options.signal);
1003
+ job = await this.summaries.retrieve(job.jobId, options);
1004
+ }
1005
+ return job;
1006
+ }
1007
+ async waitForBatch(value, options = {}) {
1008
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
1009
+ const deadline = Date.now() + timeoutMs;
1010
+ let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
1011
+ while (!terminalSummary(batch.status)) {
1012
+ if (Date.now() >= deadline) {
1013
+ throw new UsageTapError(
1014
+ "USAGETAP_RETRY_EXHAUSTED",
1015
+ `Summarization batch ${batch.batchId} did not finish before timeout`,
1016
+ { retryable: true }
1017
+ );
1018
+ }
1019
+ await sleep(pollIntervalMs, options.signal);
1020
+ batch = await this.batches.retrieve(batch.batchId, options);
1021
+ }
1022
+ return batch;
1023
+ }
1024
+ };
1025
+ var GatewayResource = class {
1026
+ chat;
1027
+ /** Buffered OpenAI Responses-compatible requests. */
1028
+ responses;
1029
+ models;
1030
+ batches;
1031
+ transport;
1032
+ idempotencyGenerator;
1033
+ constructor(config) {
1034
+ this.transport = new ResourceTransport(
1035
+ config.gatewayBaseUrl ?? DEFAULT_GATEWAY_BASE_URL,
1036
+ config
1037
+ );
1038
+ this.idempotencyGenerator = config.idempotencyGenerator ?? createIdempotencyKey;
1039
+ this.chat = {
1040
+ completions: {
1041
+ create: (params, options) => this.transport.request({
1042
+ method: "POST",
1043
+ path: "/v1/chat/completions",
1044
+ body: params,
1045
+ options,
1046
+ response: "json"
1047
+ })
1048
+ }
1049
+ };
1050
+ this.responses = {
1051
+ create: (params, options) => this.transport.request({
1052
+ method: "POST",
1053
+ path: "/v1/responses",
1054
+ body: params,
1055
+ options,
1056
+ response: "json"
1057
+ })
1058
+ };
1059
+ this.models = {
1060
+ list: (options) => this.transport.request({
1061
+ method: "GET",
1062
+ path: "/v1/models",
1063
+ options,
1064
+ response: "json"
1065
+ })
1066
+ };
1067
+ this.batches = {
1068
+ create: (params, options = {}) => this.transport.request({
1069
+ method: "POST",
1070
+ path: "/v1/batches",
1071
+ body: params,
1072
+ options: {
1073
+ ...options,
1074
+ idempotencyKey: options.idempotencyKey ?? this.idempotencyGenerator()
1075
+ },
1076
+ response: "json"
1077
+ }),
1078
+ retrieve: (batchId, options) => this.transport.request({
1079
+ method: "GET",
1080
+ path: `/v1/batches/${encodeURIComponent(
1081
+ resourceId(batchId, ["id"], "gateway.batches.retrieve")
1082
+ )}`,
1083
+ options,
1084
+ response: "json"
1085
+ }),
1086
+ wait: (batch, options) => this.waitForBatch(batch, options),
1087
+ cancel: (batchId, options) => this.transport.request({
1088
+ method: "POST",
1089
+ path: `/v1/batches/${encodeURIComponent(
1090
+ resourceId(batchId, ["id"], "gateway.batches.cancel")
1091
+ )}/cancel`,
1092
+ options,
1093
+ response: "json"
1094
+ }),
1095
+ results: (batchId, options) => this.transport.request({
1096
+ method: "GET",
1097
+ path: `/v1/batches/${encodeURIComponent(
1098
+ resourceId(batchId, ["id"], "gateway.batches.results")
1099
+ )}/results`,
1100
+ options,
1101
+ response: "ndjson"
1102
+ })
1103
+ };
1104
+ }
1105
+ async waitForBatch(value, options = {}) {
1106
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
1107
+ const deadline = Date.now() + timeoutMs;
1108
+ let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
1109
+ while (!terminalGatewayBatch(batch.status)) {
1110
+ if (Date.now() >= deadline) {
1111
+ throw new UsageTapError(
1112
+ "USAGETAP_RETRY_EXHAUSTED",
1113
+ `Gateway batch ${batch.id} did not finish before timeout`,
1114
+ { retryable: true }
1115
+ );
1116
+ }
1117
+ await sleep(pollIntervalMs, options.signal);
1118
+ batch = await this.batches.retrieve(batch.id, options);
1119
+ }
1120
+ return batch;
1121
+ }
1122
+ };
1123
+
755
1124
  // src/client.ts
756
1125
  var CALL_BEGIN_PATH = "call_begin";
757
1126
  var CALL_END_PATH = "call_end";
758
1127
  var COMPRESS_PROMPT_PATH = "compress_prompt";
1128
+ var SAMPLES_PATH = "samples";
1129
+ var SAMPLING_SETTINGS_PATH = "sampling/settings";
1130
+ var SAMPLING_DECIDE_PATH = "sampling/decide";
759
1131
  var CHECK_USAGE_PATH = "customers/{customerId}/usage";
760
1132
  var CREATE_CUSTOMER_PATH = "customers";
761
1133
  var CHANGE_PLAN_PATH = "customers/{customerId}/change_plan";
@@ -766,11 +1138,16 @@ var CORRELATION_HEADER = "x-usage-correlation-id";
766
1138
  var IDEMPOTENCY_HEADER = "idempotency-key";
767
1139
  var SDK_HEADER = "x-usage-sdk";
768
1140
  var USER_AGENT = "UsageTapClient";
769
- var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
1141
+ var CANONICAL_MEDIA_TYPE2 = "application/vnd.usagetap.v1+json";
770
1142
  var DEFAULT_BASE_URL = "https://api.usagetap.com";
771
- var SDK_VERSION = "1.3.2" ;
1143
+ var DEFAULT_RUN_INACTIVITY_MS = 60 * 60 * 1e3;
1144
+ var SDK_VERSION = "1.7.0" ;
772
1145
  var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
773
1146
  var UsageTapClient = class {
1147
+ /** OpenAI-compatible chat, model, and native batch operations. */
1148
+ gateway;
1149
+ /** Published-profile context summarization operations. */
1150
+ summarization;
774
1151
  apiKey;
775
1152
  baseUrl;
776
1153
  fetchImpl;
@@ -795,6 +1172,11 @@ var UsageTapClient = class {
795
1172
  usageTapCompressionMessagesEndpoint;
796
1173
  usageTapCompressionModel;
797
1174
  usageTapCompressionAggressiveness;
1175
+ sampling;
1176
+ samplingSettingsCacheMs;
1177
+ circuitBreaker;
1178
+ circuitBreakerRuns = /* @__PURE__ */ new Map();
1179
+ samplingSettingsCache;
798
1180
  constructor(options = {}) {
799
1181
  const apiKey = options.apiKey?.trim() || readEnvironmentVariable("USAGETAP_API_KEY");
800
1182
  const baseUrl = options.baseUrl?.trim() || readEnvironmentVariable("USAGETAP_BASE_URL") || DEFAULT_BASE_URL;
@@ -817,10 +1199,21 @@ var UsageTapClient = class {
817
1199
  "A global fetch implementation was not found. Pass fetchImpl in UsageTapClientOptions."
818
1200
  );
819
1201
  }
820
- const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
821
- this.baseUrl = new URL(normalizedBaseUrl);
1202
+ const normalizedBaseUrl2 = normalizeBaseUrl(baseUrl);
1203
+ this.baseUrl = new URL(normalizedBaseUrl2);
822
1204
  this.apiKey = apiKey;
823
1205
  this.fetchImpl = wrapFetchImplementation(fetchCandidate, !options.fetchImpl);
1206
+ const resourceConfig = {
1207
+ apiKey,
1208
+ apiBaseUrl: normalizedBaseUrl2,
1209
+ gatewayBaseUrl: options.gatewayBaseUrl?.trim() || readEnvironmentVariable("USAGETAP_GATEWAY_URL"),
1210
+ fetchImpl: this.fetchImpl,
1211
+ headers: options.headers,
1212
+ sdkVersion: SDK_VERSION,
1213
+ idempotencyGenerator: options.idempotencyGenerator
1214
+ };
1215
+ this.gateway = new GatewayResource(resourceConfig);
1216
+ this.summarization = new SummarizationResource(resourceConfig);
824
1217
  this.defaultFeature = options.defaultFeature;
825
1218
  this.defaultTags = options.defaultTags?.length ? dedupeStrings(options.defaultTags) : void 0;
826
1219
  this.defaultHeaders = options.headers ? normalizeHeaderDictionary(options.headers) : {};
@@ -842,11 +1235,122 @@ var UsageTapClient = class {
842
1235
  this.usageTapCompressionMessagesEndpoint = options.usageTapCompressionMessagesEndpoint;
843
1236
  this.usageTapCompressionModel = options.usageTapCompressionModel;
844
1237
  this.usageTapCompressionAggressiveness = options.usageTapCompressionAggressiveness;
1238
+ this.sampling = options.sampling;
1239
+ this.samplingSettingsCacheMs = Number.isFinite(options.samplingSettingsCacheMs) ? Math.max(0, Number(options.samplingSettingsCacheMs)) : 5 * 60 * 1e3;
1240
+ if (options.circuitBreaker) {
1241
+ const maxCallsPerRun = options.circuitBreaker.maxCallsPerRun;
1242
+ if (!Number.isInteger(maxCallsPerRun) || maxCallsPerRun < 1) {
1243
+ throw new UsageTapError(
1244
+ "USAGETAP_BAD_REQUEST",
1245
+ "circuitBreaker.maxCallsPerRun must be a positive integer"
1246
+ );
1247
+ }
1248
+ const runInactivityMs = options.circuitBreaker.runInactivityMs ?? DEFAULT_RUN_INACTIVITY_MS;
1249
+ if (!Number.isFinite(runInactivityMs) || runInactivityMs < 1) {
1250
+ throw new UsageTapError(
1251
+ "USAGETAP_BAD_REQUEST",
1252
+ "circuitBreaker.runInactivityMs must be a positive number"
1253
+ );
1254
+ }
1255
+ this.circuitBreaker = {
1256
+ maxCallsPerRun,
1257
+ runInactivityMs
1258
+ };
1259
+ }
1260
+ }
1261
+ shouldSample(request, policy = this.sampling || void 0) {
1262
+ if (!policy) return false;
1263
+ const rate = Math.min(1, Math.max(0, Number(policy.rate) || 0));
1264
+ if (rate <= 0) return false;
1265
+ const customerId = request.customerId?.trim();
1266
+ if (customerId && policy.customers?.exclude?.includes(customerId)) return false;
1267
+ const feature = request.feature?.trim();
1268
+ if (feature && policy.features?.exclude?.includes(feature)) return false;
1269
+ const included = policy.features?.include?.filter(Boolean) ?? [];
1270
+ if (included.length > 0 && (!feature || !included.includes(feature))) return false;
1271
+ const minimum = Math.max(0, Math.round(policy.minInputTokens ?? 0));
1272
+ if (minimum > 0 && estimatePromptTokens(request.input) < minimum) return false;
1273
+ return (policy.random ?? Math.random)() < rate;
1274
+ }
1275
+ async getSamplingSettings(options = {}) {
1276
+ const now = Date.now();
1277
+ if (!options.forceRefresh && this.samplingSettingsCache && this.samplingSettingsCache.expiresAtMs > now) {
1278
+ return {
1279
+ result: { status: "ACCEPTED", code: "SAMPLING_SETTINGS_CACHED" },
1280
+ data: this.samplingSettingsCache.settings,
1281
+ correlationId: options.correlationId ?? "local-cache"
1282
+ };
1283
+ }
1284
+ const response = await this.requestGet(
1285
+ SAMPLING_SETTINGS_PATH,
1286
+ {
1287
+ signal: options.signal,
1288
+ headers: options.headers,
1289
+ retries: options.retries,
1290
+ correlationId: options.correlationId
1291
+ }
1292
+ );
1293
+ const serverCacheMs = Math.max(0, Number(response.data.cacheSeconds) || 0) * 1e3;
1294
+ const cacheMs = Math.min(this.samplingSettingsCacheMs, serverCacheMs);
1295
+ this.samplingSettingsCache = {
1296
+ settings: response.data,
1297
+ expiresAtMs: now + cacheMs
1298
+ };
1299
+ return response;
1300
+ }
1301
+ async shouldSampleAsync(request, policy) {
1302
+ if (policy) return this.shouldSample(request, policy);
1303
+ if (this.sampling === false) return false;
1304
+ if (this.sampling) return this.shouldSample(request, this.sampling);
1305
+ try {
1306
+ const settings = await this.getSamplingSettings();
1307
+ return this.shouldSample(request, settings.data);
1308
+ } catch {
1309
+ return false;
1310
+ }
1311
+ }
1312
+ async decideSample(request, options = {}) {
1313
+ const hasTokens = Number.isFinite(request.inputTokens) && Number(request.inputTokens) >= 0;
1314
+ const hasCharacters = Number.isFinite(request.inputCharacters) && Number(request.inputCharacters) >= 0;
1315
+ if (!hasTokens && !hasCharacters) {
1316
+ throw new UsageTapError(
1317
+ "USAGETAP_BAD_REQUEST",
1318
+ "decideSample requires inputTokens or inputCharacters"
1319
+ );
1320
+ }
1321
+ return this.request(
1322
+ SAMPLING_DECIDE_PATH,
1323
+ request,
1324
+ options
1325
+ );
1326
+ }
1327
+ async captureSample(request, options = {}) {
1328
+ if (!request || request.input === void 0) {
1329
+ throw new UsageTapError(
1330
+ "USAGETAP_BAD_REQUEST",
1331
+ "captureSample requires input"
1332
+ );
1333
+ }
1334
+ if (!request.provider?.trim()) {
1335
+ throw new UsageTapError(
1336
+ "USAGETAP_BAD_REQUEST",
1337
+ "captureSample requires provider"
1338
+ );
1339
+ }
1340
+ const sampleId = request.sampleId?.trim() || this.idempotencyGenerator();
1341
+ return this.request(
1342
+ SAMPLES_PATH,
1343
+ { ...request, sampleId },
1344
+ { ...options, idempotencyKey: sampleId }
1345
+ );
845
1346
  }
846
1347
  async beginCall(request, options = {}) {
847
1348
  const idempotencyKey = request.idempotencyKey ?? request.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
1349
+ this.reserveRunCall(request, idempotencyKey);
1350
+ const apiRequest = { ...request };
1351
+ delete apiRequest.runId;
848
1352
  const payload = {
849
- ...request,
1353
+ ...apiRequest,
850
1354
  feature: request.feature ?? this.defaultFeature,
851
1355
  tags: this.mergeTags(request.tags)
852
1356
  };
@@ -864,6 +1368,28 @@ var UsageTapClient = class {
864
1368
  );
865
1369
  return response;
866
1370
  }
1371
+ /**
1372
+ * Inspect a configured run circuit breaker without consuming another call.
1373
+ */
1374
+ canRunContinue(request) {
1375
+ const identity = this.resolveRunIdentity(request);
1376
+ if (!identity || !this.circuitBreaker) {
1377
+ throw new UsageTapError(
1378
+ "USAGETAP_BAD_REQUEST",
1379
+ "canRunContinue requires circuitBreaker configuration and a non-empty runId"
1380
+ );
1381
+ }
1382
+ this.expireInactiveRuns();
1383
+ const calls = this.circuitBreakerRuns.get(identity.key)?.calls ?? 0;
1384
+ return this.createCircuitBreakerDecision(identity.customerId, identity.runId, calls);
1385
+ }
1386
+ /**
1387
+ * Release local state after a workflow finishes. Returns true when state existed.
1388
+ */
1389
+ resetRun(request) {
1390
+ const identity = this.resolveRunIdentity(request);
1391
+ return identity ? this.circuitBreakerRuns.delete(identity.key) : false;
1392
+ }
867
1393
  async promptCompress(request, options = {}) {
868
1394
  if (!request?.callId) {
869
1395
  throw new UsageTapError(
@@ -961,6 +1487,10 @@ var UsageTapClient = class {
961
1487
  usageTapCompressionMessagesEndpoint: this.usageTapCompressionMessagesEndpoint,
962
1488
  aggressiveness: options.aggressiveness ?? this.aggressiveness,
963
1489
  usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
1490
+ mode: options.mode,
1491
+ latencyBudgetMs: options.latencyBudgetMs,
1492
+ compactEmptyUserMessages: options.compactEmptyUserMessages,
1493
+ compactDuplicateUserTextParts: options.compactDuplicateUserTextParts,
964
1494
  fetchImpl: this.fetchImpl,
965
1495
  signal: options.signal,
966
1496
  failOpen: options.failOpen
@@ -1002,14 +1532,29 @@ var UsageTapClient = class {
1002
1532
  callId: request.callId,
1003
1533
  feature: feature ?? this.defaultFeature,
1004
1534
  tags: tags ?? this.defaultTags,
1535
+ providerUsed: request.providerUsed,
1005
1536
  modelUsed: request.modelUsed,
1537
+ reasoningEffort: request.reasoningEffort,
1538
+ reasoningEffortSource: request.reasoningEffortSource,
1539
+ reasoningMode: request.reasoningMode,
1540
+ reasoningBudgetTokens: request.reasoningBudgetTokens,
1006
1541
  metrics: {
1007
1542
  inputTokens: request.inputTokens,
1008
1543
  responseTokens: request.responseTokens,
1009
1544
  cachedInputTokens: request.cachedInputTokens,
1545
+ cacheWriteInputTokens: request.cacheWriteInputTokens,
1546
+ cacheWrite5mInputTokens: request.cacheWrite5mInputTokens,
1547
+ cacheWrite1hInputTokens: request.cacheWrite1hInputTokens,
1010
1548
  reasoningTokens: request.reasoningTokens,
1011
1549
  searches: request.searches,
1012
1550
  audioSeconds: request.audioSeconds,
1551
+ imageInputCount: request.imageInputCount,
1552
+ imageInputTokens: request.imageInputTokens,
1553
+ imageOutputCount: request.imageOutputCount,
1554
+ imageOutputTokens: request.imageOutputTokens,
1555
+ audioInputTokens: request.audioInputTokens,
1556
+ cachedAudioInputTokens: request.cachedAudioInputTokens,
1557
+ audioOutputTokens: request.audioOutputTokens,
1013
1558
  costUsd: response.data.costUSD
1014
1559
  },
1015
1560
  correlationId: response.correlationId
@@ -1096,10 +1641,10 @@ var UsageTapClient = class {
1096
1641
  "incrementCustomMeter requires meterSlot"
1097
1642
  );
1098
1643
  }
1099
- if (!["CUSTOM1", "CUSTOM2"].includes(request.meterSlot)) {
1644
+ if (!["CUSTOM1", "CUSTOM2", "AGENTIC_API"].includes(request.meterSlot)) {
1100
1645
  throw new UsageTapError(
1101
1646
  "USAGETAP_BAD_REQUEST",
1102
- "meterSlot must be CUSTOM1 or CUSTOM2"
1647
+ "meterSlot must be CUSTOM1, CUSTOM2 or AGENTIC_API"
1103
1648
  );
1104
1649
  }
1105
1650
  if (typeof request.amount !== "number" || !Number.isFinite(request.amount) || request.amount <= 0) {
@@ -1114,6 +1659,15 @@ var UsageTapClient = class {
1114
1659
  meterSlot: request.meterSlot,
1115
1660
  amount: request.amount
1116
1661
  };
1662
+ if (request.customerUserId) {
1663
+ payload.customerUserId = request.customerUserId;
1664
+ }
1665
+ if (request.customerUserName) {
1666
+ payload.customerUserName = request.customerUserName;
1667
+ }
1668
+ if (request.customerUserEmail) {
1669
+ payload.customerUserEmail = request.customerUserEmail;
1670
+ }
1117
1671
  if (request.feature) {
1118
1672
  payload.feature = request.feature;
1119
1673
  }
@@ -1150,6 +1704,11 @@ var UsageTapClient = class {
1150
1704
  const beginPayload = idempotencyKey ? { ...beginRequest, idempotencyKey, idempotency: idempotencyKey } : { ...beginRequest };
1151
1705
  const beginResponse = await this.beginCall(beginPayload, options);
1152
1706
  let usage = {};
1707
+ const pricingMode = beginResponse.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
1708
+ if (pricingMode) {
1709
+ usage.pricingMode = pricingMode;
1710
+ usage.batch = pricingMode === "batch";
1711
+ }
1153
1712
  const initialStripeCustomerId = typeof beginResponse.data.stripeCustomerId === "string" ? beginResponse.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
1154
1713
  if (initialStripeCustomerId) {
1155
1714
  usage = { ...usage, stripeCustomerId: initialStripeCustomerId };
@@ -1158,6 +1717,28 @@ var UsageTapClient = class {
1158
1717
  let handlerResult;
1159
1718
  let handlerError;
1160
1719
  let endCallError;
1720
+ let finalizationDeferred = false;
1721
+ let finalizationPromise;
1722
+ const finalize = () => {
1723
+ if (!finalizationPromise) {
1724
+ finalizationPromise = this.endCall(
1725
+ {
1726
+ callId: beginResponse.data.callId,
1727
+ // Pass context for metric tracking
1728
+ customerId: beginRequest.customerId,
1729
+ feature: beginRequest.feature ?? this.defaultFeature,
1730
+ tags: beginRequest.tags ?? this.defaultTags,
1731
+ ...usage,
1732
+ error: errorPayload
1733
+ },
1734
+ {
1735
+ ...options,
1736
+ correlationId: beginResponse.correlationId
1737
+ }
1738
+ ).then(() => void 0);
1739
+ }
1740
+ return finalizationPromise;
1741
+ };
1161
1742
  const context = {
1162
1743
  begin: beginResponse,
1163
1744
  setUsage: (u) => {
@@ -1165,6 +1746,10 @@ var UsageTapClient = class {
1165
1746
  },
1166
1747
  setError: (err) => {
1167
1748
  errorPayload = err;
1749
+ },
1750
+ deferFinalization: () => {
1751
+ finalizationDeferred = true;
1752
+ return finalize;
1168
1753
  }
1169
1754
  };
1170
1755
  try {
@@ -1178,24 +1763,12 @@ var UsageTapClient = class {
1178
1763
  };
1179
1764
  }
1180
1765
  } finally {
1181
- try {
1182
- await this.endCall(
1183
- {
1184
- callId: beginResponse.data.callId,
1185
- // Pass context for metric tracking
1186
- customerId: beginRequest.customerId,
1187
- feature: beginRequest.feature ?? this.defaultFeature,
1188
- tags: beginRequest.tags ?? this.defaultTags,
1189
- ...usage,
1190
- error: errorPayload
1191
- },
1192
- {
1193
- ...options,
1194
- correlationId: beginResponse.correlationId
1195
- }
1196
- );
1197
- } catch (error) {
1198
- endCallError = error;
1766
+ if (handlerError || !finalizationDeferred) {
1767
+ try {
1768
+ await finalize();
1769
+ } catch (error) {
1770
+ endCallError = error;
1771
+ }
1199
1772
  }
1200
1773
  }
1201
1774
  if (handlerError) {
@@ -1217,17 +1790,86 @@ var UsageTapClient = class {
1217
1790
  toPromptCompressionTelemetry(result) {
1218
1791
  return {
1219
1792
  provider: result.provider,
1220
- originalCharacters: result.originalCharacters,
1221
- compressedCharacters: result.compressedCharacters,
1222
- savedCharacters: result.savedCharacters,
1223
1793
  originalTokens: result.originalTokens,
1224
1794
  compressedTokens: result.compressedTokens,
1225
1795
  savedTokens: result.savedTokens,
1226
1796
  tokenSavingsRatio: result.tokenSavingsRatio,
1227
- savingsRatio: result.savingsRatio,
1228
1797
  techniques: result.techniques
1229
1798
  };
1230
1799
  }
1800
+ reserveRunCall(request, idempotencyKey) {
1801
+ const identity = this.resolveRunIdentity(request);
1802
+ if (!identity || !this.circuitBreaker) return;
1803
+ this.expireInactiveRuns();
1804
+ const now = Date.now();
1805
+ const state = this.circuitBreakerRuns.get(identity.key) ?? {
1806
+ calls: 0,
1807
+ reservationKeys: /* @__PURE__ */ new Set(),
1808
+ lastSeenAtMs: now
1809
+ };
1810
+ const reservationKey = idempotencyKey ?? this.idempotencyGenerator();
1811
+ state.lastSeenAtMs = now;
1812
+ if (state.reservationKeys.has(reservationKey)) {
1813
+ this.circuitBreakerRuns.set(identity.key, state);
1814
+ return;
1815
+ }
1816
+ const decision = this.createCircuitBreakerDecision(
1817
+ identity.customerId,
1818
+ identity.runId,
1819
+ state.calls
1820
+ );
1821
+ if (!decision.allowed) {
1822
+ throw new UsageTapError(
1823
+ "USAGETAP_CIRCUIT_OPEN",
1824
+ `Run ${identity.runId} reached its ${decision.limit}-call circuit-breaker limit`,
1825
+ {
1826
+ details: {
1827
+ reason: decision.reason,
1828
+ customerId: identity.customerId,
1829
+ runId: identity.runId,
1830
+ calls: decision.calls,
1831
+ limit: decision.limit,
1832
+ remaining: decision.remaining
1833
+ }
1834
+ }
1835
+ );
1836
+ }
1837
+ state.calls += 1;
1838
+ state.reservationKeys.add(reservationKey);
1839
+ this.circuitBreakerRuns.set(identity.key, state);
1840
+ }
1841
+ resolveRunIdentity(request) {
1842
+ const customerId = request.customerId?.trim();
1843
+ const runId = request.runId?.trim();
1844
+ if (!customerId || !runId) return void 0;
1845
+ return {
1846
+ key: `${customerId}\0${runId}`,
1847
+ customerId,
1848
+ runId
1849
+ };
1850
+ }
1851
+ createCircuitBreakerDecision(customerId, runId, calls) {
1852
+ const limit = this.circuitBreaker?.maxCallsPerRun ?? 0;
1853
+ const allowed = calls < limit;
1854
+ return {
1855
+ allowed,
1856
+ ...allowed ? {} : { reason: "max_calls_per_run" },
1857
+ customerId,
1858
+ runId,
1859
+ calls,
1860
+ limit,
1861
+ remaining: Math.max(0, limit - calls)
1862
+ };
1863
+ }
1864
+ expireInactiveRuns() {
1865
+ if (!this.circuitBreaker || this.circuitBreakerRuns.size === 0) return;
1866
+ const expiredBefore = Date.now() - this.circuitBreaker.runInactivityMs;
1867
+ for (const [key, state] of this.circuitBreakerRuns) {
1868
+ if (state.lastSeenAtMs < expiredBefore) {
1869
+ this.circuitBreakerRuns.delete(key);
1870
+ }
1871
+ }
1872
+ }
1231
1873
  async request(path, payload, options) {
1232
1874
  const url = new URL(path, this.baseUrl).toString();
1233
1875
  const body = payload !== void 0 ? JSON.stringify(payload) : void 0;
@@ -1415,7 +2057,7 @@ var UsageTapClient = class {
1415
2057
  ...this.defaultHeaders,
1416
2058
  [SDK_HEADER]: `js/${SDK_VERSION}`,
1417
2059
  "content-type": "application/json",
1418
- accept: CANONICAL_MEDIA_TYPE
2060
+ accept: CANONICAL_MEDIA_TYPE2
1419
2061
  };
1420
2062
  if (!HAS_WINDOW) {
1421
2063
  headers["user-agent"] = `${USER_AGENT}/${SDK_VERSION}`;
@@ -1468,7 +2110,8 @@ var UsageTapClient = class {
1468
2110
  }
1469
2111
  toHttpError(status, payload, correlationId) {
1470
2112
  const code = mapStatusToErrorCode(status);
1471
- const retryable = isRetryableStatus(status);
2113
+ const apiCode = payload?.error?.code ?? payload?.result?.code ?? "UNKNOWN";
2114
+ const retryable = isRetryableStatus(status) || isRetryableApiCode(apiCode);
1472
2115
  const message = payload?.error?.message ?? payload?.result?.message ?? `UsageTap responded with HTTP ${status}`;
1473
2116
  return new UsageTapError(code, message, {
1474
2117
  status,
@@ -1501,7 +2144,7 @@ function isRetryableStatus(status) {
1501
2144
  }
1502
2145
  function isRetryableApiCode(code) {
1503
2146
  const normalized = code.toUpperCase();
1504
- return normalized.includes("TRANSIENT") || normalized.includes("RETRY") || normalized.includes("TIMEOUT") || normalized.includes("THROTTLE") || normalized.includes("RATE_LIMIT");
2147
+ return normalized === "PAYG_CONFLICT" || normalized.includes("TRANSIENT") || normalized.includes("RETRY") || normalized.includes("TIMEOUT") || normalized.includes("THROTTLE") || normalized.includes("RATE_LIMIT");
1505
2148
  }
1506
2149
  function mapApiCodeToError(code) {
1507
2150
  const normalized = code.toUpperCase();
@@ -1616,17 +2259,18 @@ var OpenAIPromptCompressionStats = class {
1616
2259
  }
1617
2260
  };
1618
2261
  function createOpenAIAdapter(init) {
1619
- const { client, usageTap } = init;
2262
+ const { client, usageTap, provider = "openai" } = init;
1620
2263
  return {
1621
2264
  async invoke(params) {
1622
2265
  const result = await usageTap.withUsage(
1623
2266
  params.begin,
1624
2267
  async (ctx) => {
2268
+ ctx.setUsage({ providerUsed: provider });
1625
2269
  const response = await params.call(client, {
1626
2270
  hints: ctx.begin.data.vendorHints,
1627
2271
  begin: ctx.begin
1628
2272
  });
1629
- tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx);
2273
+ tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx, provider);
1630
2274
  return {
1631
2275
  data: response,
1632
2276
  begin: ctx.begin
@@ -1640,24 +2284,29 @@ function createOpenAIAdapter(init) {
1640
2284
  const result = await usageTap.withUsage(
1641
2285
  params.begin,
1642
2286
  async (ctx) => {
2287
+ const settle = deferUsageFinalization(ctx);
2288
+ ctx.setUsage({ providerUsed: provider });
1643
2289
  const { stream, onComplete } = await params.call(client, {
1644
2290
  hints: ctx.begin.data.vendorHints,
1645
2291
  begin: ctx.begin
1646
2292
  });
1647
- const wrapped = wrapStreamForUsageTap(stream, async () => {
1648
- if (!onComplete) return;
2293
+ const wrapped = wrapStreamForUsageTap(stream, async (termination) => {
1649
2294
  try {
1650
- const maybeUsage = await onComplete();
1651
- if (maybeUsage) {
1652
- ctx.setUsage(maybeUsage);
2295
+ if (termination === "complete" && onComplete) {
2296
+ const maybeUsage = await onComplete();
2297
+ if (maybeUsage) {
2298
+ ctx.setUsage(maybeUsage);
2299
+ }
1653
2300
  }
1654
2301
  } catch (error) {
1655
2302
  ctx.setError({
1656
2303
  code: "USAGE_FINALIZE_ERROR",
1657
2304
  message: error instanceof Error ? error.message : String(error)
1658
2305
  });
2306
+ await settle();
1659
2307
  throw error;
1660
2308
  }
2309
+ await settle();
1661
2310
  }, ctx);
1662
2311
  const finalize = async () => {
1663
2312
  await wrapped.__usageTapFinalize?.();
@@ -1765,6 +2414,181 @@ async function pipeToResponse(stream, res, options = {}) {
1765
2414
  }
1766
2415
  }
1767
2416
  var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
2417
+ function withSampling(client, options = {}) {
2418
+ if (!client || !options) {
2419
+ throw new UsageTapError(
2420
+ "USAGETAP_BAD_REQUEST",
2421
+ "withSampling requires an OpenAI-compatible client and sampling options"
2422
+ );
2423
+ }
2424
+ const { apiKey, usageTapClient, provider = "openai", ...policy } = options;
2425
+ const localPolicy = typeof policy.rate === "number" ? policy : void 0;
2426
+ const usageTap = usageTapClient ?? new UsageTapClient({ apiKey, sampling: localPolicy });
2427
+ const wrapCreate = (create) => async (params, requestOptions) => {
2428
+ const { usageTap: callContextRaw, ...providerOptions } = requestOptions ?? {};
2429
+ const callContext = isObjectRecord(callContextRaw) ? callContextRaw : {};
2430
+ const streaming = params.stream === true;
2431
+ const decision = streaming ? Promise.resolve(false) : usageTap.shouldSampleAsync({
2432
+ customerId: readString(callContext.customerId),
2433
+ feature: readString(callContext.feature),
2434
+ input: params
2435
+ }, localPolicy);
2436
+ const startedAt = Date.now();
2437
+ try {
2438
+ const response = await create(
2439
+ params,
2440
+ Object.keys(providerOptions).length ? providerOptions : void 0
2441
+ );
2442
+ const selected = await decision;
2443
+ if (selected) {
2444
+ const record = isObjectRecord(response) ? response : {};
2445
+ await usageTap.captureSample({
2446
+ customerId: readString(callContext.customerId),
2447
+ feature: readString(callContext.feature),
2448
+ environment: readString(callContext.environment),
2449
+ tags: readStringArray(callContext.tags),
2450
+ provider,
2451
+ model: readString(record.model) ?? readString(params.model),
2452
+ input: params,
2453
+ output: response,
2454
+ usage: record.usage,
2455
+ latencyMs: Date.now() - startedAt
2456
+ }).catch(() => void 0);
2457
+ }
2458
+ return response;
2459
+ } catch (error) {
2460
+ const selected = await decision;
2461
+ if (selected) {
2462
+ await usageTap.captureSample({
2463
+ customerId: readString(callContext.customerId),
2464
+ feature: readString(callContext.feature),
2465
+ environment: readString(callContext.environment),
2466
+ tags: readStringArray(callContext.tags),
2467
+ provider,
2468
+ model: readString(params.model),
2469
+ input: params,
2470
+ latencyMs: Date.now() - startedAt,
2471
+ error: serializeSamplingError(error)
2472
+ }).catch(() => void 0);
2473
+ }
2474
+ throw error;
2475
+ }
2476
+ };
2477
+ const chat = client.chat?.completions ? new Proxy(client.chat, {
2478
+ get(target, prop, receiver) {
2479
+ if (prop !== "completions") return safeReflectGet(target, prop, receiver);
2480
+ const completions = target.completions;
2481
+ return new Proxy(completions, {
2482
+ get(completionTarget, completionProp, completionReceiver) {
2483
+ if (completionProp === "create") {
2484
+ return wrapCreate(
2485
+ completionTarget.create.bind(completionTarget)
2486
+ );
2487
+ }
2488
+ return safeReflectGet(
2489
+ completionTarget,
2490
+ completionProp,
2491
+ completionReceiver
2492
+ );
2493
+ }
2494
+ });
2495
+ }
2496
+ }) : void 0;
2497
+ const responses = typeof client.responses !== "undefined" && client.responses ? new Proxy(client.responses, {
2498
+ get(target, prop, receiver) {
2499
+ if (prop === "create") {
2500
+ const create = Reflect.get(target, prop, receiver);
2501
+ return wrapCreate(create.bind(target));
2502
+ }
2503
+ return safeReflectGet(target, prop, receiver);
2504
+ }
2505
+ }) : void 0;
2506
+ return new Proxy(client, {
2507
+ get(target, prop, receiver) {
2508
+ if (prop === "chat" && chat) return chat;
2509
+ if (prop === "responses" && responses) return responses;
2510
+ if (prop === "unwrap") return () => target;
2511
+ return safeReflectGet(target, prop, receiver);
2512
+ }
2513
+ });
2514
+ }
2515
+ function safeReflectGet(target, prop, receiver) {
2516
+ return Reflect.get(target, prop, receiver);
2517
+ }
2518
+ function readStringArray(value) {
2519
+ if (!Array.isArray(value)) return void 0;
2520
+ const strings = value.filter((item) => typeof item === "string");
2521
+ return strings.length ? strings : void 0;
2522
+ }
2523
+ function readString(value) {
2524
+ return typeof value === "string" && value.trim() ? value : void 0;
2525
+ }
2526
+ function serializeSamplingError(error) {
2527
+ if (error instanceof Error) {
2528
+ return { name: error.name, message: error.message };
2529
+ }
2530
+ return { message: String(error) };
2531
+ }
2532
+ function normalizeMeteredOpenAISampling(options) {
2533
+ if (!options) return void 0;
2534
+ if (options === true) return { provider: "openai" };
2535
+ const { provider = "openai", ...policyFields } = options;
2536
+ return {
2537
+ provider,
2538
+ policy: typeof policyFields.rate === "number" ? policyFields : void 0
2539
+ };
2540
+ }
2541
+ function startMeteredOpenAISampleDecision({
2542
+ usageTap,
2543
+ sampling,
2544
+ beginRequest,
2545
+ input
2546
+ }) {
2547
+ if (!sampling) return Promise.resolve(false);
2548
+ return usageTap.shouldSampleAsync(
2549
+ {
2550
+ customerId: beginRequest.customerId,
2551
+ feature: beginRequest.feature,
2552
+ input
2553
+ },
2554
+ sampling.policy
2555
+ );
2556
+ }
2557
+ async function captureMeteredOpenAISample({
2558
+ usageTap,
2559
+ sampling,
2560
+ decision,
2561
+ ctx,
2562
+ beginRequest,
2563
+ input,
2564
+ response,
2565
+ error,
2566
+ startedAt
2567
+ }) {
2568
+ if (!sampling) return;
2569
+ let selected = false;
2570
+ try {
2571
+ selected = await decision;
2572
+ } catch {
2573
+ return;
2574
+ }
2575
+ if (!selected) return;
2576
+ const record = isObjectRecord(response) ? response : {};
2577
+ await usageTap.captureSample({
2578
+ sampleId: ctx.begin.data.callId,
2579
+ callId: ctx.begin.data.callId,
2580
+ customerId: beginRequest.customerId,
2581
+ feature: beginRequest.feature,
2582
+ tags: beginRequest.tags,
2583
+ provider: sampling.provider,
2584
+ model: readString(record.model) ?? readString(input.model),
2585
+ input,
2586
+ ...response === void 0 ? {} : { output: response },
2587
+ usage: record.usage,
2588
+ latencyMs: Date.now() - startedAt,
2589
+ ...error === void 0 ? {} : { error: serializeSamplingError(error) }
2590
+ }).catch(() => void 0);
2591
+ }
1768
2592
  function withCompression(client, options = {}) {
1769
2593
  if (!client) {
1770
2594
  throw new UsageTapError(
@@ -1775,11 +2599,13 @@ function withCompression(client, options = {}) {
1775
2599
  const {
1776
2600
  apiKey,
1777
2601
  usageTapClient,
2602
+ sampling,
1778
2603
  ...compressionOverrides
1779
2604
  } = options;
1780
2605
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
1781
2606
  const compression = {
1782
2607
  provider: "usagetap",
2608
+ roles: { user: { enabled: true, aggressiveness: 0.2 } },
1783
2609
  minContextTokens: DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS,
1784
2610
  ...compressionOverrides
1785
2611
  };
@@ -1789,7 +2615,7 @@ function withCompression(client, options = {}) {
1789
2615
  usageTap,
1790
2616
  compression
1791
2617
  ) : void 0;
1792
- return new Proxy(client, {
2618
+ const compressed = new Proxy(client, {
1793
2619
  get(target, prop, receiver) {
1794
2620
  if (prop === "chat" && proxiedChat) {
1795
2621
  return proxiedChat;
@@ -1803,6 +2629,11 @@ function withCompression(client, options = {}) {
1803
2629
  return Reflect.get(target, prop, receiver);
1804
2630
  }
1805
2631
  });
2632
+ return sampling ? withSampling(compressed, {
2633
+ ...sampling === true ? {} : sampling,
2634
+ apiKey,
2635
+ usageTapClient
2636
+ }) : compressed;
1806
2637
  }
1807
2638
  function withMetering(client, customer) {
1808
2639
  const config = typeof customer === "string" ? { customerId: customer } : customer;
@@ -1817,6 +2648,8 @@ function withMetering(client, customer) {
1817
2648
  usageTapClient,
1818
2649
  applyVendorHints,
1819
2650
  promptCompression,
2651
+ sampling,
2652
+ provider,
1820
2653
  ...defaultContext
1821
2654
  } = config;
1822
2655
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
@@ -1824,7 +2657,9 @@ function withMetering(client, customer) {
1824
2657
  return wrapOpenAI(client, usageTap, {
1825
2658
  defaultContext,
1826
2659
  applyVendorHints,
1827
- promptCompression: normalizedCompression
2660
+ promptCompression: normalizedCompression,
2661
+ sampling,
2662
+ provider
1828
2663
  });
1829
2664
  }
1830
2665
  function wrapOpenAI(client, usageTap, options = {}) {
@@ -1834,6 +2669,8 @@ function wrapOpenAI(client, usageTap, options = {}) {
1834
2669
  const defaultContext = options.defaultContext;
1835
2670
  const applyVendorHints = options.applyVendorHints !== false;
1836
2671
  const defaultPromptCompression = normalizePromptCompressionOptions(options.promptCompression);
2672
+ const defaultSampling = normalizeMeteredOpenAISampling(options.sampling);
2673
+ const provider = options.provider ?? "openai";
1837
2674
  const promptCompressionStats = new OpenAIPromptCompressionStats();
1838
2675
  const proxiedChat = client.chat ? createChatProxy(
1839
2676
  client.chat,
@@ -1841,7 +2678,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
1841
2678
  defaultContext,
1842
2679
  applyVendorHints,
1843
2680
  defaultPromptCompression,
1844
- promptCompressionStats
2681
+ promptCompressionStats,
2682
+ defaultSampling,
2683
+ provider
1845
2684
  ) : void 0;
1846
2685
  const proxiedResponses = typeof client.responses !== "undefined" ? createResponsesProxy(
1847
2686
  client.responses,
@@ -1849,7 +2688,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
1849
2688
  defaultContext,
1850
2689
  applyVendorHints,
1851
2690
  defaultPromptCompression,
1852
- promptCompressionStats
2691
+ promptCompressionStats,
2692
+ defaultSampling,
2693
+ provider
1853
2694
  ) : void 0;
1854
2695
  const handler = {
1855
2696
  get(target, prop, receiver) {
@@ -1968,14 +2809,16 @@ function streamOpenAIRoute(usageTap, openai, options) {
1968
2809
  });
1969
2810
  };
1970
2811
  }
1971
- function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2812
+ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1972
2813
  const completions = createChatCompletionsProxy(
1973
2814
  resource.completions,
1974
2815
  usageTap,
1975
2816
  defaultContext,
1976
2817
  applyVendorHints,
1977
2818
  defaultPromptCompression,
1978
- promptCompressionStats
2819
+ promptCompressionStats,
2820
+ defaultSampling,
2821
+ provider
1979
2822
  );
1980
2823
  const handler = {
1981
2824
  get(target, prop, receiver) {
@@ -1987,7 +2830,7 @@ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, d
1987
2830
  };
1988
2831
  return new Proxy(resource, handler);
1989
2832
  }
1990
- function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2833
+ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1991
2834
  if (!resource || typeof resource !== "object") {
1992
2835
  return void 0;
1993
2836
  }
@@ -2002,9 +2845,20 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2002
2845
  withUsage,
2003
2846
  promptCompression
2004
2847
  } = splitUsageOptions(options);
2005
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2848
+ const beginRequest = responsesBeginRequest(
2849
+ resolveBeginRequest(defaultContext, usageContext),
2850
+ params
2851
+ );
2006
2852
  const wantsStream = isStreamingRequest(params);
2007
2853
  return usageTap.withUsage(beginRequest, async (ctx) => {
2854
+ const settle = wantsStream ? deferUsageFinalization(ctx) : void 0;
2855
+ const sampleStartedAt = Date.now();
2856
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2857
+ usageTap,
2858
+ sampling: defaultSampling,
2859
+ beginRequest,
2860
+ input: params
2861
+ });
2008
2862
  const hintedParams = applyVendorHints ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints) : params;
2009
2863
  const finalParams = await compressResponsesParamsForCall({
2010
2864
  params: hintedParams,
@@ -2016,25 +2870,67 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2016
2870
  withUsage,
2017
2871
  operation: "responses.create"
2018
2872
  });
2873
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2019
2874
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2020
2875
  if (wantsStream) {
2021
2876
  const apiPromise2 = originalCreate(finalParams, request);
2022
2877
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
2023
2878
  ensureAsyncIterable(rawStream, "responses.create");
2024
- const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
2025
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2026
- if (usage) {
2027
- ctx.setUsage(usage);
2879
+ const wrappedStream = wrapStreamForUsageTap(rawStream, async (termination) => {
2880
+ try {
2881
+ if (termination === "complete") {
2882
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2883
+ if (usage) {
2884
+ ctx.setUsage(usage);
2885
+ }
2886
+ }
2887
+ } catch (error) {
2888
+ ctx.setError({
2889
+ code: "USAGE_FINALIZE_ERROR",
2890
+ message: error instanceof Error ? error.message : String(error)
2891
+ });
2892
+ throw error;
2893
+ } finally {
2894
+ await settle?.();
2028
2895
  }
2029
- }, ctx);
2896
+ }, ctx, (chunk) => {
2897
+ tryInferUsageFromStreamChunk(
2898
+ chunk,
2899
+ ctx.begin.data.vendorHints,
2900
+ ctx,
2901
+ provider
2902
+ );
2903
+ });
2030
2904
  return wrappedStream;
2031
2905
  });
2032
2906
  return wrappedPromise2;
2033
2907
  }
2034
2908
  const apiPromise = originalCreate(finalParams, request);
2035
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
2036
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2909
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2910
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2911
+ await captureMeteredOpenAISample({
2912
+ usageTap,
2913
+ sampling: defaultSampling,
2914
+ decision: sampleDecision,
2915
+ ctx,
2916
+ beginRequest,
2917
+ input: params,
2918
+ response,
2919
+ startedAt: sampleStartedAt
2920
+ });
2037
2921
  return response;
2922
+ }, async (error) => {
2923
+ await captureMeteredOpenAISample({
2924
+ usageTap,
2925
+ sampling: defaultSampling,
2926
+ decision: sampleDecision,
2927
+ ctx,
2928
+ beginRequest,
2929
+ input: params,
2930
+ error,
2931
+ startedAt: sampleStartedAt
2932
+ });
2933
+ throw error;
2038
2934
  });
2039
2935
  return wrappedPromise;
2040
2936
  }, withUsage);
@@ -2049,7 +2945,7 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2049
2945
  };
2050
2946
  return new Proxy(resource, handler);
2051
2947
  }
2052
- function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2948
+ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
2053
2949
  const originalCreate = resource.create.bind(resource);
2054
2950
  const streamCandidate = resource.stream;
2055
2951
  const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(resource) : void 0;
@@ -2063,8 +2959,16 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2063
2959
  const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2064
2960
  const wantsStream = isStreamingRequest(params);
2065
2961
  return usageTap.withUsage(beginRequest, async (ctx) => {
2962
+ const settle = wantsStream ? deferUsageFinalization(ctx) : void 0;
2963
+ const sampleStartedAt = Date.now();
2964
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2965
+ usageTap,
2966
+ sampling: defaultSampling,
2967
+ beginRequest,
2968
+ input: params
2969
+ });
2066
2970
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2067
- const finalParams = await compressChatParamsForCall({
2971
+ const compressedParams = await compressChatParamsForCall({
2068
2972
  params: hintedParams,
2069
2973
  usageTap,
2070
2974
  ctx,
@@ -2074,25 +2978,68 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2074
2978
  withUsage,
2075
2979
  operation: "chat.completions.create"
2076
2980
  });
2981
+ const finalParams = wantsStream ? ensureOpenAIStreamUsage(compressedParams) : compressedParams;
2982
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2077
2983
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2078
2984
  if (wantsStream) {
2079
2985
  const apiPromise2 = originalCreate(finalParams, request);
2080
2986
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
2081
2987
  ensureAsyncIterable(rawStream, "chat.completions.create");
2082
- const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
2083
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2084
- if (usage) {
2085
- ctx.setUsage(usage);
2988
+ const wrappedStream2 = wrapStreamForUsageTap(rawStream, async (termination) => {
2989
+ try {
2990
+ if (termination === "complete") {
2991
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2992
+ if (usage) {
2993
+ ctx.setUsage(usage);
2994
+ }
2995
+ }
2996
+ } catch (error) {
2997
+ ctx.setError({
2998
+ code: "USAGE_FINALIZE_ERROR",
2999
+ message: error instanceof Error ? error.message : String(error)
3000
+ });
3001
+ throw error;
3002
+ } finally {
3003
+ await settle?.();
2086
3004
  }
2087
- }, ctx);
3005
+ }, ctx, (chunk) => {
3006
+ tryInferUsageFromStreamChunk(
3007
+ chunk,
3008
+ ctx.begin.data.vendorHints,
3009
+ ctx,
3010
+ provider
3011
+ );
3012
+ });
2088
3013
  return wrappedStream2;
2089
3014
  });
2090
3015
  return wrappedPromise2;
2091
3016
  }
2092
3017
  const apiPromise = originalCreate(finalParams, request);
2093
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
2094
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
3018
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
3019
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
3020
+ await captureMeteredOpenAISample({
3021
+ usageTap,
3022
+ sampling: defaultSampling,
3023
+ decision: sampleDecision,
3024
+ ctx,
3025
+ beginRequest,
3026
+ input: params,
3027
+ response,
3028
+ startedAt: sampleStartedAt
3029
+ });
2095
3030
  return response;
3031
+ }, async (error) => {
3032
+ await captureMeteredOpenAISample({
3033
+ usageTap,
3034
+ sampling: defaultSampling,
3035
+ decision: sampleDecision,
3036
+ ctx,
3037
+ beginRequest,
3038
+ input: params,
3039
+ error,
3040
+ startedAt: sampleStartedAt
3041
+ });
3042
+ throw error;
2096
3043
  });
2097
3044
  return wrappedPromise;
2098
3045
  }, withUsage);
@@ -2106,8 +3053,9 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2106
3053
  } = splitUsageOptions(options);
2107
3054
  const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2108
3055
  return usageTap.withUsage(beginRequest, async (ctx) => {
3056
+ const settle = deferUsageFinalization(ctx);
2109
3057
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2110
- const finalParams = await compressChatParamsForCall({
3058
+ const compressedParams = await compressChatParamsForCall({
2111
3059
  params: hintedParams,
2112
3060
  usageTap,
2113
3061
  ctx,
@@ -2117,16 +3065,41 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2117
3065
  withUsage,
2118
3066
  operation: "chat.completions.stream"
2119
3067
  });
3068
+ const finalParams = ensureOpenAIStreamUsage(compressedParams);
3069
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2120
3070
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2121
3071
  const apiPromise = originalStream(finalParams, request);
2122
3072
  const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
2123
3073
  ensureAsyncIterable(rawStream, "chat.completions.stream");
2124
- const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
2125
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2126
- if (usage) {
2127
- ctx.setUsage(usage);
3074
+ const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async (termination) => {
3075
+ try {
3076
+ if (termination === "complete") {
3077
+ const usage = await extractUsageFromStream(
3078
+ rawStream,
3079
+ ctx.begin.data.vendorHints,
3080
+ provider
3081
+ );
3082
+ if (usage) {
3083
+ ctx.setUsage(usage);
3084
+ }
3085
+ }
3086
+ } catch (error) {
3087
+ ctx.setError({
3088
+ code: "USAGE_FINALIZE_ERROR",
3089
+ message: error instanceof Error ? error.message : String(error)
3090
+ });
3091
+ throw error;
3092
+ } finally {
3093
+ await settle();
2128
3094
  }
2129
- }, ctx);
3095
+ }, ctx, (chunk) => {
3096
+ tryInferUsageFromStreamChunk(
3097
+ chunk,
3098
+ ctx.begin.data.vendorHints,
3099
+ ctx,
3100
+ provider
3101
+ );
3102
+ });
2130
3103
  return wrappedStreamInner;
2131
3104
  });
2132
3105
  return wrappedPromise;
@@ -2248,6 +3221,10 @@ async function compressChatParams(params, usageTap, compression, signal) {
2248
3221
  const result = await usageTap.compressPromptMessages(source, {
2249
3222
  provider: "usagetap",
2250
3223
  failOpen: compression.failOpen,
3224
+ mode: compression.mode,
3225
+ latencyBudgetMs: compression.latencyBudgetMs,
3226
+ compactEmptyUserMessages: compression.compactEmptyUserMessages,
3227
+ compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
2251
3228
  aggressiveness: resolveMessageEndpointAggressiveness(compression),
2252
3229
  signal
2253
3230
  });
@@ -2717,19 +3694,52 @@ function resolveBeginRequest(defaults, override) {
2717
3694
  if (requested) begin.requested = requested;
2718
3695
  const feature = current.feature ?? base.feature;
2719
3696
  if (feature) begin.feature = feature;
3697
+ const runId = current.runId ?? base.runId;
3698
+ if (runId) begin.runId = runId;
2720
3699
  const idempotency = current.idempotency ?? base.idempotency;
2721
3700
  if (idempotency) begin.idempotency = idempotency;
2722
3701
  const customerName = current.customerName ?? base.customerName;
2723
3702
  if (customerName) begin.customerName = customerName;
2724
3703
  const customerEmail = current.customerEmail ?? base.customerEmail;
2725
3704
  if (customerEmail) begin.customerEmail = customerEmail;
3705
+ const customerUserId = current.customerUserId ?? base.customerUserId;
3706
+ if (customerUserId) begin.customerUserId = customerUserId;
3707
+ const customerUserName = current.customerUserName ?? base.customerUserName;
3708
+ if (customerUserName) begin.customerUserName = customerUserName;
3709
+ const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
3710
+ if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
3711
+ const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
3712
+ if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
3713
+ const batch = current.batch ?? base.batch;
3714
+ if (typeof batch === "boolean") begin.batch = batch;
3715
+ const pricingMode = current.pricingMode ?? base.pricingMode;
3716
+ if (pricingMode) begin.pricingMode = pricingMode;
2726
3717
  if (tags?.length) {
2727
3718
  begin.tags = tags;
2728
3719
  }
2729
3720
  return begin;
2730
3721
  }
2731
- function transformApiPromise(apiPromise, onResolve) {
2732
- const resolvedPromise = Promise.resolve(apiPromise).then(onResolve);
3722
+ function responsesBeginRequest(begin, params) {
3723
+ if (!responsesRequestUsesWebSearch(params)) return begin;
3724
+ return {
3725
+ ...begin,
3726
+ requested: {
3727
+ ...begin.requested ?? {},
3728
+ search: true
3729
+ }
3730
+ };
3731
+ }
3732
+ function responsesRequestUsesWebSearch(params) {
3733
+ if (!params || typeof params !== "object") return false;
3734
+ const tools = params.tools;
3735
+ return Array.isArray(tools) && tools.some((tool) => {
3736
+ if (!tool || typeof tool !== "object") return false;
3737
+ const type = tool.type;
3738
+ return type === "web_search" || type === "web_search_preview";
3739
+ });
3740
+ }
3741
+ function transformApiPromise(apiPromise, onResolve, onReject) {
3742
+ const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
2733
3743
  if (isObjectRecord(apiPromise)) {
2734
3744
  const proto = Object.getPrototypeOf(apiPromise);
2735
3745
  if (proto) {
@@ -2864,12 +3874,12 @@ function applyResponsesVendorHints(params, hints) {
2864
3874
  }
2865
3875
  return next;
2866
3876
  }
2867
- async function extractUsageFromStream(stream, hints) {
3877
+ async function extractUsageFromStream(stream, hints, provider = "openai") {
2868
3878
  const finalPayload = await resolveStreamFinalPayload(stream);
2869
3879
  if (!finalPayload) {
2870
3880
  return void 0;
2871
3881
  }
2872
- return inferUsageFromResponse(finalPayload, hints);
3882
+ return inferUsageFromResponse(finalPayload, hints, provider);
2873
3883
  }
2874
3884
  async function resolveStreamFinalPayload(stream) {
2875
3885
  if (!stream || typeof stream !== "object") {
@@ -2942,14 +3952,14 @@ function setHeaderIfPossible(res, key, value) {
2942
3952
  res.setHeader(key, value);
2943
3953
  }
2944
3954
  }
2945
- function tryInferUsage(response, hints, extractor, ctx) {
3955
+ function tryInferUsage(response, hints, extractor, ctx, provider = "openai") {
2946
3956
  const explicit = extractor?.(response);
2947
- const inferred = explicit ?? inferUsageFromResponse(response, hints);
3957
+ const inferred = explicit ?? inferUsageFromResponse(response, hints, provider);
2948
3958
  if (inferred) {
2949
3959
  ctx.setUsage(inferred);
2950
3960
  }
2951
3961
  }
2952
- function inferUsageFromResponse(response, hints) {
3962
+ function inferUsageFromResponse(response, hints, provider = "openai") {
2953
3963
  if (!response || typeof response !== "object") {
2954
3964
  return void 0;
2955
3965
  }
@@ -2957,32 +3967,110 @@ function inferUsageFromResponse(response, hints) {
2957
3967
  if (!candidate.usage) {
2958
3968
  return void 0;
2959
3969
  }
2960
- const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
3970
+ const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
3971
+ 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;
3972
+ const cacheWrite5mInputTokens = candidate.usage.cache_write_5m_input_tokens ?? candidate.usage.cache_creation?.ephemeral_5m_input_tokens;
3973
+ const cacheWrite1hInputTokens = candidate.usage.cache_write_1h_input_tokens ?? candidate.usage.cache_creation?.ephemeral_1h_input_tokens;
3974
+ const outputSearches = Array.isArray(candidate.output) ? candidate.output.filter((item) => item?.type === "web_search_call").length : 0;
3975
+ const searches = candidate.usage.searches ?? candidate.usage.web_search_queries ?? candidate.usage.server_tool_use?.web_search_requests ?? outputSearches;
3976
+ const responseEffort = normalizeExecutionReasoningEffort(
3977
+ candidate.reasoning?.effort
3978
+ );
2961
3979
  return {
3980
+ providerUsed: provider,
2962
3981
  modelUsed: candidate.model ?? hints?.preferredModel,
2963
- inputTokens: candidate.usage.prompt_tokens,
2964
- responseTokens: candidate.usage.completion_tokens,
2965
- cachedInputTokens
3982
+ inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
3983
+ responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
3984
+ cachedInputTokens,
3985
+ cacheWriteInputTokens,
3986
+ cacheWrite5mInputTokens,
3987
+ cacheWrite1hInputTokens,
3988
+ audioInputTokens: candidate.usage.prompt_tokens_details?.audio_tokens ?? candidate.usage.input_tokens_details?.audio_tokens,
3989
+ 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,
3990
+ imageInputTokens: candidate.usage.prompt_tokens_details?.image_tokens ?? candidate.usage.input_tokens_details?.image_tokens,
3991
+ imageOutputTokens: candidate.usage.completion_tokens_details?.image_tokens ?? candidate.usage.output_tokens_details?.image_tokens,
3992
+ audioOutputTokens: candidate.usage.completion_tokens_details?.audio_tokens ?? candidate.usage.output_tokens_details?.audio_tokens,
3993
+ reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
3994
+ ...responseEffort ? {
3995
+ reasoningEffort: responseEffort,
3996
+ reasoningEffortSource: "provider_response"
3997
+ } : {},
3998
+ ...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {},
3999
+ ...typeof searches === "number" && searches > 0 ? { searches } : {}
2966
4000
  };
2967
4001
  }
2968
- function wrapStreamForUsageTap(source, finalize, ctx) {
4002
+ function normalizeExecutionReasoningEffort(value) {
4003
+ return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
4004
+ }
4005
+ function openAIRequestExecutionMetadata(params, provider) {
4006
+ const record = params && typeof params === "object" ? params : {};
4007
+ const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
4008
+ const effort = normalizeExecutionReasoningEffort(
4009
+ record.reasoning_effort ?? reasoning?.effort ?? record.thinking_level
4010
+ );
4011
+ const mode = typeof reasoning?.type === "string" ? reasoning.type : typeof reasoning?.mode === "string" ? reasoning.mode : void 0;
4012
+ const rawBudget = reasoning?.budget_tokens ?? record.thinking_budget ?? record.thinking_budget_tokens;
4013
+ const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
4014
+ return {
4015
+ providerUsed: provider,
4016
+ ...typeof record.model === "string" ? { modelUsed: record.model } : {},
4017
+ ...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
4018
+ ...mode ? { reasoningMode: mode } : {},
4019
+ ...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
4020
+ };
4021
+ }
4022
+ function tryInferUsageFromStreamChunk(chunk, hints, ctx, provider) {
4023
+ const payload = isObjectRecord(chunk) && isObjectRecord(chunk.response) ? chunk.response : chunk;
4024
+ const inferred = inferUsageFromResponse(payload, hints, provider);
4025
+ if (inferred) {
4026
+ ctx.setUsage(inferred);
4027
+ }
4028
+ }
4029
+ function ensureOpenAIStreamUsage(params) {
4030
+ if (!isObjectRecord(params)) {
4031
+ return params;
4032
+ }
4033
+ const streamOptions = isObjectRecord(params.stream_options) ? params.stream_options : {};
4034
+ return {
4035
+ ...params,
4036
+ stream_options: {
4037
+ ...streamOptions,
4038
+ include_usage: true
4039
+ }
4040
+ };
4041
+ }
4042
+ function deferUsageFinalization(ctx) {
4043
+ return ctx.deferFinalization?.() ?? (() => Promise.resolve());
4044
+ }
4045
+ function wrapStreamForUsageTap(source, finalize, ctx, onChunk) {
2969
4046
  const getIterator = source[Symbol.asyncIterator];
2970
4047
  if (typeof getIterator !== "function") {
2971
4048
  throw new TypeError("Stream is not async iterable");
2972
4049
  }
2973
4050
  const iterator = getIterator.call(source);
2974
4051
  let completed = false;
2975
- const invokeFinalize = async () => {
4052
+ const invokeFinalize = async (termination, error) => {
2976
4053
  if (completed) return;
2977
4054
  completed = true;
4055
+ if (termination === "cancel" || termination === "manual") {
4056
+ ctx.setError({
4057
+ code: "STREAM_ABORTED",
4058
+ message: termination === "cancel" ? "Provider stream consumption was cancelled before completion" : "Provider stream was finalized before completion"
4059
+ });
4060
+ } else if (termination === "error") {
4061
+ ctx.setError({
4062
+ code: "VENDOR_ERROR",
4063
+ message: error instanceof Error ? error.message : String(error)
4064
+ });
4065
+ }
2978
4066
  try {
2979
- await finalize();
2980
- } catch (error) {
4067
+ await finalize(termination);
4068
+ } catch (error2) {
2981
4069
  ctx.setError({
2982
4070
  code: "USAGE_FINALIZE_ERROR",
2983
- message: error instanceof Error ? error.message : String(error)
4071
+ message: error2 instanceof Error ? error2.message : String(error2)
2984
4072
  });
2985
- throw error;
4073
+ throw error2;
2986
4074
  }
2987
4075
  };
2988
4076
  const prototype = Object.getPrototypeOf(source) ?? Object.prototype;
@@ -3006,12 +4094,15 @@ function wrapStreamForUsageTap(source, finalize, ctx) {
3006
4094
  value: async (...args) => {
3007
4095
  try {
3008
4096
  const result = await iterator.next(...args);
4097
+ if (!result.done) {
4098
+ onChunk?.(result.value);
4099
+ }
3009
4100
  if (result.done) {
3010
- await invokeFinalize();
4101
+ await invokeFinalize("complete");
3011
4102
  }
3012
4103
  return result;
3013
4104
  } catch (error) {
3014
- await invokeFinalize().catch(() => void 0);
4105
+ await invokeFinalize("error", error).catch(() => void 0);
3015
4106
  throw error;
3016
4107
  }
3017
4108
  },
@@ -3020,39 +4111,49 @@ function wrapStreamForUsageTap(source, finalize, ctx) {
3020
4111
  });
3021
4112
  Object.defineProperty(wrapped, "return", {
3022
4113
  value: async (value) => {
3023
- if (typeof iterator.return === "function") {
3024
- const rawResult = await iterator.return(value);
3025
- if (!isIteratorResult(rawResult)) {
3026
- throw new TypeError("Iterator.return() returned an invalid result");
4114
+ try {
4115
+ if (typeof iterator.return === "function") {
4116
+ const rawResult = await iterator.return(value);
4117
+ if (!isIteratorResult(rawResult)) {
4118
+ throw new TypeError("Iterator.return() returned an invalid result");
4119
+ }
4120
+ await invokeFinalize("cancel");
4121
+ return rawResult;
3027
4122
  }
3028
- await invokeFinalize();
3029
- return rawResult;
4123
+ await invokeFinalize("cancel");
4124
+ return { done: true, value };
4125
+ } catch (error) {
4126
+ await invokeFinalize("error", error).catch(() => void 0);
4127
+ throw error;
3030
4128
  }
3031
- await invokeFinalize();
3032
- return { done: true, value };
3033
4129
  },
3034
4130
  configurable: true,
3035
4131
  writable: true
3036
4132
  });
3037
4133
  Object.defineProperty(wrapped, "throw", {
3038
4134
  value: async (error) => {
3039
- if (typeof iterator.throw === "function") {
3040
- const rawResult = await iterator.throw(error);
3041
- if (!isIteratorResult(rawResult)) {
3042
- throw new TypeError("Iterator.throw() returned an invalid result");
4135
+ try {
4136
+ if (typeof iterator.throw === "function") {
4137
+ const rawResult = await iterator.throw(error);
4138
+ if (!isIteratorResult(rawResult)) {
4139
+ throw new TypeError("Iterator.throw() returned an invalid result");
4140
+ }
4141
+ await invokeFinalize("error", error);
4142
+ return rawResult;
3043
4143
  }
3044
- await invokeFinalize();
3045
- return rawResult;
4144
+ await invokeFinalize("error", error);
4145
+ throw error;
4146
+ } catch (thrownError) {
4147
+ await invokeFinalize("error", thrownError).catch(() => void 0);
4148
+ throw thrownError;
3046
4149
  }
3047
- await invokeFinalize();
3048
- throw error;
3049
4150
  },
3050
4151
  configurable: true,
3051
4152
  writable: true
3052
4153
  });
3053
4154
  Object.defineProperty(wrapped, "__usageTapFinalize", {
3054
4155
  value: async () => {
3055
- await invokeFinalize();
4156
+ await invokeFinalize("manual");
3056
4157
  },
3057
4158
  configurable: true
3058
4159
  });
@@ -3069,6 +4170,7 @@ exports.streamOpenAIRoute = streamOpenAIRoute;
3069
4170
  exports.toNextResponse = toNextResponse;
3070
4171
  exports.withCompression = withCompression;
3071
4172
  exports.withMetering = withMetering;
4173
+ exports.withSampling = withSampling;
3072
4174
  exports.wrapOpenAI = wrapOpenAI;
3073
4175
  //# sourceMappingURL=openai.cjs.map
3074
4176
  //# sourceMappingURL=openai.cjs.map