@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
@@ -240,6 +240,11 @@ async function compressMessagesWithUsageTap(options) {
240
240
  aggressiveness,
241
241
  "UsageTap prompt message compression"
242
242
  );
243
+ if (options.latencyBudgetMs !== void 0 && (!Number.isFinite(options.latencyBudgetMs) || options.latencyBudgetMs < 0)) {
244
+ throw new Error(
245
+ "UsageTap prompt message compression latencyBudgetMs must be a non-negative number"
246
+ );
247
+ }
243
248
  const original = stableStringifyInput(options.input);
244
249
  const headers = {
245
250
  "content-type": "application/json"
@@ -254,7 +259,15 @@ async function compressMessagesWithUsageTap(options) {
254
259
  headers,
255
260
  body: JSON.stringify({
256
261
  ...cloneInputRecord(options.input),
257
- compression_settings: { aggressiveness }
262
+ compression_settings: {
263
+ aggressiveness,
264
+ ...options.mode === void 0 ? {} : { mode: options.mode },
265
+ ...options.latencyBudgetMs === void 0 ? {} : { latency_budget_ms: options.latencyBudgetMs },
266
+ ...options.compactEmptyUserMessages === void 0 ? {} : { compact_empty_user_messages: options.compactEmptyUserMessages },
267
+ ...options.compactDuplicateUserTextParts === void 0 ? {} : {
268
+ compact_duplicate_user_text_parts: options.compactDuplicateUserTextParts
269
+ }
270
+ }
258
271
  }),
259
272
  signal: options.signal
260
273
  }
@@ -750,10 +763,369 @@ function scalarToToon(value) {
750
763
  return JSON.stringify(text);
751
764
  }
752
765
 
766
+ // src/resources.ts
767
+ var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
768
+ var DEFAULT_GATEWAY_BASE_URL = "https://gateway.usagetap.com";
769
+ function normalizedBaseUrl(value) {
770
+ return `${value.replace(/\/+$/, "")}/`;
771
+ }
772
+ function errorMessage(payload, status) {
773
+ if (payload && typeof payload === "object") {
774
+ const record = payload;
775
+ const error = record.error;
776
+ if (error && typeof error === "object") {
777
+ const message2 = error.message;
778
+ if (typeof message2 === "string" && message2) return message2;
779
+ }
780
+ const message = record.message;
781
+ if (typeof message === "string" && message) return message;
782
+ }
783
+ return `UsageTap request failed with HTTP ${status}`;
784
+ }
785
+ function errorCode(status) {
786
+ if (status === 401 || status === 403) return "USAGETAP_AUTH_ERROR";
787
+ if (status === 429) return "USAGETAP_RATE_LIMITED";
788
+ if (status >= 500) return "USAGETAP_SERVER_ERROR";
789
+ return "USAGETAP_BAD_REQUEST";
790
+ }
791
+ var ResourceTransport = class {
792
+ baseUrl;
793
+ apiKey;
794
+ fetchImpl;
795
+ defaultHeaders;
796
+ sdkVersion;
797
+ constructor(baseUrl, config) {
798
+ this.baseUrl = normalizedBaseUrl(baseUrl);
799
+ this.apiKey = config.apiKey;
800
+ this.fetchImpl = config.fetchImpl;
801
+ this.defaultHeaders = config.headers ?? {};
802
+ this.sdkVersion = config.sdkVersion;
803
+ }
804
+ async request(request) {
805
+ const body = request.body === void 0 ? void 0 : JSON.stringify(request.body);
806
+ const headers = {
807
+ ...this.defaultHeaders,
808
+ accept: request.response === "data" ? CANONICAL_MEDIA_TYPE : request.response === "ndjson" ? "application/x-ndjson" : "application/json",
809
+ authorization: `Bearer ${this.apiKey}`,
810
+ "x-usage-sdk": `js/${this.sdkVersion}`,
811
+ ...body ? { "content-type": "application/json" } : {},
812
+ ...request.options?.idempotencyKey ? { "idempotency-key": request.options.idempotencyKey } : {},
813
+ ...request.options?.headers
814
+ };
815
+ let response;
816
+ try {
817
+ response = await this.fetchImpl(
818
+ new URL(request.path.replace(/^\/+/, ""), this.baseUrl),
819
+ {
820
+ method: request.method,
821
+ headers,
822
+ body,
823
+ signal: request.options?.signal
824
+ }
825
+ );
826
+ } catch (error) {
827
+ throw new UsageTapError(
828
+ "USAGETAP_NETWORK_ERROR",
829
+ "Failed to reach UsageTap",
830
+ { retryable: true, cause: error }
831
+ );
832
+ }
833
+ const requestId = response.headers.get("x-request-id") ?? response.headers.get("x-usage-correlation-id") ?? void 0;
834
+ const text = await response.text();
835
+ let payload;
836
+ if (text && request.response !== "ndjson") {
837
+ try {
838
+ payload = JSON.parse(text);
839
+ } catch (error) {
840
+ throw new UsageTapError(
841
+ "USAGETAP_INVALID_RESPONSE",
842
+ "UsageTap returned invalid JSON",
843
+ { status: response.status, correlationId: requestId, cause: error }
844
+ );
845
+ }
846
+ }
847
+ if (!response.ok) {
848
+ throw new UsageTapError(
849
+ errorCode(response.status),
850
+ errorMessage(payload, response.status),
851
+ {
852
+ status: response.status,
853
+ retryable: response.status === 429 || response.status >= 500,
854
+ correlationId: requestId,
855
+ details: payload && typeof payload === "object" ? payload : void 0
856
+ }
857
+ );
858
+ }
859
+ if (request.response === "ndjson") {
860
+ if (!text.trim()) return [];
861
+ try {
862
+ return text.trim().split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
863
+ } catch (error) {
864
+ throw new UsageTapError(
865
+ "USAGETAP_INVALID_RESPONSE",
866
+ "UsageTap returned invalid NDJSON",
867
+ { status: response.status, correlationId: requestId, cause: error }
868
+ );
869
+ }
870
+ }
871
+ if (request.response === "data") {
872
+ if (!payload || typeof payload !== "object" || !("data" in payload)) {
873
+ throw new UsageTapError(
874
+ "USAGETAP_INVALID_RESPONSE",
875
+ "UsageTap response missing data",
876
+ { status: response.status, correlationId: requestId }
877
+ );
878
+ }
879
+ return payload.data;
880
+ }
881
+ if (payload === void 0) {
882
+ throw new UsageTapError(
883
+ "USAGETAP_INVALID_RESPONSE",
884
+ "UsageTap response was empty",
885
+ { status: response.status, correlationId: requestId }
886
+ );
887
+ }
888
+ return payload;
889
+ }
890
+ };
891
+ function resourceId(value, keys, label) {
892
+ const id = typeof value === "string" ? value : keys.map((key) => value[key]).find((candidate) => Boolean(candidate?.trim()));
893
+ if (!id?.trim()) {
894
+ throw new UsageTapError(
895
+ "USAGETAP_BAD_REQUEST",
896
+ `${label} requires a non-empty ID`
897
+ );
898
+ }
899
+ return id.trim();
900
+ }
901
+ function terminalSummary(status) {
902
+ return status === "COMPLETE" || status === "FAILED";
903
+ }
904
+ function terminalGatewayBatch(status) {
905
+ return ["completed", "failed", "expired", "cancelled"].includes(status);
906
+ }
907
+ function validateWaitOptions(options) {
908
+ const pollIntervalMs = options.pollIntervalMs ?? 1500;
909
+ const timeoutMs = options.timeoutMs ?? 3 * 6e4;
910
+ if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 0) {
911
+ throw new UsageTapError(
912
+ "USAGETAP_BAD_REQUEST",
913
+ "pollIntervalMs must be a non-negative number"
914
+ );
915
+ }
916
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
917
+ throw new UsageTapError(
918
+ "USAGETAP_BAD_REQUEST",
919
+ "timeoutMs must be a positive number"
920
+ );
921
+ }
922
+ return { pollIntervalMs, timeoutMs };
923
+ }
924
+ var SummarizationResource = class {
925
+ summaries;
926
+ batches;
927
+ profiles;
928
+ measurements;
929
+ transport;
930
+ constructor(config) {
931
+ this.transport = new ResourceTransport(config.apiBaseUrl, config);
932
+ this.summaries = {
933
+ create: (params, options) => this.transport.request({
934
+ method: "POST",
935
+ path: "/v1/compression/summaries",
936
+ body: params,
937
+ options,
938
+ response: "data"
939
+ }),
940
+ retrieve: (jobId, options) => this.transport.request({
941
+ method: "GET",
942
+ path: `/v1/compression/jobs/${encodeURIComponent(
943
+ resourceId(jobId, ["jobId"], "summaries.retrieve")
944
+ )}`,
945
+ options,
946
+ response: "data"
947
+ }),
948
+ wait: (job, options) => this.waitForSummary(job, options)
949
+ };
950
+ this.batches = {
951
+ create: (params, options) => this.transport.request({
952
+ method: "POST",
953
+ path: "/v1/compression/batches",
954
+ body: params,
955
+ options,
956
+ response: "data"
957
+ }),
958
+ retrieve: (batchId, options) => this.transport.request({
959
+ method: "GET",
960
+ path: `/v1/compression/batches/${encodeURIComponent(
961
+ resourceId(batchId, ["batchId"], "summarization.batches.retrieve")
962
+ )}`,
963
+ options,
964
+ response: "data"
965
+ }),
966
+ wait: (batch, options) => this.waitForBatch(batch, options)
967
+ };
968
+ this.profiles = {
969
+ retrieve: (profile, options) => this.transport.request({
970
+ method: "GET",
971
+ path: `/v1/compression/profiles/${encodeURIComponent(
972
+ resourceId(profile, [], "summarization.profiles.retrieve")
973
+ )}`,
974
+ options,
975
+ response: "data"
976
+ })
977
+ };
978
+ this.measurements = {
979
+ create: (params, options) => this.transport.request({
980
+ method: "POST",
981
+ path: "/v1/compression/measurements",
982
+ body: params,
983
+ options,
984
+ response: "data"
985
+ })
986
+ };
987
+ }
988
+ async waitForSummary(value, options = {}) {
989
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
990
+ const deadline = Date.now() + timeoutMs;
991
+ let job = typeof value === "string" ? await this.summaries.retrieve(value, options) : value;
992
+ while (!terminalSummary(job.status)) {
993
+ if (Date.now() >= deadline) {
994
+ throw new UsageTapError(
995
+ "USAGETAP_RETRY_EXHAUSTED",
996
+ `Summarization job ${job.jobId} did not finish before timeout`,
997
+ { retryable: true }
998
+ );
999
+ }
1000
+ await sleep(pollIntervalMs, options.signal);
1001
+ job = await this.summaries.retrieve(job.jobId, options);
1002
+ }
1003
+ return job;
1004
+ }
1005
+ async waitForBatch(value, options = {}) {
1006
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
1007
+ const deadline = Date.now() + timeoutMs;
1008
+ let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
1009
+ while (!terminalSummary(batch.status)) {
1010
+ if (Date.now() >= deadline) {
1011
+ throw new UsageTapError(
1012
+ "USAGETAP_RETRY_EXHAUSTED",
1013
+ `Summarization batch ${batch.batchId} did not finish before timeout`,
1014
+ { retryable: true }
1015
+ );
1016
+ }
1017
+ await sleep(pollIntervalMs, options.signal);
1018
+ batch = await this.batches.retrieve(batch.batchId, options);
1019
+ }
1020
+ return batch;
1021
+ }
1022
+ };
1023
+ var GatewayResource = class {
1024
+ chat;
1025
+ /** Buffered OpenAI Responses-compatible requests. */
1026
+ responses;
1027
+ models;
1028
+ batches;
1029
+ transport;
1030
+ idempotencyGenerator;
1031
+ constructor(config) {
1032
+ this.transport = new ResourceTransport(
1033
+ config.gatewayBaseUrl ?? DEFAULT_GATEWAY_BASE_URL,
1034
+ config
1035
+ );
1036
+ this.idempotencyGenerator = config.idempotencyGenerator ?? createIdempotencyKey;
1037
+ this.chat = {
1038
+ completions: {
1039
+ create: (params, options) => this.transport.request({
1040
+ method: "POST",
1041
+ path: "/v1/chat/completions",
1042
+ body: params,
1043
+ options,
1044
+ response: "json"
1045
+ })
1046
+ }
1047
+ };
1048
+ this.responses = {
1049
+ create: (params, options) => this.transport.request({
1050
+ method: "POST",
1051
+ path: "/v1/responses",
1052
+ body: params,
1053
+ options,
1054
+ response: "json"
1055
+ })
1056
+ };
1057
+ this.models = {
1058
+ list: (options) => this.transport.request({
1059
+ method: "GET",
1060
+ path: "/v1/models",
1061
+ options,
1062
+ response: "json"
1063
+ })
1064
+ };
1065
+ this.batches = {
1066
+ create: (params, options = {}) => this.transport.request({
1067
+ method: "POST",
1068
+ path: "/v1/batches",
1069
+ body: params,
1070
+ options: {
1071
+ ...options,
1072
+ idempotencyKey: options.idempotencyKey ?? this.idempotencyGenerator()
1073
+ },
1074
+ response: "json"
1075
+ }),
1076
+ retrieve: (batchId, options) => this.transport.request({
1077
+ method: "GET",
1078
+ path: `/v1/batches/${encodeURIComponent(
1079
+ resourceId(batchId, ["id"], "gateway.batches.retrieve")
1080
+ )}`,
1081
+ options,
1082
+ response: "json"
1083
+ }),
1084
+ wait: (batch, options) => this.waitForBatch(batch, options),
1085
+ cancel: (batchId, options) => this.transport.request({
1086
+ method: "POST",
1087
+ path: `/v1/batches/${encodeURIComponent(
1088
+ resourceId(batchId, ["id"], "gateway.batches.cancel")
1089
+ )}/cancel`,
1090
+ options,
1091
+ response: "json"
1092
+ }),
1093
+ results: (batchId, options) => this.transport.request({
1094
+ method: "GET",
1095
+ path: `/v1/batches/${encodeURIComponent(
1096
+ resourceId(batchId, ["id"], "gateway.batches.results")
1097
+ )}/results`,
1098
+ options,
1099
+ response: "ndjson"
1100
+ })
1101
+ };
1102
+ }
1103
+ async waitForBatch(value, options = {}) {
1104
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
1105
+ const deadline = Date.now() + timeoutMs;
1106
+ let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
1107
+ while (!terminalGatewayBatch(batch.status)) {
1108
+ if (Date.now() >= deadline) {
1109
+ throw new UsageTapError(
1110
+ "USAGETAP_RETRY_EXHAUSTED",
1111
+ `Gateway batch ${batch.id} did not finish before timeout`,
1112
+ { retryable: true }
1113
+ );
1114
+ }
1115
+ await sleep(pollIntervalMs, options.signal);
1116
+ batch = await this.batches.retrieve(batch.id, options);
1117
+ }
1118
+ return batch;
1119
+ }
1120
+ };
1121
+
753
1122
  // src/client.ts
754
1123
  var CALL_BEGIN_PATH = "call_begin";
755
1124
  var CALL_END_PATH = "call_end";
756
1125
  var COMPRESS_PROMPT_PATH = "compress_prompt";
1126
+ var SAMPLES_PATH = "samples";
1127
+ var SAMPLING_SETTINGS_PATH = "sampling/settings";
1128
+ var SAMPLING_DECIDE_PATH = "sampling/decide";
757
1129
  var CHECK_USAGE_PATH = "customers/{customerId}/usage";
758
1130
  var CREATE_CUSTOMER_PATH = "customers";
759
1131
  var CHANGE_PLAN_PATH = "customers/{customerId}/change_plan";
@@ -764,11 +1136,16 @@ var CORRELATION_HEADER = "x-usage-correlation-id";
764
1136
  var IDEMPOTENCY_HEADER = "idempotency-key";
765
1137
  var SDK_HEADER = "x-usage-sdk";
766
1138
  var USER_AGENT = "UsageTapClient";
767
- var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
1139
+ var CANONICAL_MEDIA_TYPE2 = "application/vnd.usagetap.v1+json";
768
1140
  var DEFAULT_BASE_URL = "https://api.usagetap.com";
769
- var SDK_VERSION = "1.3.2" ;
1141
+ var DEFAULT_RUN_INACTIVITY_MS = 60 * 60 * 1e3;
1142
+ var SDK_VERSION = "1.7.0" ;
770
1143
  var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
771
1144
  var UsageTapClient = class {
1145
+ /** OpenAI-compatible chat, model, and native batch operations. */
1146
+ gateway;
1147
+ /** Published-profile context summarization operations. */
1148
+ summarization;
772
1149
  apiKey;
773
1150
  baseUrl;
774
1151
  fetchImpl;
@@ -793,6 +1170,11 @@ var UsageTapClient = class {
793
1170
  usageTapCompressionMessagesEndpoint;
794
1171
  usageTapCompressionModel;
795
1172
  usageTapCompressionAggressiveness;
1173
+ sampling;
1174
+ samplingSettingsCacheMs;
1175
+ circuitBreaker;
1176
+ circuitBreakerRuns = /* @__PURE__ */ new Map();
1177
+ samplingSettingsCache;
796
1178
  constructor(options = {}) {
797
1179
  const apiKey = options.apiKey?.trim() || readEnvironmentVariable("USAGETAP_API_KEY");
798
1180
  const baseUrl = options.baseUrl?.trim() || readEnvironmentVariable("USAGETAP_BASE_URL") || DEFAULT_BASE_URL;
@@ -815,10 +1197,21 @@ var UsageTapClient = class {
815
1197
  "A global fetch implementation was not found. Pass fetchImpl in UsageTapClientOptions."
816
1198
  );
817
1199
  }
818
- const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
819
- this.baseUrl = new URL(normalizedBaseUrl);
1200
+ const normalizedBaseUrl2 = normalizeBaseUrl(baseUrl);
1201
+ this.baseUrl = new URL(normalizedBaseUrl2);
820
1202
  this.apiKey = apiKey;
821
1203
  this.fetchImpl = wrapFetchImplementation(fetchCandidate, !options.fetchImpl);
1204
+ const resourceConfig = {
1205
+ apiKey,
1206
+ apiBaseUrl: normalizedBaseUrl2,
1207
+ gatewayBaseUrl: options.gatewayBaseUrl?.trim() || readEnvironmentVariable("USAGETAP_GATEWAY_URL"),
1208
+ fetchImpl: this.fetchImpl,
1209
+ headers: options.headers,
1210
+ sdkVersion: SDK_VERSION,
1211
+ idempotencyGenerator: options.idempotencyGenerator
1212
+ };
1213
+ this.gateway = new GatewayResource(resourceConfig);
1214
+ this.summarization = new SummarizationResource(resourceConfig);
822
1215
  this.defaultFeature = options.defaultFeature;
823
1216
  this.defaultTags = options.defaultTags?.length ? dedupeStrings(options.defaultTags) : void 0;
824
1217
  this.defaultHeaders = options.headers ? normalizeHeaderDictionary(options.headers) : {};
@@ -840,11 +1233,122 @@ var UsageTapClient = class {
840
1233
  this.usageTapCompressionMessagesEndpoint = options.usageTapCompressionMessagesEndpoint;
841
1234
  this.usageTapCompressionModel = options.usageTapCompressionModel;
842
1235
  this.usageTapCompressionAggressiveness = options.usageTapCompressionAggressiveness;
1236
+ this.sampling = options.sampling;
1237
+ this.samplingSettingsCacheMs = Number.isFinite(options.samplingSettingsCacheMs) ? Math.max(0, Number(options.samplingSettingsCacheMs)) : 5 * 60 * 1e3;
1238
+ if (options.circuitBreaker) {
1239
+ const maxCallsPerRun = options.circuitBreaker.maxCallsPerRun;
1240
+ if (!Number.isInteger(maxCallsPerRun) || maxCallsPerRun < 1) {
1241
+ throw new UsageTapError(
1242
+ "USAGETAP_BAD_REQUEST",
1243
+ "circuitBreaker.maxCallsPerRun must be a positive integer"
1244
+ );
1245
+ }
1246
+ const runInactivityMs = options.circuitBreaker.runInactivityMs ?? DEFAULT_RUN_INACTIVITY_MS;
1247
+ if (!Number.isFinite(runInactivityMs) || runInactivityMs < 1) {
1248
+ throw new UsageTapError(
1249
+ "USAGETAP_BAD_REQUEST",
1250
+ "circuitBreaker.runInactivityMs must be a positive number"
1251
+ );
1252
+ }
1253
+ this.circuitBreaker = {
1254
+ maxCallsPerRun,
1255
+ runInactivityMs
1256
+ };
1257
+ }
1258
+ }
1259
+ shouldSample(request, policy = this.sampling || void 0) {
1260
+ if (!policy) return false;
1261
+ const rate = Math.min(1, Math.max(0, Number(policy.rate) || 0));
1262
+ if (rate <= 0) return false;
1263
+ const customerId = request.customerId?.trim();
1264
+ if (customerId && policy.customers?.exclude?.includes(customerId)) return false;
1265
+ const feature = request.feature?.trim();
1266
+ if (feature && policy.features?.exclude?.includes(feature)) return false;
1267
+ const included = policy.features?.include?.filter(Boolean) ?? [];
1268
+ if (included.length > 0 && (!feature || !included.includes(feature))) return false;
1269
+ const minimum = Math.max(0, Math.round(policy.minInputTokens ?? 0));
1270
+ if (minimum > 0 && estimatePromptTokens(request.input) < minimum) return false;
1271
+ return (policy.random ?? Math.random)() < rate;
1272
+ }
1273
+ async getSamplingSettings(options = {}) {
1274
+ const now = Date.now();
1275
+ if (!options.forceRefresh && this.samplingSettingsCache && this.samplingSettingsCache.expiresAtMs > now) {
1276
+ return {
1277
+ result: { status: "ACCEPTED", code: "SAMPLING_SETTINGS_CACHED" },
1278
+ data: this.samplingSettingsCache.settings,
1279
+ correlationId: options.correlationId ?? "local-cache"
1280
+ };
1281
+ }
1282
+ const response = await this.requestGet(
1283
+ SAMPLING_SETTINGS_PATH,
1284
+ {
1285
+ signal: options.signal,
1286
+ headers: options.headers,
1287
+ retries: options.retries,
1288
+ correlationId: options.correlationId
1289
+ }
1290
+ );
1291
+ const serverCacheMs = Math.max(0, Number(response.data.cacheSeconds) || 0) * 1e3;
1292
+ const cacheMs = Math.min(this.samplingSettingsCacheMs, serverCacheMs);
1293
+ this.samplingSettingsCache = {
1294
+ settings: response.data,
1295
+ expiresAtMs: now + cacheMs
1296
+ };
1297
+ return response;
1298
+ }
1299
+ async shouldSampleAsync(request, policy) {
1300
+ if (policy) return this.shouldSample(request, policy);
1301
+ if (this.sampling === false) return false;
1302
+ if (this.sampling) return this.shouldSample(request, this.sampling);
1303
+ try {
1304
+ const settings = await this.getSamplingSettings();
1305
+ return this.shouldSample(request, settings.data);
1306
+ } catch {
1307
+ return false;
1308
+ }
1309
+ }
1310
+ async decideSample(request, options = {}) {
1311
+ const hasTokens = Number.isFinite(request.inputTokens) && Number(request.inputTokens) >= 0;
1312
+ const hasCharacters = Number.isFinite(request.inputCharacters) && Number(request.inputCharacters) >= 0;
1313
+ if (!hasTokens && !hasCharacters) {
1314
+ throw new UsageTapError(
1315
+ "USAGETAP_BAD_REQUEST",
1316
+ "decideSample requires inputTokens or inputCharacters"
1317
+ );
1318
+ }
1319
+ return this.request(
1320
+ SAMPLING_DECIDE_PATH,
1321
+ request,
1322
+ options
1323
+ );
1324
+ }
1325
+ async captureSample(request, options = {}) {
1326
+ if (!request || request.input === void 0) {
1327
+ throw new UsageTapError(
1328
+ "USAGETAP_BAD_REQUEST",
1329
+ "captureSample requires input"
1330
+ );
1331
+ }
1332
+ if (!request.provider?.trim()) {
1333
+ throw new UsageTapError(
1334
+ "USAGETAP_BAD_REQUEST",
1335
+ "captureSample requires provider"
1336
+ );
1337
+ }
1338
+ const sampleId = request.sampleId?.trim() || this.idempotencyGenerator();
1339
+ return this.request(
1340
+ SAMPLES_PATH,
1341
+ { ...request, sampleId },
1342
+ { ...options, idempotencyKey: sampleId }
1343
+ );
843
1344
  }
844
1345
  async beginCall(request, options = {}) {
845
1346
  const idempotencyKey = request.idempotencyKey ?? request.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
1347
+ this.reserveRunCall(request, idempotencyKey);
1348
+ const apiRequest = { ...request };
1349
+ delete apiRequest.runId;
846
1350
  const payload = {
847
- ...request,
1351
+ ...apiRequest,
848
1352
  feature: request.feature ?? this.defaultFeature,
849
1353
  tags: this.mergeTags(request.tags)
850
1354
  };
@@ -862,6 +1366,28 @@ var UsageTapClient = class {
862
1366
  );
863
1367
  return response;
864
1368
  }
1369
+ /**
1370
+ * Inspect a configured run circuit breaker without consuming another call.
1371
+ */
1372
+ canRunContinue(request) {
1373
+ const identity = this.resolveRunIdentity(request);
1374
+ if (!identity || !this.circuitBreaker) {
1375
+ throw new UsageTapError(
1376
+ "USAGETAP_BAD_REQUEST",
1377
+ "canRunContinue requires circuitBreaker configuration and a non-empty runId"
1378
+ );
1379
+ }
1380
+ this.expireInactiveRuns();
1381
+ const calls = this.circuitBreakerRuns.get(identity.key)?.calls ?? 0;
1382
+ return this.createCircuitBreakerDecision(identity.customerId, identity.runId, calls);
1383
+ }
1384
+ /**
1385
+ * Release local state after a workflow finishes. Returns true when state existed.
1386
+ */
1387
+ resetRun(request) {
1388
+ const identity = this.resolveRunIdentity(request);
1389
+ return identity ? this.circuitBreakerRuns.delete(identity.key) : false;
1390
+ }
865
1391
  async promptCompress(request, options = {}) {
866
1392
  if (!request?.callId) {
867
1393
  throw new UsageTapError(
@@ -959,6 +1485,10 @@ var UsageTapClient = class {
959
1485
  usageTapCompressionMessagesEndpoint: this.usageTapCompressionMessagesEndpoint,
960
1486
  aggressiveness: options.aggressiveness ?? this.aggressiveness,
961
1487
  usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
1488
+ mode: options.mode,
1489
+ latencyBudgetMs: options.latencyBudgetMs,
1490
+ compactEmptyUserMessages: options.compactEmptyUserMessages,
1491
+ compactDuplicateUserTextParts: options.compactDuplicateUserTextParts,
962
1492
  fetchImpl: this.fetchImpl,
963
1493
  signal: options.signal,
964
1494
  failOpen: options.failOpen
@@ -1000,14 +1530,29 @@ var UsageTapClient = class {
1000
1530
  callId: request.callId,
1001
1531
  feature: feature ?? this.defaultFeature,
1002
1532
  tags: tags ?? this.defaultTags,
1533
+ providerUsed: request.providerUsed,
1003
1534
  modelUsed: request.modelUsed,
1535
+ reasoningEffort: request.reasoningEffort,
1536
+ reasoningEffortSource: request.reasoningEffortSource,
1537
+ reasoningMode: request.reasoningMode,
1538
+ reasoningBudgetTokens: request.reasoningBudgetTokens,
1004
1539
  metrics: {
1005
1540
  inputTokens: request.inputTokens,
1006
1541
  responseTokens: request.responseTokens,
1007
1542
  cachedInputTokens: request.cachedInputTokens,
1543
+ cacheWriteInputTokens: request.cacheWriteInputTokens,
1544
+ cacheWrite5mInputTokens: request.cacheWrite5mInputTokens,
1545
+ cacheWrite1hInputTokens: request.cacheWrite1hInputTokens,
1008
1546
  reasoningTokens: request.reasoningTokens,
1009
1547
  searches: request.searches,
1010
1548
  audioSeconds: request.audioSeconds,
1549
+ imageInputCount: request.imageInputCount,
1550
+ imageInputTokens: request.imageInputTokens,
1551
+ imageOutputCount: request.imageOutputCount,
1552
+ imageOutputTokens: request.imageOutputTokens,
1553
+ audioInputTokens: request.audioInputTokens,
1554
+ cachedAudioInputTokens: request.cachedAudioInputTokens,
1555
+ audioOutputTokens: request.audioOutputTokens,
1011
1556
  costUsd: response.data.costUSD
1012
1557
  },
1013
1558
  correlationId: response.correlationId
@@ -1094,10 +1639,10 @@ var UsageTapClient = class {
1094
1639
  "incrementCustomMeter requires meterSlot"
1095
1640
  );
1096
1641
  }
1097
- if (!["CUSTOM1", "CUSTOM2"].includes(request.meterSlot)) {
1642
+ if (!["CUSTOM1", "CUSTOM2", "AGENTIC_API"].includes(request.meterSlot)) {
1098
1643
  throw new UsageTapError(
1099
1644
  "USAGETAP_BAD_REQUEST",
1100
- "meterSlot must be CUSTOM1 or CUSTOM2"
1645
+ "meterSlot must be CUSTOM1, CUSTOM2 or AGENTIC_API"
1101
1646
  );
1102
1647
  }
1103
1648
  if (typeof request.amount !== "number" || !Number.isFinite(request.amount) || request.amount <= 0) {
@@ -1112,6 +1657,15 @@ var UsageTapClient = class {
1112
1657
  meterSlot: request.meterSlot,
1113
1658
  amount: request.amount
1114
1659
  };
1660
+ if (request.customerUserId) {
1661
+ payload.customerUserId = request.customerUserId;
1662
+ }
1663
+ if (request.customerUserName) {
1664
+ payload.customerUserName = request.customerUserName;
1665
+ }
1666
+ if (request.customerUserEmail) {
1667
+ payload.customerUserEmail = request.customerUserEmail;
1668
+ }
1115
1669
  if (request.feature) {
1116
1670
  payload.feature = request.feature;
1117
1671
  }
@@ -1148,6 +1702,11 @@ var UsageTapClient = class {
1148
1702
  const beginPayload = idempotencyKey ? { ...beginRequest, idempotencyKey, idempotency: idempotencyKey } : { ...beginRequest };
1149
1703
  const beginResponse = await this.beginCall(beginPayload, options);
1150
1704
  let usage = {};
1705
+ const pricingMode = beginResponse.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
1706
+ if (pricingMode) {
1707
+ usage.pricingMode = pricingMode;
1708
+ usage.batch = pricingMode === "batch";
1709
+ }
1151
1710
  const initialStripeCustomerId = typeof beginResponse.data.stripeCustomerId === "string" ? beginResponse.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
1152
1711
  if (initialStripeCustomerId) {
1153
1712
  usage = { ...usage, stripeCustomerId: initialStripeCustomerId };
@@ -1156,6 +1715,28 @@ var UsageTapClient = class {
1156
1715
  let handlerResult;
1157
1716
  let handlerError;
1158
1717
  let endCallError;
1718
+ let finalizationDeferred = false;
1719
+ let finalizationPromise;
1720
+ const finalize = () => {
1721
+ if (!finalizationPromise) {
1722
+ finalizationPromise = this.endCall(
1723
+ {
1724
+ callId: beginResponse.data.callId,
1725
+ // Pass context for metric tracking
1726
+ customerId: beginRequest.customerId,
1727
+ feature: beginRequest.feature ?? this.defaultFeature,
1728
+ tags: beginRequest.tags ?? this.defaultTags,
1729
+ ...usage,
1730
+ error: errorPayload
1731
+ },
1732
+ {
1733
+ ...options,
1734
+ correlationId: beginResponse.correlationId
1735
+ }
1736
+ ).then(() => void 0);
1737
+ }
1738
+ return finalizationPromise;
1739
+ };
1159
1740
  const context = {
1160
1741
  begin: beginResponse,
1161
1742
  setUsage: (u) => {
@@ -1163,6 +1744,10 @@ var UsageTapClient = class {
1163
1744
  },
1164
1745
  setError: (err) => {
1165
1746
  errorPayload = err;
1747
+ },
1748
+ deferFinalization: () => {
1749
+ finalizationDeferred = true;
1750
+ return finalize;
1166
1751
  }
1167
1752
  };
1168
1753
  try {
@@ -1176,24 +1761,12 @@ var UsageTapClient = class {
1176
1761
  };
1177
1762
  }
1178
1763
  } finally {
1179
- try {
1180
- await this.endCall(
1181
- {
1182
- callId: beginResponse.data.callId,
1183
- // Pass context for metric tracking
1184
- customerId: beginRequest.customerId,
1185
- feature: beginRequest.feature ?? this.defaultFeature,
1186
- tags: beginRequest.tags ?? this.defaultTags,
1187
- ...usage,
1188
- error: errorPayload
1189
- },
1190
- {
1191
- ...options,
1192
- correlationId: beginResponse.correlationId
1193
- }
1194
- );
1195
- } catch (error) {
1196
- endCallError = error;
1764
+ if (handlerError || !finalizationDeferred) {
1765
+ try {
1766
+ await finalize();
1767
+ } catch (error) {
1768
+ endCallError = error;
1769
+ }
1197
1770
  }
1198
1771
  }
1199
1772
  if (handlerError) {
@@ -1215,17 +1788,86 @@ var UsageTapClient = class {
1215
1788
  toPromptCompressionTelemetry(result) {
1216
1789
  return {
1217
1790
  provider: result.provider,
1218
- originalCharacters: result.originalCharacters,
1219
- compressedCharacters: result.compressedCharacters,
1220
- savedCharacters: result.savedCharacters,
1221
1791
  originalTokens: result.originalTokens,
1222
1792
  compressedTokens: result.compressedTokens,
1223
1793
  savedTokens: result.savedTokens,
1224
1794
  tokenSavingsRatio: result.tokenSavingsRatio,
1225
- savingsRatio: result.savingsRatio,
1226
1795
  techniques: result.techniques
1227
1796
  };
1228
1797
  }
1798
+ reserveRunCall(request, idempotencyKey) {
1799
+ const identity = this.resolveRunIdentity(request);
1800
+ if (!identity || !this.circuitBreaker) return;
1801
+ this.expireInactiveRuns();
1802
+ const now = Date.now();
1803
+ const state = this.circuitBreakerRuns.get(identity.key) ?? {
1804
+ calls: 0,
1805
+ reservationKeys: /* @__PURE__ */ new Set(),
1806
+ lastSeenAtMs: now
1807
+ };
1808
+ const reservationKey = idempotencyKey ?? this.idempotencyGenerator();
1809
+ state.lastSeenAtMs = now;
1810
+ if (state.reservationKeys.has(reservationKey)) {
1811
+ this.circuitBreakerRuns.set(identity.key, state);
1812
+ return;
1813
+ }
1814
+ const decision = this.createCircuitBreakerDecision(
1815
+ identity.customerId,
1816
+ identity.runId,
1817
+ state.calls
1818
+ );
1819
+ if (!decision.allowed) {
1820
+ throw new UsageTapError(
1821
+ "USAGETAP_CIRCUIT_OPEN",
1822
+ `Run ${identity.runId} reached its ${decision.limit}-call circuit-breaker limit`,
1823
+ {
1824
+ details: {
1825
+ reason: decision.reason,
1826
+ customerId: identity.customerId,
1827
+ runId: identity.runId,
1828
+ calls: decision.calls,
1829
+ limit: decision.limit,
1830
+ remaining: decision.remaining
1831
+ }
1832
+ }
1833
+ );
1834
+ }
1835
+ state.calls += 1;
1836
+ state.reservationKeys.add(reservationKey);
1837
+ this.circuitBreakerRuns.set(identity.key, state);
1838
+ }
1839
+ resolveRunIdentity(request) {
1840
+ const customerId = request.customerId?.trim();
1841
+ const runId = request.runId?.trim();
1842
+ if (!customerId || !runId) return void 0;
1843
+ return {
1844
+ key: `${customerId}\0${runId}`,
1845
+ customerId,
1846
+ runId
1847
+ };
1848
+ }
1849
+ createCircuitBreakerDecision(customerId, runId, calls) {
1850
+ const limit = this.circuitBreaker?.maxCallsPerRun ?? 0;
1851
+ const allowed = calls < limit;
1852
+ return {
1853
+ allowed,
1854
+ ...allowed ? {} : { reason: "max_calls_per_run" },
1855
+ customerId,
1856
+ runId,
1857
+ calls,
1858
+ limit,
1859
+ remaining: Math.max(0, limit - calls)
1860
+ };
1861
+ }
1862
+ expireInactiveRuns() {
1863
+ if (!this.circuitBreaker || this.circuitBreakerRuns.size === 0) return;
1864
+ const expiredBefore = Date.now() - this.circuitBreaker.runInactivityMs;
1865
+ for (const [key, state] of this.circuitBreakerRuns) {
1866
+ if (state.lastSeenAtMs < expiredBefore) {
1867
+ this.circuitBreakerRuns.delete(key);
1868
+ }
1869
+ }
1870
+ }
1229
1871
  async request(path, payload, options) {
1230
1872
  const url = new URL(path, this.baseUrl).toString();
1231
1873
  const body = payload !== void 0 ? JSON.stringify(payload) : void 0;
@@ -1413,7 +2055,7 @@ var UsageTapClient = class {
1413
2055
  ...this.defaultHeaders,
1414
2056
  [SDK_HEADER]: `js/${SDK_VERSION}`,
1415
2057
  "content-type": "application/json",
1416
- accept: CANONICAL_MEDIA_TYPE
2058
+ accept: CANONICAL_MEDIA_TYPE2
1417
2059
  };
1418
2060
  if (!HAS_WINDOW) {
1419
2061
  headers["user-agent"] = `${USER_AGENT}/${SDK_VERSION}`;
@@ -1466,7 +2108,8 @@ var UsageTapClient = class {
1466
2108
  }
1467
2109
  toHttpError(status, payload, correlationId) {
1468
2110
  const code = mapStatusToErrorCode(status);
1469
- const retryable = isRetryableStatus(status);
2111
+ const apiCode = payload?.error?.code ?? payload?.result?.code ?? "UNKNOWN";
2112
+ const retryable = isRetryableStatus(status) || isRetryableApiCode(apiCode);
1470
2113
  const message = payload?.error?.message ?? payload?.result?.message ?? `UsageTap responded with HTTP ${status}`;
1471
2114
  return new UsageTapError(code, message, {
1472
2115
  status,
@@ -1499,7 +2142,7 @@ function isRetryableStatus(status) {
1499
2142
  }
1500
2143
  function isRetryableApiCode(code) {
1501
2144
  const normalized = code.toUpperCase();
1502
- return normalized.includes("TRANSIENT") || normalized.includes("RETRY") || normalized.includes("TIMEOUT") || normalized.includes("THROTTLE") || normalized.includes("RATE_LIMIT");
2145
+ return normalized === "PAYG_CONFLICT" || normalized.includes("TRANSIENT") || normalized.includes("RETRY") || normalized.includes("TIMEOUT") || normalized.includes("THROTTLE") || normalized.includes("RATE_LIMIT");
1503
2146
  }
1504
2147
  function mapApiCodeToError(code) {
1505
2148
  const normalized = code.toUpperCase();
@@ -1614,17 +2257,18 @@ var OpenAIPromptCompressionStats = class {
1614
2257
  }
1615
2258
  };
1616
2259
  function createOpenAIAdapter(init) {
1617
- const { client, usageTap } = init;
2260
+ const { client, usageTap, provider = "openai" } = init;
1618
2261
  return {
1619
2262
  async invoke(params) {
1620
2263
  const result = await usageTap.withUsage(
1621
2264
  params.begin,
1622
2265
  async (ctx) => {
2266
+ ctx.setUsage({ providerUsed: provider });
1623
2267
  const response = await params.call(client, {
1624
2268
  hints: ctx.begin.data.vendorHints,
1625
2269
  begin: ctx.begin
1626
2270
  });
1627
- tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx);
2271
+ tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx, provider);
1628
2272
  return {
1629
2273
  data: response,
1630
2274
  begin: ctx.begin
@@ -1638,24 +2282,29 @@ function createOpenAIAdapter(init) {
1638
2282
  const result = await usageTap.withUsage(
1639
2283
  params.begin,
1640
2284
  async (ctx) => {
2285
+ const settle = deferUsageFinalization(ctx);
2286
+ ctx.setUsage({ providerUsed: provider });
1641
2287
  const { stream, onComplete } = await params.call(client, {
1642
2288
  hints: ctx.begin.data.vendorHints,
1643
2289
  begin: ctx.begin
1644
2290
  });
1645
- const wrapped = wrapStreamForUsageTap(stream, async () => {
1646
- if (!onComplete) return;
2291
+ const wrapped = wrapStreamForUsageTap(stream, async (termination) => {
1647
2292
  try {
1648
- const maybeUsage = await onComplete();
1649
- if (maybeUsage) {
1650
- ctx.setUsage(maybeUsage);
2293
+ if (termination === "complete" && onComplete) {
2294
+ const maybeUsage = await onComplete();
2295
+ if (maybeUsage) {
2296
+ ctx.setUsage(maybeUsage);
2297
+ }
1651
2298
  }
1652
2299
  } catch (error) {
1653
2300
  ctx.setError({
1654
2301
  code: "USAGE_FINALIZE_ERROR",
1655
2302
  message: error instanceof Error ? error.message : String(error)
1656
2303
  });
2304
+ await settle();
1657
2305
  throw error;
1658
2306
  }
2307
+ await settle();
1659
2308
  }, ctx);
1660
2309
  const finalize = async () => {
1661
2310
  await wrapped.__usageTapFinalize?.();
@@ -1763,6 +2412,181 @@ async function pipeToResponse(stream, res, options = {}) {
1763
2412
  }
1764
2413
  }
1765
2414
  var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
2415
+ function withSampling(client, options = {}) {
2416
+ if (!client || !options) {
2417
+ throw new UsageTapError(
2418
+ "USAGETAP_BAD_REQUEST",
2419
+ "withSampling requires an OpenAI-compatible client and sampling options"
2420
+ );
2421
+ }
2422
+ const { apiKey, usageTapClient, provider = "openai", ...policy } = options;
2423
+ const localPolicy = typeof policy.rate === "number" ? policy : void 0;
2424
+ const usageTap = usageTapClient ?? new UsageTapClient({ apiKey, sampling: localPolicy });
2425
+ const wrapCreate = (create) => async (params, requestOptions) => {
2426
+ const { usageTap: callContextRaw, ...providerOptions } = requestOptions ?? {};
2427
+ const callContext = isObjectRecord(callContextRaw) ? callContextRaw : {};
2428
+ const streaming = params.stream === true;
2429
+ const decision = streaming ? Promise.resolve(false) : usageTap.shouldSampleAsync({
2430
+ customerId: readString(callContext.customerId),
2431
+ feature: readString(callContext.feature),
2432
+ input: params
2433
+ }, localPolicy);
2434
+ const startedAt = Date.now();
2435
+ try {
2436
+ const response = await create(
2437
+ params,
2438
+ Object.keys(providerOptions).length ? providerOptions : void 0
2439
+ );
2440
+ const selected = await decision;
2441
+ if (selected) {
2442
+ const record = isObjectRecord(response) ? response : {};
2443
+ await usageTap.captureSample({
2444
+ customerId: readString(callContext.customerId),
2445
+ feature: readString(callContext.feature),
2446
+ environment: readString(callContext.environment),
2447
+ tags: readStringArray(callContext.tags),
2448
+ provider,
2449
+ model: readString(record.model) ?? readString(params.model),
2450
+ input: params,
2451
+ output: response,
2452
+ usage: record.usage,
2453
+ latencyMs: Date.now() - startedAt
2454
+ }).catch(() => void 0);
2455
+ }
2456
+ return response;
2457
+ } catch (error) {
2458
+ const selected = await decision;
2459
+ if (selected) {
2460
+ await usageTap.captureSample({
2461
+ customerId: readString(callContext.customerId),
2462
+ feature: readString(callContext.feature),
2463
+ environment: readString(callContext.environment),
2464
+ tags: readStringArray(callContext.tags),
2465
+ provider,
2466
+ model: readString(params.model),
2467
+ input: params,
2468
+ latencyMs: Date.now() - startedAt,
2469
+ error: serializeSamplingError(error)
2470
+ }).catch(() => void 0);
2471
+ }
2472
+ throw error;
2473
+ }
2474
+ };
2475
+ const chat = client.chat?.completions ? new Proxy(client.chat, {
2476
+ get(target, prop, receiver) {
2477
+ if (prop !== "completions") return safeReflectGet(target, prop, receiver);
2478
+ const completions = target.completions;
2479
+ return new Proxy(completions, {
2480
+ get(completionTarget, completionProp, completionReceiver) {
2481
+ if (completionProp === "create") {
2482
+ return wrapCreate(
2483
+ completionTarget.create.bind(completionTarget)
2484
+ );
2485
+ }
2486
+ return safeReflectGet(
2487
+ completionTarget,
2488
+ completionProp,
2489
+ completionReceiver
2490
+ );
2491
+ }
2492
+ });
2493
+ }
2494
+ }) : void 0;
2495
+ const responses = typeof client.responses !== "undefined" && client.responses ? new Proxy(client.responses, {
2496
+ get(target, prop, receiver) {
2497
+ if (prop === "create") {
2498
+ const create = Reflect.get(target, prop, receiver);
2499
+ return wrapCreate(create.bind(target));
2500
+ }
2501
+ return safeReflectGet(target, prop, receiver);
2502
+ }
2503
+ }) : void 0;
2504
+ return new Proxy(client, {
2505
+ get(target, prop, receiver) {
2506
+ if (prop === "chat" && chat) return chat;
2507
+ if (prop === "responses" && responses) return responses;
2508
+ if (prop === "unwrap") return () => target;
2509
+ return safeReflectGet(target, prop, receiver);
2510
+ }
2511
+ });
2512
+ }
2513
+ function safeReflectGet(target, prop, receiver) {
2514
+ return Reflect.get(target, prop, receiver);
2515
+ }
2516
+ function readStringArray(value) {
2517
+ if (!Array.isArray(value)) return void 0;
2518
+ const strings = value.filter((item) => typeof item === "string");
2519
+ return strings.length ? strings : void 0;
2520
+ }
2521
+ function readString(value) {
2522
+ return typeof value === "string" && value.trim() ? value : void 0;
2523
+ }
2524
+ function serializeSamplingError(error) {
2525
+ if (error instanceof Error) {
2526
+ return { name: error.name, message: error.message };
2527
+ }
2528
+ return { message: String(error) };
2529
+ }
2530
+ function normalizeMeteredOpenAISampling(options) {
2531
+ if (!options) return void 0;
2532
+ if (options === true) return { provider: "openai" };
2533
+ const { provider = "openai", ...policyFields } = options;
2534
+ return {
2535
+ provider,
2536
+ policy: typeof policyFields.rate === "number" ? policyFields : void 0
2537
+ };
2538
+ }
2539
+ function startMeteredOpenAISampleDecision({
2540
+ usageTap,
2541
+ sampling,
2542
+ beginRequest,
2543
+ input
2544
+ }) {
2545
+ if (!sampling) return Promise.resolve(false);
2546
+ return usageTap.shouldSampleAsync(
2547
+ {
2548
+ customerId: beginRequest.customerId,
2549
+ feature: beginRequest.feature,
2550
+ input
2551
+ },
2552
+ sampling.policy
2553
+ );
2554
+ }
2555
+ async function captureMeteredOpenAISample({
2556
+ usageTap,
2557
+ sampling,
2558
+ decision,
2559
+ ctx,
2560
+ beginRequest,
2561
+ input,
2562
+ response,
2563
+ error,
2564
+ startedAt
2565
+ }) {
2566
+ if (!sampling) return;
2567
+ let selected = false;
2568
+ try {
2569
+ selected = await decision;
2570
+ } catch {
2571
+ return;
2572
+ }
2573
+ if (!selected) return;
2574
+ const record = isObjectRecord(response) ? response : {};
2575
+ await usageTap.captureSample({
2576
+ sampleId: ctx.begin.data.callId,
2577
+ callId: ctx.begin.data.callId,
2578
+ customerId: beginRequest.customerId,
2579
+ feature: beginRequest.feature,
2580
+ tags: beginRequest.tags,
2581
+ provider: sampling.provider,
2582
+ model: readString(record.model) ?? readString(input.model),
2583
+ input,
2584
+ ...response === void 0 ? {} : { output: response },
2585
+ usage: record.usage,
2586
+ latencyMs: Date.now() - startedAt,
2587
+ ...error === void 0 ? {} : { error: serializeSamplingError(error) }
2588
+ }).catch(() => void 0);
2589
+ }
1766
2590
  function withCompression(client, options = {}) {
1767
2591
  if (!client) {
1768
2592
  throw new UsageTapError(
@@ -1773,11 +2597,13 @@ function withCompression(client, options = {}) {
1773
2597
  const {
1774
2598
  apiKey,
1775
2599
  usageTapClient,
2600
+ sampling,
1776
2601
  ...compressionOverrides
1777
2602
  } = options;
1778
2603
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
1779
2604
  const compression = {
1780
2605
  provider: "usagetap",
2606
+ roles: { user: { enabled: true, aggressiveness: 0.2 } },
1781
2607
  minContextTokens: DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS,
1782
2608
  ...compressionOverrides
1783
2609
  };
@@ -1787,7 +2613,7 @@ function withCompression(client, options = {}) {
1787
2613
  usageTap,
1788
2614
  compression
1789
2615
  ) : void 0;
1790
- return new Proxy(client, {
2616
+ const compressed = new Proxy(client, {
1791
2617
  get(target, prop, receiver) {
1792
2618
  if (prop === "chat" && proxiedChat) {
1793
2619
  return proxiedChat;
@@ -1801,6 +2627,11 @@ function withCompression(client, options = {}) {
1801
2627
  return Reflect.get(target, prop, receiver);
1802
2628
  }
1803
2629
  });
2630
+ return sampling ? withSampling(compressed, {
2631
+ ...sampling === true ? {} : sampling,
2632
+ apiKey,
2633
+ usageTapClient
2634
+ }) : compressed;
1804
2635
  }
1805
2636
  function withMetering(client, customer) {
1806
2637
  const config = typeof customer === "string" ? { customerId: customer } : customer;
@@ -1815,6 +2646,8 @@ function withMetering(client, customer) {
1815
2646
  usageTapClient,
1816
2647
  applyVendorHints,
1817
2648
  promptCompression,
2649
+ sampling,
2650
+ provider,
1818
2651
  ...defaultContext
1819
2652
  } = config;
1820
2653
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
@@ -1822,7 +2655,9 @@ function withMetering(client, customer) {
1822
2655
  return wrapOpenAI(client, usageTap, {
1823
2656
  defaultContext,
1824
2657
  applyVendorHints,
1825
- promptCompression: normalizedCompression
2658
+ promptCompression: normalizedCompression,
2659
+ sampling,
2660
+ provider
1826
2661
  });
1827
2662
  }
1828
2663
  function wrapOpenAI(client, usageTap, options = {}) {
@@ -1832,6 +2667,8 @@ function wrapOpenAI(client, usageTap, options = {}) {
1832
2667
  const defaultContext = options.defaultContext;
1833
2668
  const applyVendorHints = options.applyVendorHints !== false;
1834
2669
  const defaultPromptCompression = normalizePromptCompressionOptions(options.promptCompression);
2670
+ const defaultSampling = normalizeMeteredOpenAISampling(options.sampling);
2671
+ const provider = options.provider ?? "openai";
1835
2672
  const promptCompressionStats = new OpenAIPromptCompressionStats();
1836
2673
  const proxiedChat = client.chat ? createChatProxy(
1837
2674
  client.chat,
@@ -1839,7 +2676,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
1839
2676
  defaultContext,
1840
2677
  applyVendorHints,
1841
2678
  defaultPromptCompression,
1842
- promptCompressionStats
2679
+ promptCompressionStats,
2680
+ defaultSampling,
2681
+ provider
1843
2682
  ) : void 0;
1844
2683
  const proxiedResponses = typeof client.responses !== "undefined" ? createResponsesProxy(
1845
2684
  client.responses,
@@ -1847,7 +2686,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
1847
2686
  defaultContext,
1848
2687
  applyVendorHints,
1849
2688
  defaultPromptCompression,
1850
- promptCompressionStats
2689
+ promptCompressionStats,
2690
+ defaultSampling,
2691
+ provider
1851
2692
  ) : void 0;
1852
2693
  const handler = {
1853
2694
  get(target, prop, receiver) {
@@ -1915,14 +2756,16 @@ function createCompressionOnlyResponsesProxy(resource, usageTap, compression) {
1915
2756
  }
1916
2757
  });
1917
2758
  }
1918
- function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2759
+ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1919
2760
  const completions = createChatCompletionsProxy(
1920
2761
  resource.completions,
1921
2762
  usageTap,
1922
2763
  defaultContext,
1923
2764
  applyVendorHints,
1924
2765
  defaultPromptCompression,
1925
- promptCompressionStats
2766
+ promptCompressionStats,
2767
+ defaultSampling,
2768
+ provider
1926
2769
  );
1927
2770
  const handler = {
1928
2771
  get(target, prop, receiver) {
@@ -1934,7 +2777,7 @@ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, d
1934
2777
  };
1935
2778
  return new Proxy(resource, handler);
1936
2779
  }
1937
- function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2780
+ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1938
2781
  if (!resource || typeof resource !== "object") {
1939
2782
  return void 0;
1940
2783
  }
@@ -1949,9 +2792,20 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1949
2792
  withUsage,
1950
2793
  promptCompression
1951
2794
  } = splitUsageOptions(options);
1952
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2795
+ const beginRequest = responsesBeginRequest(
2796
+ resolveBeginRequest(defaultContext, usageContext),
2797
+ params
2798
+ );
1953
2799
  const wantsStream = isStreamingRequest(params);
1954
2800
  return usageTap.withUsage(beginRequest, async (ctx) => {
2801
+ const settle = wantsStream ? deferUsageFinalization(ctx) : void 0;
2802
+ const sampleStartedAt = Date.now();
2803
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2804
+ usageTap,
2805
+ sampling: defaultSampling,
2806
+ beginRequest,
2807
+ input: params
2808
+ });
1955
2809
  const hintedParams = applyVendorHints ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints) : params;
1956
2810
  const finalParams = await compressResponsesParamsForCall({
1957
2811
  params: hintedParams,
@@ -1963,25 +2817,67 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1963
2817
  withUsage,
1964
2818
  operation: "responses.create"
1965
2819
  });
2820
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
1966
2821
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
1967
2822
  if (wantsStream) {
1968
2823
  const apiPromise2 = originalCreate(finalParams, request);
1969
2824
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
1970
2825
  ensureAsyncIterable(rawStream, "responses.create");
1971
- const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
1972
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
1973
- if (usage) {
1974
- ctx.setUsage(usage);
2826
+ const wrappedStream = wrapStreamForUsageTap(rawStream, async (termination) => {
2827
+ try {
2828
+ if (termination === "complete") {
2829
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2830
+ if (usage) {
2831
+ ctx.setUsage(usage);
2832
+ }
2833
+ }
2834
+ } catch (error) {
2835
+ ctx.setError({
2836
+ code: "USAGE_FINALIZE_ERROR",
2837
+ message: error instanceof Error ? error.message : String(error)
2838
+ });
2839
+ throw error;
2840
+ } finally {
2841
+ await settle?.();
1975
2842
  }
1976
- }, ctx);
2843
+ }, ctx, (chunk) => {
2844
+ tryInferUsageFromStreamChunk(
2845
+ chunk,
2846
+ ctx.begin.data.vendorHints,
2847
+ ctx,
2848
+ provider
2849
+ );
2850
+ });
1977
2851
  return wrappedStream;
1978
2852
  });
1979
2853
  return wrappedPromise2;
1980
2854
  }
1981
2855
  const apiPromise = originalCreate(finalParams, request);
1982
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
1983
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2856
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2857
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2858
+ await captureMeteredOpenAISample({
2859
+ usageTap,
2860
+ sampling: defaultSampling,
2861
+ decision: sampleDecision,
2862
+ ctx,
2863
+ beginRequest,
2864
+ input: params,
2865
+ response,
2866
+ startedAt: sampleStartedAt
2867
+ });
1984
2868
  return response;
2869
+ }, async (error) => {
2870
+ await captureMeteredOpenAISample({
2871
+ usageTap,
2872
+ sampling: defaultSampling,
2873
+ decision: sampleDecision,
2874
+ ctx,
2875
+ beginRequest,
2876
+ input: params,
2877
+ error,
2878
+ startedAt: sampleStartedAt
2879
+ });
2880
+ throw error;
1985
2881
  });
1986
2882
  return wrappedPromise;
1987
2883
  }, withUsage);
@@ -1996,7 +2892,7 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1996
2892
  };
1997
2893
  return new Proxy(resource, handler);
1998
2894
  }
1999
- function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2895
+ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
2000
2896
  const originalCreate = resource.create.bind(resource);
2001
2897
  const streamCandidate = resource.stream;
2002
2898
  const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(resource) : void 0;
@@ -2010,8 +2906,16 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2010
2906
  const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2011
2907
  const wantsStream = isStreamingRequest(params);
2012
2908
  return usageTap.withUsage(beginRequest, async (ctx) => {
2909
+ const settle = wantsStream ? deferUsageFinalization(ctx) : void 0;
2910
+ const sampleStartedAt = Date.now();
2911
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2912
+ usageTap,
2913
+ sampling: defaultSampling,
2914
+ beginRequest,
2915
+ input: params
2916
+ });
2013
2917
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2014
- const finalParams = await compressChatParamsForCall({
2918
+ const compressedParams = await compressChatParamsForCall({
2015
2919
  params: hintedParams,
2016
2920
  usageTap,
2017
2921
  ctx,
@@ -2021,25 +2925,68 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2021
2925
  withUsage,
2022
2926
  operation: "chat.completions.create"
2023
2927
  });
2928
+ const finalParams = wantsStream ? ensureOpenAIStreamUsage(compressedParams) : compressedParams;
2929
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2024
2930
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2025
2931
  if (wantsStream) {
2026
2932
  const apiPromise2 = originalCreate(finalParams, request);
2027
2933
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
2028
2934
  ensureAsyncIterable(rawStream, "chat.completions.create");
2029
- const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
2030
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2031
- if (usage) {
2032
- ctx.setUsage(usage);
2935
+ const wrappedStream2 = wrapStreamForUsageTap(rawStream, async (termination) => {
2936
+ try {
2937
+ if (termination === "complete") {
2938
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2939
+ if (usage) {
2940
+ ctx.setUsage(usage);
2941
+ }
2942
+ }
2943
+ } catch (error) {
2944
+ ctx.setError({
2945
+ code: "USAGE_FINALIZE_ERROR",
2946
+ message: error instanceof Error ? error.message : String(error)
2947
+ });
2948
+ throw error;
2949
+ } finally {
2950
+ await settle?.();
2033
2951
  }
2034
- }, ctx);
2952
+ }, ctx, (chunk) => {
2953
+ tryInferUsageFromStreamChunk(
2954
+ chunk,
2955
+ ctx.begin.data.vendorHints,
2956
+ ctx,
2957
+ provider
2958
+ );
2959
+ });
2035
2960
  return wrappedStream2;
2036
2961
  });
2037
2962
  return wrappedPromise2;
2038
2963
  }
2039
2964
  const apiPromise = originalCreate(finalParams, request);
2040
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
2041
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2965
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2966
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2967
+ await captureMeteredOpenAISample({
2968
+ usageTap,
2969
+ sampling: defaultSampling,
2970
+ decision: sampleDecision,
2971
+ ctx,
2972
+ beginRequest,
2973
+ input: params,
2974
+ response,
2975
+ startedAt: sampleStartedAt
2976
+ });
2042
2977
  return response;
2978
+ }, async (error) => {
2979
+ await captureMeteredOpenAISample({
2980
+ usageTap,
2981
+ sampling: defaultSampling,
2982
+ decision: sampleDecision,
2983
+ ctx,
2984
+ beginRequest,
2985
+ input: params,
2986
+ error,
2987
+ startedAt: sampleStartedAt
2988
+ });
2989
+ throw error;
2043
2990
  });
2044
2991
  return wrappedPromise;
2045
2992
  }, withUsage);
@@ -2053,8 +3000,9 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2053
3000
  } = splitUsageOptions(options);
2054
3001
  const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2055
3002
  return usageTap.withUsage(beginRequest, async (ctx) => {
3003
+ const settle = deferUsageFinalization(ctx);
2056
3004
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2057
- const finalParams = await compressChatParamsForCall({
3005
+ const compressedParams = await compressChatParamsForCall({
2058
3006
  params: hintedParams,
2059
3007
  usageTap,
2060
3008
  ctx,
@@ -2064,16 +3012,41 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2064
3012
  withUsage,
2065
3013
  operation: "chat.completions.stream"
2066
3014
  });
3015
+ const finalParams = ensureOpenAIStreamUsage(compressedParams);
3016
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2067
3017
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2068
3018
  const apiPromise = originalStream(finalParams, request);
2069
3019
  const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
2070
3020
  ensureAsyncIterable(rawStream, "chat.completions.stream");
2071
- const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
2072
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2073
- if (usage) {
2074
- ctx.setUsage(usage);
3021
+ const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async (termination) => {
3022
+ try {
3023
+ if (termination === "complete") {
3024
+ const usage = await extractUsageFromStream(
3025
+ rawStream,
3026
+ ctx.begin.data.vendorHints,
3027
+ provider
3028
+ );
3029
+ if (usage) {
3030
+ ctx.setUsage(usage);
3031
+ }
3032
+ }
3033
+ } catch (error) {
3034
+ ctx.setError({
3035
+ code: "USAGE_FINALIZE_ERROR",
3036
+ message: error instanceof Error ? error.message : String(error)
3037
+ });
3038
+ throw error;
3039
+ } finally {
3040
+ await settle();
2075
3041
  }
2076
- }, ctx);
3042
+ }, ctx, (chunk) => {
3043
+ tryInferUsageFromStreamChunk(
3044
+ chunk,
3045
+ ctx.begin.data.vendorHints,
3046
+ ctx,
3047
+ provider
3048
+ );
3049
+ });
2077
3050
  return wrappedStreamInner;
2078
3051
  });
2079
3052
  return wrappedPromise;
@@ -2195,6 +3168,10 @@ async function compressChatParams(params, usageTap, compression, signal) {
2195
3168
  const result = await usageTap.compressPromptMessages(source, {
2196
3169
  provider: "usagetap",
2197
3170
  failOpen: compression.failOpen,
3171
+ mode: compression.mode,
3172
+ latencyBudgetMs: compression.latencyBudgetMs,
3173
+ compactEmptyUserMessages: compression.compactEmptyUserMessages,
3174
+ compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
2198
3175
  aggressiveness: resolveMessageEndpointAggressiveness(compression),
2199
3176
  signal
2200
3177
  });
@@ -2664,19 +3641,52 @@ function resolveBeginRequest(defaults, override) {
2664
3641
  if (requested) begin.requested = requested;
2665
3642
  const feature = current.feature ?? base.feature;
2666
3643
  if (feature) begin.feature = feature;
3644
+ const runId = current.runId ?? base.runId;
3645
+ if (runId) begin.runId = runId;
2667
3646
  const idempotency = current.idempotency ?? base.idempotency;
2668
3647
  if (idempotency) begin.idempotency = idempotency;
2669
3648
  const customerName = current.customerName ?? base.customerName;
2670
3649
  if (customerName) begin.customerName = customerName;
2671
3650
  const customerEmail = current.customerEmail ?? base.customerEmail;
2672
3651
  if (customerEmail) begin.customerEmail = customerEmail;
3652
+ const customerUserId = current.customerUserId ?? base.customerUserId;
3653
+ if (customerUserId) begin.customerUserId = customerUserId;
3654
+ const customerUserName = current.customerUserName ?? base.customerUserName;
3655
+ if (customerUserName) begin.customerUserName = customerUserName;
3656
+ const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
3657
+ if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
3658
+ const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
3659
+ if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
3660
+ const batch = current.batch ?? base.batch;
3661
+ if (typeof batch === "boolean") begin.batch = batch;
3662
+ const pricingMode = current.pricingMode ?? base.pricingMode;
3663
+ if (pricingMode) begin.pricingMode = pricingMode;
2673
3664
  if (tags?.length) {
2674
3665
  begin.tags = tags;
2675
3666
  }
2676
3667
  return begin;
2677
3668
  }
2678
- function transformApiPromise(apiPromise, onResolve) {
2679
- const resolvedPromise = Promise.resolve(apiPromise).then(onResolve);
3669
+ function responsesBeginRequest(begin, params) {
3670
+ if (!responsesRequestUsesWebSearch(params)) return begin;
3671
+ return {
3672
+ ...begin,
3673
+ requested: {
3674
+ ...begin.requested ?? {},
3675
+ search: true
3676
+ }
3677
+ };
3678
+ }
3679
+ function responsesRequestUsesWebSearch(params) {
3680
+ if (!params || typeof params !== "object") return false;
3681
+ const tools = params.tools;
3682
+ return Array.isArray(tools) && tools.some((tool) => {
3683
+ if (!tool || typeof tool !== "object") return false;
3684
+ const type = tool.type;
3685
+ return type === "web_search" || type === "web_search_preview";
3686
+ });
3687
+ }
3688
+ function transformApiPromise(apiPromise, onResolve, onReject) {
3689
+ const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
2680
3690
  if (isObjectRecord(apiPromise)) {
2681
3691
  const proto = Object.getPrototypeOf(apiPromise);
2682
3692
  if (proto) {
@@ -2811,12 +3821,12 @@ function applyResponsesVendorHints(params, hints) {
2811
3821
  }
2812
3822
  return next;
2813
3823
  }
2814
- async function extractUsageFromStream(stream, hints) {
3824
+ async function extractUsageFromStream(stream, hints, provider = "openai") {
2815
3825
  const finalPayload = await resolveStreamFinalPayload(stream);
2816
3826
  if (!finalPayload) {
2817
3827
  return void 0;
2818
3828
  }
2819
- return inferUsageFromResponse(finalPayload, hints);
3829
+ return inferUsageFromResponse(finalPayload, hints, provider);
2820
3830
  }
2821
3831
  async function resolveStreamFinalPayload(stream) {
2822
3832
  if (!stream || typeof stream !== "object") {
@@ -2889,14 +3899,14 @@ function setHeaderIfPossible(res, key, value) {
2889
3899
  res.setHeader(key, value);
2890
3900
  }
2891
3901
  }
2892
- function tryInferUsage(response, hints, extractor, ctx) {
3902
+ function tryInferUsage(response, hints, extractor, ctx, provider = "openai") {
2893
3903
  const explicit = extractor?.(response);
2894
- const inferred = explicit ?? inferUsageFromResponse(response, hints);
3904
+ const inferred = explicit ?? inferUsageFromResponse(response, hints, provider);
2895
3905
  if (inferred) {
2896
3906
  ctx.setUsage(inferred);
2897
3907
  }
2898
3908
  }
2899
- function inferUsageFromResponse(response, hints) {
3909
+ function inferUsageFromResponse(response, hints, provider = "openai") {
2900
3910
  if (!response || typeof response !== "object") {
2901
3911
  return void 0;
2902
3912
  }
@@ -2904,32 +3914,110 @@ function inferUsageFromResponse(response, hints) {
2904
3914
  if (!candidate.usage) {
2905
3915
  return void 0;
2906
3916
  }
2907
- const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
3917
+ const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
3918
+ 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;
3919
+ const cacheWrite5mInputTokens = candidate.usage.cache_write_5m_input_tokens ?? candidate.usage.cache_creation?.ephemeral_5m_input_tokens;
3920
+ const cacheWrite1hInputTokens = candidate.usage.cache_write_1h_input_tokens ?? candidate.usage.cache_creation?.ephemeral_1h_input_tokens;
3921
+ const outputSearches = Array.isArray(candidate.output) ? candidate.output.filter((item) => item?.type === "web_search_call").length : 0;
3922
+ const searches = candidate.usage.searches ?? candidate.usage.web_search_queries ?? candidate.usage.server_tool_use?.web_search_requests ?? outputSearches;
3923
+ const responseEffort = normalizeExecutionReasoningEffort(
3924
+ candidate.reasoning?.effort
3925
+ );
2908
3926
  return {
3927
+ providerUsed: provider,
2909
3928
  modelUsed: candidate.model ?? hints?.preferredModel,
2910
- inputTokens: candidate.usage.prompt_tokens,
2911
- responseTokens: candidate.usage.completion_tokens,
2912
- cachedInputTokens
3929
+ inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
3930
+ responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
3931
+ cachedInputTokens,
3932
+ cacheWriteInputTokens,
3933
+ cacheWrite5mInputTokens,
3934
+ cacheWrite1hInputTokens,
3935
+ audioInputTokens: candidate.usage.prompt_tokens_details?.audio_tokens ?? candidate.usage.input_tokens_details?.audio_tokens,
3936
+ 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,
3937
+ imageInputTokens: candidate.usage.prompt_tokens_details?.image_tokens ?? candidate.usage.input_tokens_details?.image_tokens,
3938
+ imageOutputTokens: candidate.usage.completion_tokens_details?.image_tokens ?? candidate.usage.output_tokens_details?.image_tokens,
3939
+ audioOutputTokens: candidate.usage.completion_tokens_details?.audio_tokens ?? candidate.usage.output_tokens_details?.audio_tokens,
3940
+ reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
3941
+ ...responseEffort ? {
3942
+ reasoningEffort: responseEffort,
3943
+ reasoningEffortSource: "provider_response"
3944
+ } : {},
3945
+ ...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {},
3946
+ ...typeof searches === "number" && searches > 0 ? { searches } : {}
2913
3947
  };
2914
3948
  }
2915
- function wrapStreamForUsageTap(source, finalize, ctx) {
3949
+ function normalizeExecutionReasoningEffort(value) {
3950
+ return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
3951
+ }
3952
+ function openAIRequestExecutionMetadata(params, provider) {
3953
+ const record = params && typeof params === "object" ? params : {};
3954
+ const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
3955
+ const effort = normalizeExecutionReasoningEffort(
3956
+ record.reasoning_effort ?? reasoning?.effort ?? record.thinking_level
3957
+ );
3958
+ const mode = typeof reasoning?.type === "string" ? reasoning.type : typeof reasoning?.mode === "string" ? reasoning.mode : void 0;
3959
+ const rawBudget = reasoning?.budget_tokens ?? record.thinking_budget ?? record.thinking_budget_tokens;
3960
+ const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
3961
+ return {
3962
+ providerUsed: provider,
3963
+ ...typeof record.model === "string" ? { modelUsed: record.model } : {},
3964
+ ...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
3965
+ ...mode ? { reasoningMode: mode } : {},
3966
+ ...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
3967
+ };
3968
+ }
3969
+ function tryInferUsageFromStreamChunk(chunk, hints, ctx, provider) {
3970
+ const payload = isObjectRecord(chunk) && isObjectRecord(chunk.response) ? chunk.response : chunk;
3971
+ const inferred = inferUsageFromResponse(payload, hints, provider);
3972
+ if (inferred) {
3973
+ ctx.setUsage(inferred);
3974
+ }
3975
+ }
3976
+ function ensureOpenAIStreamUsage(params) {
3977
+ if (!isObjectRecord(params)) {
3978
+ return params;
3979
+ }
3980
+ const streamOptions = isObjectRecord(params.stream_options) ? params.stream_options : {};
3981
+ return {
3982
+ ...params,
3983
+ stream_options: {
3984
+ ...streamOptions,
3985
+ include_usage: true
3986
+ }
3987
+ };
3988
+ }
3989
+ function deferUsageFinalization(ctx) {
3990
+ return ctx.deferFinalization?.() ?? (() => Promise.resolve());
3991
+ }
3992
+ function wrapStreamForUsageTap(source, finalize, ctx, onChunk) {
2916
3993
  const getIterator = source[Symbol.asyncIterator];
2917
3994
  if (typeof getIterator !== "function") {
2918
3995
  throw new TypeError("Stream is not async iterable");
2919
3996
  }
2920
3997
  const iterator = getIterator.call(source);
2921
3998
  let completed = false;
2922
- const invokeFinalize = async () => {
3999
+ const invokeFinalize = async (termination, error) => {
2923
4000
  if (completed) return;
2924
4001
  completed = true;
4002
+ if (termination === "cancel" || termination === "manual") {
4003
+ ctx.setError({
4004
+ code: "STREAM_ABORTED",
4005
+ message: termination === "cancel" ? "Provider stream consumption was cancelled before completion" : "Provider stream was finalized before completion"
4006
+ });
4007
+ } else if (termination === "error") {
4008
+ ctx.setError({
4009
+ code: "VENDOR_ERROR",
4010
+ message: error instanceof Error ? error.message : String(error)
4011
+ });
4012
+ }
2925
4013
  try {
2926
- await finalize();
2927
- } catch (error) {
4014
+ await finalize(termination);
4015
+ } catch (error2) {
2928
4016
  ctx.setError({
2929
4017
  code: "USAGE_FINALIZE_ERROR",
2930
- message: error instanceof Error ? error.message : String(error)
4018
+ message: error2 instanceof Error ? error2.message : String(error2)
2931
4019
  });
2932
- throw error;
4020
+ throw error2;
2933
4021
  }
2934
4022
  };
2935
4023
  const prototype = Object.getPrototypeOf(source) ?? Object.prototype;
@@ -2953,12 +4041,15 @@ function wrapStreamForUsageTap(source, finalize, ctx) {
2953
4041
  value: async (...args) => {
2954
4042
  try {
2955
4043
  const result = await iterator.next(...args);
4044
+ if (!result.done) {
4045
+ onChunk?.(result.value);
4046
+ }
2956
4047
  if (result.done) {
2957
- await invokeFinalize();
4048
+ await invokeFinalize("complete");
2958
4049
  }
2959
4050
  return result;
2960
4051
  } catch (error) {
2961
- await invokeFinalize().catch(() => void 0);
4052
+ await invokeFinalize("error", error).catch(() => void 0);
2962
4053
  throw error;
2963
4054
  }
2964
4055
  },
@@ -2967,39 +4058,49 @@ function wrapStreamForUsageTap(source, finalize, ctx) {
2967
4058
  });
2968
4059
  Object.defineProperty(wrapped, "return", {
2969
4060
  value: async (value) => {
2970
- if (typeof iterator.return === "function") {
2971
- const rawResult = await iterator.return(value);
2972
- if (!isIteratorResult(rawResult)) {
2973
- throw new TypeError("Iterator.return() returned an invalid result");
4061
+ try {
4062
+ if (typeof iterator.return === "function") {
4063
+ const rawResult = await iterator.return(value);
4064
+ if (!isIteratorResult(rawResult)) {
4065
+ throw new TypeError("Iterator.return() returned an invalid result");
4066
+ }
4067
+ await invokeFinalize("cancel");
4068
+ return rawResult;
2974
4069
  }
2975
- await invokeFinalize();
2976
- return rawResult;
4070
+ await invokeFinalize("cancel");
4071
+ return { done: true, value };
4072
+ } catch (error) {
4073
+ await invokeFinalize("error", error).catch(() => void 0);
4074
+ throw error;
2977
4075
  }
2978
- await invokeFinalize();
2979
- return { done: true, value };
2980
4076
  },
2981
4077
  configurable: true,
2982
4078
  writable: true
2983
4079
  });
2984
4080
  Object.defineProperty(wrapped, "throw", {
2985
4081
  value: async (error) => {
2986
- if (typeof iterator.throw === "function") {
2987
- const rawResult = await iterator.throw(error);
2988
- if (!isIteratorResult(rawResult)) {
2989
- throw new TypeError("Iterator.throw() returned an invalid result");
4082
+ try {
4083
+ if (typeof iterator.throw === "function") {
4084
+ const rawResult = await iterator.throw(error);
4085
+ if (!isIteratorResult(rawResult)) {
4086
+ throw new TypeError("Iterator.throw() returned an invalid result");
4087
+ }
4088
+ await invokeFinalize("error", error);
4089
+ return rawResult;
2990
4090
  }
2991
- await invokeFinalize();
2992
- return rawResult;
4091
+ await invokeFinalize("error", error);
4092
+ throw error;
4093
+ } catch (thrownError) {
4094
+ await invokeFinalize("error", thrownError).catch(() => void 0);
4095
+ throw thrownError;
2993
4096
  }
2994
- await invokeFinalize();
2995
- throw error;
2996
4097
  },
2997
4098
  configurable: true,
2998
4099
  writable: true
2999
4100
  });
3000
4101
  Object.defineProperty(wrapped, "__usageTapFinalize", {
3001
4102
  value: async () => {
3002
- await invokeFinalize();
4103
+ await invokeFinalize("manual");
3003
4104
  },
3004
4105
  configurable: true
3005
4106
  });
@@ -3011,9 +4112,24 @@ function isIteratorResult(value) {
3011
4112
 
3012
4113
  // src/adapters/openrouter.ts
3013
4114
  function createOpenRouterAdapter(init) {
3014
- return createOpenAIAdapter(init);
4115
+ return createOpenAIAdapter({ ...init, provider: "openrouter" });
4116
+ }
4117
+ function withMetering2(client, customer) {
4118
+ return withMetering(
4119
+ client,
4120
+ typeof customer === "string" ? { customerId: customer, provider: "openrouter" } : { ...customer, provider: "openrouter" }
4121
+ );
4122
+ }
4123
+ function wrapOpenAI2(client, usageTap, options = {}) {
4124
+ return wrapOpenAI(client, usageTap, {
4125
+ ...options,
4126
+ provider: "openrouter"
4127
+ });
4128
+ }
4129
+ function withSampling2(client, options = {}) {
4130
+ return withSampling(client, { ...options, provider: "openrouter" });
3015
4131
  }
3016
4132
 
3017
- export { createOpenRouterAdapter, withCompression, withMetering, wrapOpenAI };
4133
+ export { createOpenRouterAdapter, withCompression, withMetering2 as withMetering, withSampling2 as withSampling, wrapOpenAI2 as wrapOpenAI };
3018
4134
  //# sourceMappingURL=index.mjs.map
3019
4135
  //# sourceMappingURL=index.mjs.map