@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) {
@@ -1966,14 +2807,16 @@ function streamOpenAIRoute(usageTap, openai, options) {
1966
2807
  });
1967
2808
  };
1968
2809
  }
1969
- function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2810
+ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1970
2811
  const completions = createChatCompletionsProxy(
1971
2812
  resource.completions,
1972
2813
  usageTap,
1973
2814
  defaultContext,
1974
2815
  applyVendorHints,
1975
2816
  defaultPromptCompression,
1976
- promptCompressionStats
2817
+ promptCompressionStats,
2818
+ defaultSampling,
2819
+ provider
1977
2820
  );
1978
2821
  const handler = {
1979
2822
  get(target, prop, receiver) {
@@ -1985,7 +2828,7 @@ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, d
1985
2828
  };
1986
2829
  return new Proxy(resource, handler);
1987
2830
  }
1988
- function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2831
+ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1989
2832
  if (!resource || typeof resource !== "object") {
1990
2833
  return void 0;
1991
2834
  }
@@ -2000,9 +2843,20 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2000
2843
  withUsage,
2001
2844
  promptCompression
2002
2845
  } = splitUsageOptions(options);
2003
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2846
+ const beginRequest = responsesBeginRequest(
2847
+ resolveBeginRequest(defaultContext, usageContext),
2848
+ params
2849
+ );
2004
2850
  const wantsStream = isStreamingRequest(params);
2005
2851
  return usageTap.withUsage(beginRequest, async (ctx) => {
2852
+ const settle = wantsStream ? deferUsageFinalization(ctx) : void 0;
2853
+ const sampleStartedAt = Date.now();
2854
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2855
+ usageTap,
2856
+ sampling: defaultSampling,
2857
+ beginRequest,
2858
+ input: params
2859
+ });
2006
2860
  const hintedParams = applyVendorHints ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints) : params;
2007
2861
  const finalParams = await compressResponsesParamsForCall({
2008
2862
  params: hintedParams,
@@ -2014,25 +2868,67 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2014
2868
  withUsage,
2015
2869
  operation: "responses.create"
2016
2870
  });
2871
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2017
2872
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2018
2873
  if (wantsStream) {
2019
2874
  const apiPromise2 = originalCreate(finalParams, request);
2020
2875
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
2021
2876
  ensureAsyncIterable(rawStream, "responses.create");
2022
- const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
2023
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2024
- if (usage) {
2025
- ctx.setUsage(usage);
2877
+ const wrappedStream = wrapStreamForUsageTap(rawStream, async (termination) => {
2878
+ try {
2879
+ if (termination === "complete") {
2880
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2881
+ if (usage) {
2882
+ ctx.setUsage(usage);
2883
+ }
2884
+ }
2885
+ } catch (error) {
2886
+ ctx.setError({
2887
+ code: "USAGE_FINALIZE_ERROR",
2888
+ message: error instanceof Error ? error.message : String(error)
2889
+ });
2890
+ throw error;
2891
+ } finally {
2892
+ await settle?.();
2026
2893
  }
2027
- }, ctx);
2894
+ }, ctx, (chunk) => {
2895
+ tryInferUsageFromStreamChunk(
2896
+ chunk,
2897
+ ctx.begin.data.vendorHints,
2898
+ ctx,
2899
+ provider
2900
+ );
2901
+ });
2028
2902
  return wrappedStream;
2029
2903
  });
2030
2904
  return wrappedPromise2;
2031
2905
  }
2032
2906
  const apiPromise = originalCreate(finalParams, request);
2033
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
2034
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2907
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2908
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2909
+ await captureMeteredOpenAISample({
2910
+ usageTap,
2911
+ sampling: defaultSampling,
2912
+ decision: sampleDecision,
2913
+ ctx,
2914
+ beginRequest,
2915
+ input: params,
2916
+ response,
2917
+ startedAt: sampleStartedAt
2918
+ });
2035
2919
  return response;
2920
+ }, async (error) => {
2921
+ await captureMeteredOpenAISample({
2922
+ usageTap,
2923
+ sampling: defaultSampling,
2924
+ decision: sampleDecision,
2925
+ ctx,
2926
+ beginRequest,
2927
+ input: params,
2928
+ error,
2929
+ startedAt: sampleStartedAt
2930
+ });
2931
+ throw error;
2036
2932
  });
2037
2933
  return wrappedPromise;
2038
2934
  }, withUsage);
@@ -2047,7 +2943,7 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2047
2943
  };
2048
2944
  return new Proxy(resource, handler);
2049
2945
  }
2050
- function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2946
+ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
2051
2947
  const originalCreate = resource.create.bind(resource);
2052
2948
  const streamCandidate = resource.stream;
2053
2949
  const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(resource) : void 0;
@@ -2061,8 +2957,16 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2061
2957
  const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2062
2958
  const wantsStream = isStreamingRequest(params);
2063
2959
  return usageTap.withUsage(beginRequest, async (ctx) => {
2960
+ const settle = wantsStream ? deferUsageFinalization(ctx) : void 0;
2961
+ const sampleStartedAt = Date.now();
2962
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2963
+ usageTap,
2964
+ sampling: defaultSampling,
2965
+ beginRequest,
2966
+ input: params
2967
+ });
2064
2968
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2065
- const finalParams = await compressChatParamsForCall({
2969
+ const compressedParams = await compressChatParamsForCall({
2066
2970
  params: hintedParams,
2067
2971
  usageTap,
2068
2972
  ctx,
@@ -2072,25 +2976,68 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2072
2976
  withUsage,
2073
2977
  operation: "chat.completions.create"
2074
2978
  });
2979
+ const finalParams = wantsStream ? ensureOpenAIStreamUsage(compressedParams) : compressedParams;
2980
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2075
2981
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2076
2982
  if (wantsStream) {
2077
2983
  const apiPromise2 = originalCreate(finalParams, request);
2078
2984
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
2079
2985
  ensureAsyncIterable(rawStream, "chat.completions.create");
2080
- const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
2081
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2082
- if (usage) {
2083
- ctx.setUsage(usage);
2986
+ const wrappedStream2 = wrapStreamForUsageTap(rawStream, async (termination) => {
2987
+ try {
2988
+ if (termination === "complete") {
2989
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2990
+ if (usage) {
2991
+ ctx.setUsage(usage);
2992
+ }
2993
+ }
2994
+ } catch (error) {
2995
+ ctx.setError({
2996
+ code: "USAGE_FINALIZE_ERROR",
2997
+ message: error instanceof Error ? error.message : String(error)
2998
+ });
2999
+ throw error;
3000
+ } finally {
3001
+ await settle?.();
2084
3002
  }
2085
- }, ctx);
3003
+ }, ctx, (chunk) => {
3004
+ tryInferUsageFromStreamChunk(
3005
+ chunk,
3006
+ ctx.begin.data.vendorHints,
3007
+ ctx,
3008
+ provider
3009
+ );
3010
+ });
2086
3011
  return wrappedStream2;
2087
3012
  });
2088
3013
  return wrappedPromise2;
2089
3014
  }
2090
3015
  const apiPromise = originalCreate(finalParams, request);
2091
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
2092
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
3016
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
3017
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
3018
+ await captureMeteredOpenAISample({
3019
+ usageTap,
3020
+ sampling: defaultSampling,
3021
+ decision: sampleDecision,
3022
+ ctx,
3023
+ beginRequest,
3024
+ input: params,
3025
+ response,
3026
+ startedAt: sampleStartedAt
3027
+ });
2093
3028
  return response;
3029
+ }, async (error) => {
3030
+ await captureMeteredOpenAISample({
3031
+ usageTap,
3032
+ sampling: defaultSampling,
3033
+ decision: sampleDecision,
3034
+ ctx,
3035
+ beginRequest,
3036
+ input: params,
3037
+ error,
3038
+ startedAt: sampleStartedAt
3039
+ });
3040
+ throw error;
2094
3041
  });
2095
3042
  return wrappedPromise;
2096
3043
  }, withUsage);
@@ -2104,8 +3051,9 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2104
3051
  } = splitUsageOptions(options);
2105
3052
  const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2106
3053
  return usageTap.withUsage(beginRequest, async (ctx) => {
3054
+ const settle = deferUsageFinalization(ctx);
2107
3055
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2108
- const finalParams = await compressChatParamsForCall({
3056
+ const compressedParams = await compressChatParamsForCall({
2109
3057
  params: hintedParams,
2110
3058
  usageTap,
2111
3059
  ctx,
@@ -2115,16 +3063,41 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2115
3063
  withUsage,
2116
3064
  operation: "chat.completions.stream"
2117
3065
  });
3066
+ const finalParams = ensureOpenAIStreamUsage(compressedParams);
3067
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2118
3068
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2119
3069
  const apiPromise = originalStream(finalParams, request);
2120
3070
  const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
2121
3071
  ensureAsyncIterable(rawStream, "chat.completions.stream");
2122
- const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
2123
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2124
- if (usage) {
2125
- ctx.setUsage(usage);
3072
+ const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async (termination) => {
3073
+ try {
3074
+ if (termination === "complete") {
3075
+ const usage = await extractUsageFromStream(
3076
+ rawStream,
3077
+ ctx.begin.data.vendorHints,
3078
+ provider
3079
+ );
3080
+ if (usage) {
3081
+ ctx.setUsage(usage);
3082
+ }
3083
+ }
3084
+ } catch (error) {
3085
+ ctx.setError({
3086
+ code: "USAGE_FINALIZE_ERROR",
3087
+ message: error instanceof Error ? error.message : String(error)
3088
+ });
3089
+ throw error;
3090
+ } finally {
3091
+ await settle();
2126
3092
  }
2127
- }, ctx);
3093
+ }, ctx, (chunk) => {
3094
+ tryInferUsageFromStreamChunk(
3095
+ chunk,
3096
+ ctx.begin.data.vendorHints,
3097
+ ctx,
3098
+ provider
3099
+ );
3100
+ });
2128
3101
  return wrappedStreamInner;
2129
3102
  });
2130
3103
  return wrappedPromise;
@@ -2246,6 +3219,10 @@ async function compressChatParams(params, usageTap, compression, signal) {
2246
3219
  const result = await usageTap.compressPromptMessages(source, {
2247
3220
  provider: "usagetap",
2248
3221
  failOpen: compression.failOpen,
3222
+ mode: compression.mode,
3223
+ latencyBudgetMs: compression.latencyBudgetMs,
3224
+ compactEmptyUserMessages: compression.compactEmptyUserMessages,
3225
+ compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
2249
3226
  aggressiveness: resolveMessageEndpointAggressiveness(compression),
2250
3227
  signal
2251
3228
  });
@@ -2715,19 +3692,52 @@ function resolveBeginRequest(defaults, override) {
2715
3692
  if (requested) begin.requested = requested;
2716
3693
  const feature = current.feature ?? base.feature;
2717
3694
  if (feature) begin.feature = feature;
3695
+ const runId = current.runId ?? base.runId;
3696
+ if (runId) begin.runId = runId;
2718
3697
  const idempotency = current.idempotency ?? base.idempotency;
2719
3698
  if (idempotency) begin.idempotency = idempotency;
2720
3699
  const customerName = current.customerName ?? base.customerName;
2721
3700
  if (customerName) begin.customerName = customerName;
2722
3701
  const customerEmail = current.customerEmail ?? base.customerEmail;
2723
3702
  if (customerEmail) begin.customerEmail = customerEmail;
3703
+ const customerUserId = current.customerUserId ?? base.customerUserId;
3704
+ if (customerUserId) begin.customerUserId = customerUserId;
3705
+ const customerUserName = current.customerUserName ?? base.customerUserName;
3706
+ if (customerUserName) begin.customerUserName = customerUserName;
3707
+ const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
3708
+ if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
3709
+ const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
3710
+ if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
3711
+ const batch = current.batch ?? base.batch;
3712
+ if (typeof batch === "boolean") begin.batch = batch;
3713
+ const pricingMode = current.pricingMode ?? base.pricingMode;
3714
+ if (pricingMode) begin.pricingMode = pricingMode;
2724
3715
  if (tags?.length) {
2725
3716
  begin.tags = tags;
2726
3717
  }
2727
3718
  return begin;
2728
3719
  }
2729
- function transformApiPromise(apiPromise, onResolve) {
2730
- const resolvedPromise = Promise.resolve(apiPromise).then(onResolve);
3720
+ function responsesBeginRequest(begin, params) {
3721
+ if (!responsesRequestUsesWebSearch(params)) return begin;
3722
+ return {
3723
+ ...begin,
3724
+ requested: {
3725
+ ...begin.requested ?? {},
3726
+ search: true
3727
+ }
3728
+ };
3729
+ }
3730
+ function responsesRequestUsesWebSearch(params) {
3731
+ if (!params || typeof params !== "object") return false;
3732
+ const tools = params.tools;
3733
+ return Array.isArray(tools) && tools.some((tool) => {
3734
+ if (!tool || typeof tool !== "object") return false;
3735
+ const type = tool.type;
3736
+ return type === "web_search" || type === "web_search_preview";
3737
+ });
3738
+ }
3739
+ function transformApiPromise(apiPromise, onResolve, onReject) {
3740
+ const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
2731
3741
  if (isObjectRecord(apiPromise)) {
2732
3742
  const proto = Object.getPrototypeOf(apiPromise);
2733
3743
  if (proto) {
@@ -2862,12 +3872,12 @@ function applyResponsesVendorHints(params, hints) {
2862
3872
  }
2863
3873
  return next;
2864
3874
  }
2865
- async function extractUsageFromStream(stream, hints) {
3875
+ async function extractUsageFromStream(stream, hints, provider = "openai") {
2866
3876
  const finalPayload = await resolveStreamFinalPayload(stream);
2867
3877
  if (!finalPayload) {
2868
3878
  return void 0;
2869
3879
  }
2870
- return inferUsageFromResponse(finalPayload, hints);
3880
+ return inferUsageFromResponse(finalPayload, hints, provider);
2871
3881
  }
2872
3882
  async function resolveStreamFinalPayload(stream) {
2873
3883
  if (!stream || typeof stream !== "object") {
@@ -2940,14 +3950,14 @@ function setHeaderIfPossible(res, key, value) {
2940
3950
  res.setHeader(key, value);
2941
3951
  }
2942
3952
  }
2943
- function tryInferUsage(response, hints, extractor, ctx) {
3953
+ function tryInferUsage(response, hints, extractor, ctx, provider = "openai") {
2944
3954
  const explicit = extractor?.(response);
2945
- const inferred = explicit ?? inferUsageFromResponse(response, hints);
3955
+ const inferred = explicit ?? inferUsageFromResponse(response, hints, provider);
2946
3956
  if (inferred) {
2947
3957
  ctx.setUsage(inferred);
2948
3958
  }
2949
3959
  }
2950
- function inferUsageFromResponse(response, hints) {
3960
+ function inferUsageFromResponse(response, hints, provider = "openai") {
2951
3961
  if (!response || typeof response !== "object") {
2952
3962
  return void 0;
2953
3963
  }
@@ -2955,32 +3965,110 @@ function inferUsageFromResponse(response, hints) {
2955
3965
  if (!candidate.usage) {
2956
3966
  return void 0;
2957
3967
  }
2958
- const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
3968
+ const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
3969
+ 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;
3970
+ const cacheWrite5mInputTokens = candidate.usage.cache_write_5m_input_tokens ?? candidate.usage.cache_creation?.ephemeral_5m_input_tokens;
3971
+ const cacheWrite1hInputTokens = candidate.usage.cache_write_1h_input_tokens ?? candidate.usage.cache_creation?.ephemeral_1h_input_tokens;
3972
+ const outputSearches = Array.isArray(candidate.output) ? candidate.output.filter((item) => item?.type === "web_search_call").length : 0;
3973
+ const searches = candidate.usage.searches ?? candidate.usage.web_search_queries ?? candidate.usage.server_tool_use?.web_search_requests ?? outputSearches;
3974
+ const responseEffort = normalizeExecutionReasoningEffort(
3975
+ candidate.reasoning?.effort
3976
+ );
2959
3977
  return {
3978
+ providerUsed: provider,
2960
3979
  modelUsed: candidate.model ?? hints?.preferredModel,
2961
- inputTokens: candidate.usage.prompt_tokens,
2962
- responseTokens: candidate.usage.completion_tokens,
2963
- cachedInputTokens
3980
+ inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
3981
+ responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
3982
+ cachedInputTokens,
3983
+ cacheWriteInputTokens,
3984
+ cacheWrite5mInputTokens,
3985
+ cacheWrite1hInputTokens,
3986
+ audioInputTokens: candidate.usage.prompt_tokens_details?.audio_tokens ?? candidate.usage.input_tokens_details?.audio_tokens,
3987
+ 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,
3988
+ imageInputTokens: candidate.usage.prompt_tokens_details?.image_tokens ?? candidate.usage.input_tokens_details?.image_tokens,
3989
+ imageOutputTokens: candidate.usage.completion_tokens_details?.image_tokens ?? candidate.usage.output_tokens_details?.image_tokens,
3990
+ audioOutputTokens: candidate.usage.completion_tokens_details?.audio_tokens ?? candidate.usage.output_tokens_details?.audio_tokens,
3991
+ reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
3992
+ ...responseEffort ? {
3993
+ reasoningEffort: responseEffort,
3994
+ reasoningEffortSource: "provider_response"
3995
+ } : {},
3996
+ ...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {},
3997
+ ...typeof searches === "number" && searches > 0 ? { searches } : {}
2964
3998
  };
2965
3999
  }
2966
- function wrapStreamForUsageTap(source, finalize, ctx) {
4000
+ function normalizeExecutionReasoningEffort(value) {
4001
+ return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
4002
+ }
4003
+ function openAIRequestExecutionMetadata(params, provider) {
4004
+ const record = params && typeof params === "object" ? params : {};
4005
+ const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
4006
+ const effort = normalizeExecutionReasoningEffort(
4007
+ record.reasoning_effort ?? reasoning?.effort ?? record.thinking_level
4008
+ );
4009
+ const mode = typeof reasoning?.type === "string" ? reasoning.type : typeof reasoning?.mode === "string" ? reasoning.mode : void 0;
4010
+ const rawBudget = reasoning?.budget_tokens ?? record.thinking_budget ?? record.thinking_budget_tokens;
4011
+ const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
4012
+ return {
4013
+ providerUsed: provider,
4014
+ ...typeof record.model === "string" ? { modelUsed: record.model } : {},
4015
+ ...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
4016
+ ...mode ? { reasoningMode: mode } : {},
4017
+ ...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
4018
+ };
4019
+ }
4020
+ function tryInferUsageFromStreamChunk(chunk, hints, ctx, provider) {
4021
+ const payload = isObjectRecord(chunk) && isObjectRecord(chunk.response) ? chunk.response : chunk;
4022
+ const inferred = inferUsageFromResponse(payload, hints, provider);
4023
+ if (inferred) {
4024
+ ctx.setUsage(inferred);
4025
+ }
4026
+ }
4027
+ function ensureOpenAIStreamUsage(params) {
4028
+ if (!isObjectRecord(params)) {
4029
+ return params;
4030
+ }
4031
+ const streamOptions = isObjectRecord(params.stream_options) ? params.stream_options : {};
4032
+ return {
4033
+ ...params,
4034
+ stream_options: {
4035
+ ...streamOptions,
4036
+ include_usage: true
4037
+ }
4038
+ };
4039
+ }
4040
+ function deferUsageFinalization(ctx) {
4041
+ return ctx.deferFinalization?.() ?? (() => Promise.resolve());
4042
+ }
4043
+ function wrapStreamForUsageTap(source, finalize, ctx, onChunk) {
2967
4044
  const getIterator = source[Symbol.asyncIterator];
2968
4045
  if (typeof getIterator !== "function") {
2969
4046
  throw new TypeError("Stream is not async iterable");
2970
4047
  }
2971
4048
  const iterator = getIterator.call(source);
2972
4049
  let completed = false;
2973
- const invokeFinalize = async () => {
4050
+ const invokeFinalize = async (termination, error) => {
2974
4051
  if (completed) return;
2975
4052
  completed = true;
4053
+ if (termination === "cancel" || termination === "manual") {
4054
+ ctx.setError({
4055
+ code: "STREAM_ABORTED",
4056
+ message: termination === "cancel" ? "Provider stream consumption was cancelled before completion" : "Provider stream was finalized before completion"
4057
+ });
4058
+ } else if (termination === "error") {
4059
+ ctx.setError({
4060
+ code: "VENDOR_ERROR",
4061
+ message: error instanceof Error ? error.message : String(error)
4062
+ });
4063
+ }
2976
4064
  try {
2977
- await finalize();
2978
- } catch (error) {
4065
+ await finalize(termination);
4066
+ } catch (error2) {
2979
4067
  ctx.setError({
2980
4068
  code: "USAGE_FINALIZE_ERROR",
2981
- message: error instanceof Error ? error.message : String(error)
4069
+ message: error2 instanceof Error ? error2.message : String(error2)
2982
4070
  });
2983
- throw error;
4071
+ throw error2;
2984
4072
  }
2985
4073
  };
2986
4074
  const prototype = Object.getPrototypeOf(source) ?? Object.prototype;
@@ -3004,12 +4092,15 @@ function wrapStreamForUsageTap(source, finalize, ctx) {
3004
4092
  value: async (...args) => {
3005
4093
  try {
3006
4094
  const result = await iterator.next(...args);
4095
+ if (!result.done) {
4096
+ onChunk?.(result.value);
4097
+ }
3007
4098
  if (result.done) {
3008
- await invokeFinalize();
4099
+ await invokeFinalize("complete");
3009
4100
  }
3010
4101
  return result;
3011
4102
  } catch (error) {
3012
- await invokeFinalize().catch(() => void 0);
4103
+ await invokeFinalize("error", error).catch(() => void 0);
3013
4104
  throw error;
3014
4105
  }
3015
4106
  },
@@ -3018,39 +4109,49 @@ function wrapStreamForUsageTap(source, finalize, ctx) {
3018
4109
  });
3019
4110
  Object.defineProperty(wrapped, "return", {
3020
4111
  value: async (value) => {
3021
- if (typeof iterator.return === "function") {
3022
- const rawResult = await iterator.return(value);
3023
- if (!isIteratorResult(rawResult)) {
3024
- throw new TypeError("Iterator.return() returned an invalid result");
4112
+ try {
4113
+ if (typeof iterator.return === "function") {
4114
+ const rawResult = await iterator.return(value);
4115
+ if (!isIteratorResult(rawResult)) {
4116
+ throw new TypeError("Iterator.return() returned an invalid result");
4117
+ }
4118
+ await invokeFinalize("cancel");
4119
+ return rawResult;
3025
4120
  }
3026
- await invokeFinalize();
3027
- return rawResult;
4121
+ await invokeFinalize("cancel");
4122
+ return { done: true, value };
4123
+ } catch (error) {
4124
+ await invokeFinalize("error", error).catch(() => void 0);
4125
+ throw error;
3028
4126
  }
3029
- await invokeFinalize();
3030
- return { done: true, value };
3031
4127
  },
3032
4128
  configurable: true,
3033
4129
  writable: true
3034
4130
  });
3035
4131
  Object.defineProperty(wrapped, "throw", {
3036
4132
  value: async (error) => {
3037
- if (typeof iterator.throw === "function") {
3038
- const rawResult = await iterator.throw(error);
3039
- if (!isIteratorResult(rawResult)) {
3040
- throw new TypeError("Iterator.throw() returned an invalid result");
4133
+ try {
4134
+ if (typeof iterator.throw === "function") {
4135
+ const rawResult = await iterator.throw(error);
4136
+ if (!isIteratorResult(rawResult)) {
4137
+ throw new TypeError("Iterator.throw() returned an invalid result");
4138
+ }
4139
+ await invokeFinalize("error", error);
4140
+ return rawResult;
3041
4141
  }
3042
- await invokeFinalize();
3043
- return rawResult;
4142
+ await invokeFinalize("error", error);
4143
+ throw error;
4144
+ } catch (thrownError) {
4145
+ await invokeFinalize("error", thrownError).catch(() => void 0);
4146
+ throw thrownError;
3044
4147
  }
3045
- await invokeFinalize();
3046
- throw error;
3047
4148
  },
3048
4149
  configurable: true,
3049
4150
  writable: true
3050
4151
  });
3051
4152
  Object.defineProperty(wrapped, "__usageTapFinalize", {
3052
4153
  value: async () => {
3053
- await invokeFinalize();
4154
+ await invokeFinalize("manual");
3054
4155
  },
3055
4156
  configurable: true
3056
4157
  });
@@ -3062,9 +4163,9 @@ function isIteratorResult(value) {
3062
4163
 
3063
4164
  // src/adapters/openrouter.ts
3064
4165
  function createOpenRouterAdapter(init) {
3065
- return createOpenAIAdapter(init);
4166
+ return createOpenAIAdapter({ ...init, provider: "openrouter" });
3066
4167
  }
3067
4168
 
3068
- export { OpenAIPromptCompressionStats, createOpenAIAdapter, createOpenRouterAdapter, pipeToResponse, streamOpenAIRoute, toNextResponse, withCompression, withMetering, wrapOpenAI };
4169
+ export { OpenAIPromptCompressionStats, createOpenAIAdapter, createOpenRouterAdapter, pipeToResponse, streamOpenAIRoute, toNextResponse, withCompression, withMetering, withSampling, wrapOpenAI };
3069
4170
  //# sourceMappingURL=index.mjs.map
3070
4171
  //# sourceMappingURL=index.mjs.map