@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) {
@@ -1917,14 +2758,16 @@ function createCompressionOnlyResponsesProxy(resource, usageTap, compression) {
1917
2758
  }
1918
2759
  });
1919
2760
  }
1920
- function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2761
+ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1921
2762
  const completions = createChatCompletionsProxy(
1922
2763
  resource.completions,
1923
2764
  usageTap,
1924
2765
  defaultContext,
1925
2766
  applyVendorHints,
1926
2767
  defaultPromptCompression,
1927
- promptCompressionStats
2768
+ promptCompressionStats,
2769
+ defaultSampling,
2770
+ provider
1928
2771
  );
1929
2772
  const handler = {
1930
2773
  get(target, prop, receiver) {
@@ -1936,7 +2779,7 @@ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, d
1936
2779
  };
1937
2780
  return new Proxy(resource, handler);
1938
2781
  }
1939
- function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2782
+ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1940
2783
  if (!resource || typeof resource !== "object") {
1941
2784
  return void 0;
1942
2785
  }
@@ -1951,9 +2794,20 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1951
2794
  withUsage,
1952
2795
  promptCompression
1953
2796
  } = splitUsageOptions(options);
1954
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2797
+ const beginRequest = responsesBeginRequest(
2798
+ resolveBeginRequest(defaultContext, usageContext),
2799
+ params
2800
+ );
1955
2801
  const wantsStream = isStreamingRequest(params);
1956
2802
  return usageTap.withUsage(beginRequest, async (ctx) => {
2803
+ const settle = wantsStream ? deferUsageFinalization(ctx) : void 0;
2804
+ const sampleStartedAt = Date.now();
2805
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2806
+ usageTap,
2807
+ sampling: defaultSampling,
2808
+ beginRequest,
2809
+ input: params
2810
+ });
1957
2811
  const hintedParams = applyVendorHints ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints) : params;
1958
2812
  const finalParams = await compressResponsesParamsForCall({
1959
2813
  params: hintedParams,
@@ -1965,25 +2819,67 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1965
2819
  withUsage,
1966
2820
  operation: "responses.create"
1967
2821
  });
2822
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
1968
2823
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
1969
2824
  if (wantsStream) {
1970
2825
  const apiPromise2 = originalCreate(finalParams, request);
1971
2826
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
1972
2827
  ensureAsyncIterable(rawStream, "responses.create");
1973
- const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
1974
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
1975
- if (usage) {
1976
- ctx.setUsage(usage);
2828
+ const wrappedStream = wrapStreamForUsageTap(rawStream, async (termination) => {
2829
+ try {
2830
+ if (termination === "complete") {
2831
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2832
+ if (usage) {
2833
+ ctx.setUsage(usage);
2834
+ }
2835
+ }
2836
+ } catch (error) {
2837
+ ctx.setError({
2838
+ code: "USAGE_FINALIZE_ERROR",
2839
+ message: error instanceof Error ? error.message : String(error)
2840
+ });
2841
+ throw error;
2842
+ } finally {
2843
+ await settle?.();
1977
2844
  }
1978
- }, ctx);
2845
+ }, ctx, (chunk) => {
2846
+ tryInferUsageFromStreamChunk(
2847
+ chunk,
2848
+ ctx.begin.data.vendorHints,
2849
+ ctx,
2850
+ provider
2851
+ );
2852
+ });
1979
2853
  return wrappedStream;
1980
2854
  });
1981
2855
  return wrappedPromise2;
1982
2856
  }
1983
2857
  const apiPromise = originalCreate(finalParams, request);
1984
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
1985
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2858
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2859
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2860
+ await captureMeteredOpenAISample({
2861
+ usageTap,
2862
+ sampling: defaultSampling,
2863
+ decision: sampleDecision,
2864
+ ctx,
2865
+ beginRequest,
2866
+ input: params,
2867
+ response,
2868
+ startedAt: sampleStartedAt
2869
+ });
1986
2870
  return response;
2871
+ }, async (error) => {
2872
+ await captureMeteredOpenAISample({
2873
+ usageTap,
2874
+ sampling: defaultSampling,
2875
+ decision: sampleDecision,
2876
+ ctx,
2877
+ beginRequest,
2878
+ input: params,
2879
+ error,
2880
+ startedAt: sampleStartedAt
2881
+ });
2882
+ throw error;
1987
2883
  });
1988
2884
  return wrappedPromise;
1989
2885
  }, withUsage);
@@ -1998,7 +2894,7 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1998
2894
  };
1999
2895
  return new Proxy(resource, handler);
2000
2896
  }
2001
- function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2897
+ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
2002
2898
  const originalCreate = resource.create.bind(resource);
2003
2899
  const streamCandidate = resource.stream;
2004
2900
  const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(resource) : void 0;
@@ -2012,8 +2908,16 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2012
2908
  const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2013
2909
  const wantsStream = isStreamingRequest(params);
2014
2910
  return usageTap.withUsage(beginRequest, async (ctx) => {
2911
+ const settle = wantsStream ? deferUsageFinalization(ctx) : void 0;
2912
+ const sampleStartedAt = Date.now();
2913
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2914
+ usageTap,
2915
+ sampling: defaultSampling,
2916
+ beginRequest,
2917
+ input: params
2918
+ });
2015
2919
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2016
- const finalParams = await compressChatParamsForCall({
2920
+ const compressedParams = await compressChatParamsForCall({
2017
2921
  params: hintedParams,
2018
2922
  usageTap,
2019
2923
  ctx,
@@ -2023,25 +2927,68 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2023
2927
  withUsage,
2024
2928
  operation: "chat.completions.create"
2025
2929
  });
2930
+ const finalParams = wantsStream ? ensureOpenAIStreamUsage(compressedParams) : compressedParams;
2931
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2026
2932
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2027
2933
  if (wantsStream) {
2028
2934
  const apiPromise2 = originalCreate(finalParams, request);
2029
2935
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
2030
2936
  ensureAsyncIterable(rawStream, "chat.completions.create");
2031
- const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
2032
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2033
- if (usage) {
2034
- ctx.setUsage(usage);
2937
+ const wrappedStream2 = wrapStreamForUsageTap(rawStream, async (termination) => {
2938
+ try {
2939
+ if (termination === "complete") {
2940
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2941
+ if (usage) {
2942
+ ctx.setUsage(usage);
2943
+ }
2944
+ }
2945
+ } catch (error) {
2946
+ ctx.setError({
2947
+ code: "USAGE_FINALIZE_ERROR",
2948
+ message: error instanceof Error ? error.message : String(error)
2949
+ });
2950
+ throw error;
2951
+ } finally {
2952
+ await settle?.();
2035
2953
  }
2036
- }, ctx);
2954
+ }, ctx, (chunk) => {
2955
+ tryInferUsageFromStreamChunk(
2956
+ chunk,
2957
+ ctx.begin.data.vendorHints,
2958
+ ctx,
2959
+ provider
2960
+ );
2961
+ });
2037
2962
  return wrappedStream2;
2038
2963
  });
2039
2964
  return wrappedPromise2;
2040
2965
  }
2041
2966
  const apiPromise = originalCreate(finalParams, request);
2042
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
2043
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2967
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2968
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2969
+ await captureMeteredOpenAISample({
2970
+ usageTap,
2971
+ sampling: defaultSampling,
2972
+ decision: sampleDecision,
2973
+ ctx,
2974
+ beginRequest,
2975
+ input: params,
2976
+ response,
2977
+ startedAt: sampleStartedAt
2978
+ });
2044
2979
  return response;
2980
+ }, async (error) => {
2981
+ await captureMeteredOpenAISample({
2982
+ usageTap,
2983
+ sampling: defaultSampling,
2984
+ decision: sampleDecision,
2985
+ ctx,
2986
+ beginRequest,
2987
+ input: params,
2988
+ error,
2989
+ startedAt: sampleStartedAt
2990
+ });
2991
+ throw error;
2045
2992
  });
2046
2993
  return wrappedPromise;
2047
2994
  }, withUsage);
@@ -2055,8 +3002,9 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2055
3002
  } = splitUsageOptions(options);
2056
3003
  const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2057
3004
  return usageTap.withUsage(beginRequest, async (ctx) => {
3005
+ const settle = deferUsageFinalization(ctx);
2058
3006
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2059
- const finalParams = await compressChatParamsForCall({
3007
+ const compressedParams = await compressChatParamsForCall({
2060
3008
  params: hintedParams,
2061
3009
  usageTap,
2062
3010
  ctx,
@@ -2066,16 +3014,41 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2066
3014
  withUsage,
2067
3015
  operation: "chat.completions.stream"
2068
3016
  });
3017
+ const finalParams = ensureOpenAIStreamUsage(compressedParams);
3018
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2069
3019
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2070
3020
  const apiPromise = originalStream(finalParams, request);
2071
3021
  const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
2072
3022
  ensureAsyncIterable(rawStream, "chat.completions.stream");
2073
- const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
2074
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2075
- if (usage) {
2076
- ctx.setUsage(usage);
3023
+ const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async (termination) => {
3024
+ try {
3025
+ if (termination === "complete") {
3026
+ const usage = await extractUsageFromStream(
3027
+ rawStream,
3028
+ ctx.begin.data.vendorHints,
3029
+ provider
3030
+ );
3031
+ if (usage) {
3032
+ ctx.setUsage(usage);
3033
+ }
3034
+ }
3035
+ } catch (error) {
3036
+ ctx.setError({
3037
+ code: "USAGE_FINALIZE_ERROR",
3038
+ message: error instanceof Error ? error.message : String(error)
3039
+ });
3040
+ throw error;
3041
+ } finally {
3042
+ await settle();
2077
3043
  }
2078
- }, ctx);
3044
+ }, ctx, (chunk) => {
3045
+ tryInferUsageFromStreamChunk(
3046
+ chunk,
3047
+ ctx.begin.data.vendorHints,
3048
+ ctx,
3049
+ provider
3050
+ );
3051
+ });
2079
3052
  return wrappedStreamInner;
2080
3053
  });
2081
3054
  return wrappedPromise;
@@ -2197,6 +3170,10 @@ async function compressChatParams(params, usageTap, compression, signal) {
2197
3170
  const result = await usageTap.compressPromptMessages(source, {
2198
3171
  provider: "usagetap",
2199
3172
  failOpen: compression.failOpen,
3173
+ mode: compression.mode,
3174
+ latencyBudgetMs: compression.latencyBudgetMs,
3175
+ compactEmptyUserMessages: compression.compactEmptyUserMessages,
3176
+ compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
2200
3177
  aggressiveness: resolveMessageEndpointAggressiveness(compression),
2201
3178
  signal
2202
3179
  });
@@ -2666,19 +3643,52 @@ function resolveBeginRequest(defaults, override) {
2666
3643
  if (requested) begin.requested = requested;
2667
3644
  const feature = current.feature ?? base.feature;
2668
3645
  if (feature) begin.feature = feature;
3646
+ const runId = current.runId ?? base.runId;
3647
+ if (runId) begin.runId = runId;
2669
3648
  const idempotency = current.idempotency ?? base.idempotency;
2670
3649
  if (idempotency) begin.idempotency = idempotency;
2671
3650
  const customerName = current.customerName ?? base.customerName;
2672
3651
  if (customerName) begin.customerName = customerName;
2673
3652
  const customerEmail = current.customerEmail ?? base.customerEmail;
2674
3653
  if (customerEmail) begin.customerEmail = customerEmail;
3654
+ const customerUserId = current.customerUserId ?? base.customerUserId;
3655
+ if (customerUserId) begin.customerUserId = customerUserId;
3656
+ const customerUserName = current.customerUserName ?? base.customerUserName;
3657
+ if (customerUserName) begin.customerUserName = customerUserName;
3658
+ const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
3659
+ if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
3660
+ const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
3661
+ if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
3662
+ const batch = current.batch ?? base.batch;
3663
+ if (typeof batch === "boolean") begin.batch = batch;
3664
+ const pricingMode = current.pricingMode ?? base.pricingMode;
3665
+ if (pricingMode) begin.pricingMode = pricingMode;
2675
3666
  if (tags?.length) {
2676
3667
  begin.tags = tags;
2677
3668
  }
2678
3669
  return begin;
2679
3670
  }
2680
- function transformApiPromise(apiPromise, onResolve) {
2681
- const resolvedPromise = Promise.resolve(apiPromise).then(onResolve);
3671
+ function responsesBeginRequest(begin, params) {
3672
+ if (!responsesRequestUsesWebSearch(params)) return begin;
3673
+ return {
3674
+ ...begin,
3675
+ requested: {
3676
+ ...begin.requested ?? {},
3677
+ search: true
3678
+ }
3679
+ };
3680
+ }
3681
+ function responsesRequestUsesWebSearch(params) {
3682
+ if (!params || typeof params !== "object") return false;
3683
+ const tools = params.tools;
3684
+ return Array.isArray(tools) && tools.some((tool) => {
3685
+ if (!tool || typeof tool !== "object") return false;
3686
+ const type = tool.type;
3687
+ return type === "web_search" || type === "web_search_preview";
3688
+ });
3689
+ }
3690
+ function transformApiPromise(apiPromise, onResolve, onReject) {
3691
+ const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
2682
3692
  if (isObjectRecord(apiPromise)) {
2683
3693
  const proto = Object.getPrototypeOf(apiPromise);
2684
3694
  if (proto) {
@@ -2813,12 +3823,12 @@ function applyResponsesVendorHints(params, hints) {
2813
3823
  }
2814
3824
  return next;
2815
3825
  }
2816
- async function extractUsageFromStream(stream, hints) {
3826
+ async function extractUsageFromStream(stream, hints, provider = "openai") {
2817
3827
  const finalPayload = await resolveStreamFinalPayload(stream);
2818
3828
  if (!finalPayload) {
2819
3829
  return void 0;
2820
3830
  }
2821
- return inferUsageFromResponse(finalPayload, hints);
3831
+ return inferUsageFromResponse(finalPayload, hints, provider);
2822
3832
  }
2823
3833
  async function resolveStreamFinalPayload(stream) {
2824
3834
  if (!stream || typeof stream !== "object") {
@@ -2891,14 +3901,14 @@ function setHeaderIfPossible(res, key, value) {
2891
3901
  res.setHeader(key, value);
2892
3902
  }
2893
3903
  }
2894
- function tryInferUsage(response, hints, extractor, ctx) {
3904
+ function tryInferUsage(response, hints, extractor, ctx, provider = "openai") {
2895
3905
  const explicit = extractor?.(response);
2896
- const inferred = explicit ?? inferUsageFromResponse(response, hints);
3906
+ const inferred = explicit ?? inferUsageFromResponse(response, hints, provider);
2897
3907
  if (inferred) {
2898
3908
  ctx.setUsage(inferred);
2899
3909
  }
2900
3910
  }
2901
- function inferUsageFromResponse(response, hints) {
3911
+ function inferUsageFromResponse(response, hints, provider = "openai") {
2902
3912
  if (!response || typeof response !== "object") {
2903
3913
  return void 0;
2904
3914
  }
@@ -2906,32 +3916,110 @@ function inferUsageFromResponse(response, hints) {
2906
3916
  if (!candidate.usage) {
2907
3917
  return void 0;
2908
3918
  }
2909
- const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
3919
+ const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
3920
+ 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;
3921
+ const cacheWrite5mInputTokens = candidate.usage.cache_write_5m_input_tokens ?? candidate.usage.cache_creation?.ephemeral_5m_input_tokens;
3922
+ const cacheWrite1hInputTokens = candidate.usage.cache_write_1h_input_tokens ?? candidate.usage.cache_creation?.ephemeral_1h_input_tokens;
3923
+ const outputSearches = Array.isArray(candidate.output) ? candidate.output.filter((item) => item?.type === "web_search_call").length : 0;
3924
+ const searches = candidate.usage.searches ?? candidate.usage.web_search_queries ?? candidate.usage.server_tool_use?.web_search_requests ?? outputSearches;
3925
+ const responseEffort = normalizeExecutionReasoningEffort(
3926
+ candidate.reasoning?.effort
3927
+ );
2910
3928
  return {
3929
+ providerUsed: provider,
2911
3930
  modelUsed: candidate.model ?? hints?.preferredModel,
2912
- inputTokens: candidate.usage.prompt_tokens,
2913
- responseTokens: candidate.usage.completion_tokens,
2914
- cachedInputTokens
3931
+ inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
3932
+ responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
3933
+ cachedInputTokens,
3934
+ cacheWriteInputTokens,
3935
+ cacheWrite5mInputTokens,
3936
+ cacheWrite1hInputTokens,
3937
+ audioInputTokens: candidate.usage.prompt_tokens_details?.audio_tokens ?? candidate.usage.input_tokens_details?.audio_tokens,
3938
+ 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,
3939
+ imageInputTokens: candidate.usage.prompt_tokens_details?.image_tokens ?? candidate.usage.input_tokens_details?.image_tokens,
3940
+ imageOutputTokens: candidate.usage.completion_tokens_details?.image_tokens ?? candidate.usage.output_tokens_details?.image_tokens,
3941
+ audioOutputTokens: candidate.usage.completion_tokens_details?.audio_tokens ?? candidate.usage.output_tokens_details?.audio_tokens,
3942
+ reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
3943
+ ...responseEffort ? {
3944
+ reasoningEffort: responseEffort,
3945
+ reasoningEffortSource: "provider_response"
3946
+ } : {},
3947
+ ...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {},
3948
+ ...typeof searches === "number" && searches > 0 ? { searches } : {}
2915
3949
  };
2916
3950
  }
2917
- function wrapStreamForUsageTap(source, finalize, ctx) {
3951
+ function normalizeExecutionReasoningEffort(value) {
3952
+ return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
3953
+ }
3954
+ function openAIRequestExecutionMetadata(params, provider) {
3955
+ const record = params && typeof params === "object" ? params : {};
3956
+ const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
3957
+ const effort = normalizeExecutionReasoningEffort(
3958
+ record.reasoning_effort ?? reasoning?.effort ?? record.thinking_level
3959
+ );
3960
+ const mode = typeof reasoning?.type === "string" ? reasoning.type : typeof reasoning?.mode === "string" ? reasoning.mode : void 0;
3961
+ const rawBudget = reasoning?.budget_tokens ?? record.thinking_budget ?? record.thinking_budget_tokens;
3962
+ const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
3963
+ return {
3964
+ providerUsed: provider,
3965
+ ...typeof record.model === "string" ? { modelUsed: record.model } : {},
3966
+ ...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
3967
+ ...mode ? { reasoningMode: mode } : {},
3968
+ ...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
3969
+ };
3970
+ }
3971
+ function tryInferUsageFromStreamChunk(chunk, hints, ctx, provider) {
3972
+ const payload = isObjectRecord(chunk) && isObjectRecord(chunk.response) ? chunk.response : chunk;
3973
+ const inferred = inferUsageFromResponse(payload, hints, provider);
3974
+ if (inferred) {
3975
+ ctx.setUsage(inferred);
3976
+ }
3977
+ }
3978
+ function ensureOpenAIStreamUsage(params) {
3979
+ if (!isObjectRecord(params)) {
3980
+ return params;
3981
+ }
3982
+ const streamOptions = isObjectRecord(params.stream_options) ? params.stream_options : {};
3983
+ return {
3984
+ ...params,
3985
+ stream_options: {
3986
+ ...streamOptions,
3987
+ include_usage: true
3988
+ }
3989
+ };
3990
+ }
3991
+ function deferUsageFinalization(ctx) {
3992
+ return ctx.deferFinalization?.() ?? (() => Promise.resolve());
3993
+ }
3994
+ function wrapStreamForUsageTap(source, finalize, ctx, onChunk) {
2918
3995
  const getIterator = source[Symbol.asyncIterator];
2919
3996
  if (typeof getIterator !== "function") {
2920
3997
  throw new TypeError("Stream is not async iterable");
2921
3998
  }
2922
3999
  const iterator = getIterator.call(source);
2923
4000
  let completed = false;
2924
- const invokeFinalize = async () => {
4001
+ const invokeFinalize = async (termination, error) => {
2925
4002
  if (completed) return;
2926
4003
  completed = true;
4004
+ if (termination === "cancel" || termination === "manual") {
4005
+ ctx.setError({
4006
+ code: "STREAM_ABORTED",
4007
+ message: termination === "cancel" ? "Provider stream consumption was cancelled before completion" : "Provider stream was finalized before completion"
4008
+ });
4009
+ } else if (termination === "error") {
4010
+ ctx.setError({
4011
+ code: "VENDOR_ERROR",
4012
+ message: error instanceof Error ? error.message : String(error)
4013
+ });
4014
+ }
2927
4015
  try {
2928
- await finalize();
2929
- } catch (error) {
4016
+ await finalize(termination);
4017
+ } catch (error2) {
2930
4018
  ctx.setError({
2931
4019
  code: "USAGE_FINALIZE_ERROR",
2932
- message: error instanceof Error ? error.message : String(error)
4020
+ message: error2 instanceof Error ? error2.message : String(error2)
2933
4021
  });
2934
- throw error;
4022
+ throw error2;
2935
4023
  }
2936
4024
  };
2937
4025
  const prototype = Object.getPrototypeOf(source) ?? Object.prototype;
@@ -2955,12 +4043,15 @@ function wrapStreamForUsageTap(source, finalize, ctx) {
2955
4043
  value: async (...args) => {
2956
4044
  try {
2957
4045
  const result = await iterator.next(...args);
4046
+ if (!result.done) {
4047
+ onChunk?.(result.value);
4048
+ }
2958
4049
  if (result.done) {
2959
- await invokeFinalize();
4050
+ await invokeFinalize("complete");
2960
4051
  }
2961
4052
  return result;
2962
4053
  } catch (error) {
2963
- await invokeFinalize().catch(() => void 0);
4054
+ await invokeFinalize("error", error).catch(() => void 0);
2964
4055
  throw error;
2965
4056
  }
2966
4057
  },
@@ -2969,39 +4060,49 @@ function wrapStreamForUsageTap(source, finalize, ctx) {
2969
4060
  });
2970
4061
  Object.defineProperty(wrapped, "return", {
2971
4062
  value: async (value) => {
2972
- if (typeof iterator.return === "function") {
2973
- const rawResult = await iterator.return(value);
2974
- if (!isIteratorResult(rawResult)) {
2975
- throw new TypeError("Iterator.return() returned an invalid result");
4063
+ try {
4064
+ if (typeof iterator.return === "function") {
4065
+ const rawResult = await iterator.return(value);
4066
+ if (!isIteratorResult(rawResult)) {
4067
+ throw new TypeError("Iterator.return() returned an invalid result");
4068
+ }
4069
+ await invokeFinalize("cancel");
4070
+ return rawResult;
2976
4071
  }
2977
- await invokeFinalize();
2978
- return rawResult;
4072
+ await invokeFinalize("cancel");
4073
+ return { done: true, value };
4074
+ } catch (error) {
4075
+ await invokeFinalize("error", error).catch(() => void 0);
4076
+ throw error;
2979
4077
  }
2980
- await invokeFinalize();
2981
- return { done: true, value };
2982
4078
  },
2983
4079
  configurable: true,
2984
4080
  writable: true
2985
4081
  });
2986
4082
  Object.defineProperty(wrapped, "throw", {
2987
4083
  value: async (error) => {
2988
- if (typeof iterator.throw === "function") {
2989
- const rawResult = await iterator.throw(error);
2990
- if (!isIteratorResult(rawResult)) {
2991
- throw new TypeError("Iterator.throw() returned an invalid result");
4084
+ try {
4085
+ if (typeof iterator.throw === "function") {
4086
+ const rawResult = await iterator.throw(error);
4087
+ if (!isIteratorResult(rawResult)) {
4088
+ throw new TypeError("Iterator.throw() returned an invalid result");
4089
+ }
4090
+ await invokeFinalize("error", error);
4091
+ return rawResult;
2992
4092
  }
2993
- await invokeFinalize();
2994
- return rawResult;
4093
+ await invokeFinalize("error", error);
4094
+ throw error;
4095
+ } catch (thrownError) {
4096
+ await invokeFinalize("error", thrownError).catch(() => void 0);
4097
+ throw thrownError;
2995
4098
  }
2996
- await invokeFinalize();
2997
- throw error;
2998
4099
  },
2999
4100
  configurable: true,
3000
4101
  writable: true
3001
4102
  });
3002
4103
  Object.defineProperty(wrapped, "__usageTapFinalize", {
3003
4104
  value: async () => {
3004
- await invokeFinalize();
4105
+ await invokeFinalize("manual");
3005
4106
  },
3006
4107
  configurable: true
3007
4108
  });
@@ -3013,12 +4114,28 @@ function isIteratorResult(value) {
3013
4114
 
3014
4115
  // src/adapters/openrouter.ts
3015
4116
  function createOpenRouterAdapter(init) {
3016
- return createOpenAIAdapter(init);
4117
+ return createOpenAIAdapter({ ...init, provider: "openrouter" });
4118
+ }
4119
+ function withMetering2(client, customer) {
4120
+ return withMetering(
4121
+ client,
4122
+ typeof customer === "string" ? { customerId: customer, provider: "openrouter" } : { ...customer, provider: "openrouter" }
4123
+ );
4124
+ }
4125
+ function wrapOpenAI2(client, usageTap, options = {}) {
4126
+ return wrapOpenAI(client, usageTap, {
4127
+ ...options,
4128
+ provider: "openrouter"
4129
+ });
4130
+ }
4131
+ function withSampling2(client, options = {}) {
4132
+ return withSampling(client, { ...options, provider: "openrouter" });
3017
4133
  }
3018
4134
 
3019
4135
  exports.createOpenRouterAdapter = createOpenRouterAdapter;
3020
4136
  exports.withCompression = withCompression;
3021
- exports.withMetering = withMetering;
3022
- exports.wrapOpenAI = wrapOpenAI;
4137
+ exports.withMetering = withMetering2;
4138
+ exports.withSampling = withSampling2;
4139
+ exports.wrapOpenAI = wrapOpenAI2;
3023
4140
  //# sourceMappingURL=index.cjs.map
3024
4141
  //# sourceMappingURL=index.cjs.map