@usagetap/sdk 1.3.1 → 1.4.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 +1058 -863
  2. package/dist/adapters/anthropic.cjs +990 -24
  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 +990 -25
  7. package/dist/adapters/anthropic.mjs.map +1 -1
  8. package/dist/adapters/openai.cjs +1164 -44
  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 +1164 -45
  13. package/dist/adapters/openai.mjs.map +1 -1
  14. package/dist/adapters/openrouter.cjs +3899 -22
  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 +3897 -23
  19. package/dist/adapters/openrouter.mjs.map +1 -1
  20. package/dist/anthropic/index.cjs +990 -24
  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 +990 -25
  25. package/dist/anthropic/index.mjs.map +1 -1
  26. package/dist/client-CExQ8e1T.d.cts +1225 -0
  27. package/dist/client-CExQ8e1T.d.ts +1225 -0
  28. package/dist/express/index.cjs +421 -29
  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 +421 -29
  33. package/dist/express/index.mjs.map +1 -1
  34. package/dist/index.cjs +680 -15
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +5 -3
  37. package/dist/index.d.ts +5 -3
  38. package/dist/index.mjs +680 -15
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/openai/index.cjs +1165 -45
  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 +1165 -46
  45. package/dist/openai/index.mjs.map +1 -1
  46. package/dist/openrouter/index.cjs +1182 -47
  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 +1180 -46
  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 +84 -84
  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,358 @@ 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
+ 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.models = {
1049
+ list: (options) => this.transport.request({
1050
+ method: "GET",
1051
+ path: "/v1/models",
1052
+ options,
1053
+ response: "json"
1054
+ })
1055
+ };
1056
+ this.batches = {
1057
+ create: (params, options = {}) => this.transport.request({
1058
+ method: "POST",
1059
+ path: "/v1/batches",
1060
+ body: params,
1061
+ options: {
1062
+ ...options,
1063
+ idempotencyKey: options.idempotencyKey ?? this.idempotencyGenerator()
1064
+ },
1065
+ response: "json"
1066
+ }),
1067
+ retrieve: (batchId, options) => this.transport.request({
1068
+ method: "GET",
1069
+ path: `/v1/batches/${encodeURIComponent(
1070
+ resourceId(batchId, ["id"], "gateway.batches.retrieve")
1071
+ )}`,
1072
+ options,
1073
+ response: "json"
1074
+ }),
1075
+ wait: (batch, options) => this.waitForBatch(batch, options),
1076
+ cancel: (batchId, options) => this.transport.request({
1077
+ method: "POST",
1078
+ path: `/v1/batches/${encodeURIComponent(
1079
+ resourceId(batchId, ["id"], "gateway.batches.cancel")
1080
+ )}/cancel`,
1081
+ options,
1082
+ response: "json"
1083
+ }),
1084
+ results: (batchId, options) => this.transport.request({
1085
+ method: "GET",
1086
+ path: `/v1/batches/${encodeURIComponent(
1087
+ resourceId(batchId, ["id"], "gateway.batches.results")
1088
+ )}/results`,
1089
+ options,
1090
+ response: "ndjson"
1091
+ })
1092
+ };
1093
+ }
1094
+ async waitForBatch(value, options = {}) {
1095
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
1096
+ const deadline = Date.now() + timeoutMs;
1097
+ let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
1098
+ while (!terminalGatewayBatch(batch.status)) {
1099
+ if (Date.now() >= deadline) {
1100
+ throw new UsageTapError(
1101
+ "USAGETAP_RETRY_EXHAUSTED",
1102
+ `Gateway batch ${batch.id} did not finish before timeout`,
1103
+ { retryable: true }
1104
+ );
1105
+ }
1106
+ await sleep(pollIntervalMs, options.signal);
1107
+ batch = await this.batches.retrieve(batch.id, options);
1108
+ }
1109
+ return batch;
1110
+ }
1111
+ };
1112
+
755
1113
  // src/client.ts
756
1114
  var CALL_BEGIN_PATH = "call_begin";
757
1115
  var CALL_END_PATH = "call_end";
758
1116
  var COMPRESS_PROMPT_PATH = "compress_prompt";
1117
+ var SAMPLES_PATH = "samples";
1118
+ var SAMPLING_SETTINGS_PATH = "sampling/settings";
1119
+ var SAMPLING_DECIDE_PATH = "sampling/decide";
759
1120
  var CHECK_USAGE_PATH = "customers/{customerId}/usage";
760
1121
  var CREATE_CUSTOMER_PATH = "customers";
761
1122
  var CHANGE_PLAN_PATH = "customers/{customerId}/change_plan";
@@ -766,11 +1127,16 @@ var CORRELATION_HEADER = "x-usage-correlation-id";
766
1127
  var IDEMPOTENCY_HEADER = "idempotency-key";
767
1128
  var SDK_HEADER = "x-usage-sdk";
768
1129
  var USER_AGENT = "UsageTapClient";
769
- var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
1130
+ var CANONICAL_MEDIA_TYPE2 = "application/vnd.usagetap.v1+json";
770
1131
  var DEFAULT_BASE_URL = "https://api.usagetap.com";
771
- var SDK_VERSION = "1.3.1" ;
1132
+ var DEFAULT_RUN_INACTIVITY_MS = 60 * 60 * 1e3;
1133
+ var SDK_VERSION = "1.4.0" ;
772
1134
  var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
773
1135
  var UsageTapClient = class {
1136
+ /** OpenAI-compatible chat, model, and native batch operations. */
1137
+ gateway;
1138
+ /** Published-profile context summarization operations. */
1139
+ summarization;
774
1140
  apiKey;
775
1141
  baseUrl;
776
1142
  fetchImpl;
@@ -795,6 +1161,11 @@ var UsageTapClient = class {
795
1161
  usageTapCompressionMessagesEndpoint;
796
1162
  usageTapCompressionModel;
797
1163
  usageTapCompressionAggressiveness;
1164
+ sampling;
1165
+ samplingSettingsCacheMs;
1166
+ circuitBreaker;
1167
+ circuitBreakerRuns = /* @__PURE__ */ new Map();
1168
+ samplingSettingsCache;
798
1169
  constructor(options = {}) {
799
1170
  const apiKey = options.apiKey?.trim() || readEnvironmentVariable("USAGETAP_API_KEY");
800
1171
  const baseUrl = options.baseUrl?.trim() || readEnvironmentVariable("USAGETAP_BASE_URL") || DEFAULT_BASE_URL;
@@ -817,10 +1188,21 @@ var UsageTapClient = class {
817
1188
  "A global fetch implementation was not found. Pass fetchImpl in UsageTapClientOptions."
818
1189
  );
819
1190
  }
820
- const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
821
- this.baseUrl = new URL(normalizedBaseUrl);
1191
+ const normalizedBaseUrl2 = normalizeBaseUrl(baseUrl);
1192
+ this.baseUrl = new URL(normalizedBaseUrl2);
822
1193
  this.apiKey = apiKey;
823
1194
  this.fetchImpl = wrapFetchImplementation(fetchCandidate, !options.fetchImpl);
1195
+ const resourceConfig = {
1196
+ apiKey,
1197
+ apiBaseUrl: normalizedBaseUrl2,
1198
+ gatewayBaseUrl: options.gatewayBaseUrl?.trim() || readEnvironmentVariable("USAGETAP_GATEWAY_URL"),
1199
+ fetchImpl: this.fetchImpl,
1200
+ headers: options.headers,
1201
+ sdkVersion: SDK_VERSION,
1202
+ idempotencyGenerator: options.idempotencyGenerator
1203
+ };
1204
+ this.gateway = new GatewayResource(resourceConfig);
1205
+ this.summarization = new SummarizationResource(resourceConfig);
824
1206
  this.defaultFeature = options.defaultFeature;
825
1207
  this.defaultTags = options.defaultTags?.length ? dedupeStrings(options.defaultTags) : void 0;
826
1208
  this.defaultHeaders = options.headers ? normalizeHeaderDictionary(options.headers) : {};
@@ -842,11 +1224,122 @@ var UsageTapClient = class {
842
1224
  this.usageTapCompressionMessagesEndpoint = options.usageTapCompressionMessagesEndpoint;
843
1225
  this.usageTapCompressionModel = options.usageTapCompressionModel;
844
1226
  this.usageTapCompressionAggressiveness = options.usageTapCompressionAggressiveness;
1227
+ this.sampling = options.sampling;
1228
+ this.samplingSettingsCacheMs = Number.isFinite(options.samplingSettingsCacheMs) ? Math.max(0, Number(options.samplingSettingsCacheMs)) : 5 * 60 * 1e3;
1229
+ if (options.circuitBreaker) {
1230
+ const maxCallsPerRun = options.circuitBreaker.maxCallsPerRun;
1231
+ if (!Number.isInteger(maxCallsPerRun) || maxCallsPerRun < 1) {
1232
+ throw new UsageTapError(
1233
+ "USAGETAP_BAD_REQUEST",
1234
+ "circuitBreaker.maxCallsPerRun must be a positive integer"
1235
+ );
1236
+ }
1237
+ const runInactivityMs = options.circuitBreaker.runInactivityMs ?? DEFAULT_RUN_INACTIVITY_MS;
1238
+ if (!Number.isFinite(runInactivityMs) || runInactivityMs < 1) {
1239
+ throw new UsageTapError(
1240
+ "USAGETAP_BAD_REQUEST",
1241
+ "circuitBreaker.runInactivityMs must be a positive number"
1242
+ );
1243
+ }
1244
+ this.circuitBreaker = {
1245
+ maxCallsPerRun,
1246
+ runInactivityMs
1247
+ };
1248
+ }
1249
+ }
1250
+ shouldSample(request, policy = this.sampling || void 0) {
1251
+ if (!policy) return false;
1252
+ const rate = Math.min(1, Math.max(0, Number(policy.rate) || 0));
1253
+ if (rate <= 0) return false;
1254
+ const customerId = request.customerId?.trim();
1255
+ if (customerId && policy.customers?.exclude?.includes(customerId)) return false;
1256
+ const feature = request.feature?.trim();
1257
+ if (feature && policy.features?.exclude?.includes(feature)) return false;
1258
+ const included = policy.features?.include?.filter(Boolean) ?? [];
1259
+ if (included.length > 0 && (!feature || !included.includes(feature))) return false;
1260
+ const minimum = Math.max(0, Math.round(policy.minInputTokens ?? 0));
1261
+ if (minimum > 0 && estimatePromptTokens(request.input) < minimum) return false;
1262
+ return (policy.random ?? Math.random)() < rate;
1263
+ }
1264
+ async getSamplingSettings(options = {}) {
1265
+ const now = Date.now();
1266
+ if (!options.forceRefresh && this.samplingSettingsCache && this.samplingSettingsCache.expiresAtMs > now) {
1267
+ return {
1268
+ result: { status: "ACCEPTED", code: "SAMPLING_SETTINGS_CACHED" },
1269
+ data: this.samplingSettingsCache.settings,
1270
+ correlationId: options.correlationId ?? "local-cache"
1271
+ };
1272
+ }
1273
+ const response = await this.requestGet(
1274
+ SAMPLING_SETTINGS_PATH,
1275
+ {
1276
+ signal: options.signal,
1277
+ headers: options.headers,
1278
+ retries: options.retries,
1279
+ correlationId: options.correlationId
1280
+ }
1281
+ );
1282
+ const serverCacheMs = Math.max(0, Number(response.data.cacheSeconds) || 0) * 1e3;
1283
+ const cacheMs = Math.min(this.samplingSettingsCacheMs, serverCacheMs);
1284
+ this.samplingSettingsCache = {
1285
+ settings: response.data,
1286
+ expiresAtMs: now + cacheMs
1287
+ };
1288
+ return response;
1289
+ }
1290
+ async shouldSampleAsync(request, policy) {
1291
+ if (policy) return this.shouldSample(request, policy);
1292
+ if (this.sampling === false) return false;
1293
+ if (this.sampling) return this.shouldSample(request, this.sampling);
1294
+ try {
1295
+ const settings = await this.getSamplingSettings();
1296
+ return this.shouldSample(request, settings.data);
1297
+ } catch {
1298
+ return false;
1299
+ }
1300
+ }
1301
+ async decideSample(request, options = {}) {
1302
+ const hasTokens = Number.isFinite(request.inputTokens) && Number(request.inputTokens) >= 0;
1303
+ const hasCharacters = Number.isFinite(request.inputCharacters) && Number(request.inputCharacters) >= 0;
1304
+ if (!hasTokens && !hasCharacters) {
1305
+ throw new UsageTapError(
1306
+ "USAGETAP_BAD_REQUEST",
1307
+ "decideSample requires inputTokens or inputCharacters"
1308
+ );
1309
+ }
1310
+ return this.request(
1311
+ SAMPLING_DECIDE_PATH,
1312
+ request,
1313
+ options
1314
+ );
1315
+ }
1316
+ async captureSample(request, options = {}) {
1317
+ if (!request || request.input === void 0) {
1318
+ throw new UsageTapError(
1319
+ "USAGETAP_BAD_REQUEST",
1320
+ "captureSample requires input"
1321
+ );
1322
+ }
1323
+ if (!request.provider?.trim()) {
1324
+ throw new UsageTapError(
1325
+ "USAGETAP_BAD_REQUEST",
1326
+ "captureSample requires provider"
1327
+ );
1328
+ }
1329
+ const sampleId = request.sampleId?.trim() || this.idempotencyGenerator();
1330
+ return this.request(
1331
+ SAMPLES_PATH,
1332
+ { ...request, sampleId },
1333
+ { ...options, idempotencyKey: sampleId }
1334
+ );
845
1335
  }
846
1336
  async beginCall(request, options = {}) {
847
1337
  const idempotencyKey = request.idempotencyKey ?? request.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
1338
+ this.reserveRunCall(request, idempotencyKey);
1339
+ const apiRequest = { ...request };
1340
+ delete apiRequest.runId;
848
1341
  const payload = {
849
- ...request,
1342
+ ...apiRequest,
850
1343
  feature: request.feature ?? this.defaultFeature,
851
1344
  tags: this.mergeTags(request.tags)
852
1345
  };
@@ -864,6 +1357,28 @@ var UsageTapClient = class {
864
1357
  );
865
1358
  return response;
866
1359
  }
1360
+ /**
1361
+ * Inspect a configured run circuit breaker without consuming another call.
1362
+ */
1363
+ canRunContinue(request) {
1364
+ const identity = this.resolveRunIdentity(request);
1365
+ if (!identity || !this.circuitBreaker) {
1366
+ throw new UsageTapError(
1367
+ "USAGETAP_BAD_REQUEST",
1368
+ "canRunContinue requires circuitBreaker configuration and a non-empty runId"
1369
+ );
1370
+ }
1371
+ this.expireInactiveRuns();
1372
+ const calls = this.circuitBreakerRuns.get(identity.key)?.calls ?? 0;
1373
+ return this.createCircuitBreakerDecision(identity.customerId, identity.runId, calls);
1374
+ }
1375
+ /**
1376
+ * Release local state after a workflow finishes. Returns true when state existed.
1377
+ */
1378
+ resetRun(request) {
1379
+ const identity = this.resolveRunIdentity(request);
1380
+ return identity ? this.circuitBreakerRuns.delete(identity.key) : false;
1381
+ }
867
1382
  async promptCompress(request, options = {}) {
868
1383
  if (!request?.callId) {
869
1384
  throw new UsageTapError(
@@ -961,6 +1476,10 @@ var UsageTapClient = class {
961
1476
  usageTapCompressionMessagesEndpoint: this.usageTapCompressionMessagesEndpoint,
962
1477
  aggressiveness: options.aggressiveness ?? this.aggressiveness,
963
1478
  usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
1479
+ mode: options.mode,
1480
+ latencyBudgetMs: options.latencyBudgetMs,
1481
+ compactEmptyUserMessages: options.compactEmptyUserMessages,
1482
+ compactDuplicateUserTextParts: options.compactDuplicateUserTextParts,
964
1483
  fetchImpl: this.fetchImpl,
965
1484
  signal: options.signal,
966
1485
  failOpen: options.failOpen
@@ -1002,11 +1521,17 @@ var UsageTapClient = class {
1002
1521
  callId: request.callId,
1003
1522
  feature: feature ?? this.defaultFeature,
1004
1523
  tags: tags ?? this.defaultTags,
1524
+ providerUsed: request.providerUsed,
1005
1525
  modelUsed: request.modelUsed,
1526
+ reasoningEffort: request.reasoningEffort,
1527
+ reasoningEffortSource: request.reasoningEffortSource,
1528
+ reasoningMode: request.reasoningMode,
1529
+ reasoningBudgetTokens: request.reasoningBudgetTokens,
1006
1530
  metrics: {
1007
1531
  inputTokens: request.inputTokens,
1008
1532
  responseTokens: request.responseTokens,
1009
1533
  cachedInputTokens: request.cachedInputTokens,
1534
+ cacheWriteInputTokens: request.cacheWriteInputTokens,
1010
1535
  reasoningTokens: request.reasoningTokens,
1011
1536
  searches: request.searches,
1012
1537
  audioSeconds: request.audioSeconds,
@@ -1114,6 +1639,15 @@ var UsageTapClient = class {
1114
1639
  meterSlot: request.meterSlot,
1115
1640
  amount: request.amount
1116
1641
  };
1642
+ if (request.customerUserId) {
1643
+ payload.customerUserId = request.customerUserId;
1644
+ }
1645
+ if (request.customerUserName) {
1646
+ payload.customerUserName = request.customerUserName;
1647
+ }
1648
+ if (request.customerUserEmail) {
1649
+ payload.customerUserEmail = request.customerUserEmail;
1650
+ }
1117
1651
  if (request.feature) {
1118
1652
  payload.feature = request.feature;
1119
1653
  }
@@ -1150,6 +1684,11 @@ var UsageTapClient = class {
1150
1684
  const beginPayload = idempotencyKey ? { ...beginRequest, idempotencyKey, idempotency: idempotencyKey } : { ...beginRequest };
1151
1685
  const beginResponse = await this.beginCall(beginPayload, options);
1152
1686
  let usage = {};
1687
+ const pricingMode = beginResponse.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
1688
+ if (pricingMode) {
1689
+ usage.pricingMode = pricingMode;
1690
+ usage.batch = pricingMode === "batch";
1691
+ }
1153
1692
  const initialStripeCustomerId = typeof beginResponse.data.stripeCustomerId === "string" ? beginResponse.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
1154
1693
  if (initialStripeCustomerId) {
1155
1694
  usage = { ...usage, stripeCustomerId: initialStripeCustomerId };
@@ -1217,17 +1756,86 @@ var UsageTapClient = class {
1217
1756
  toPromptCompressionTelemetry(result) {
1218
1757
  return {
1219
1758
  provider: result.provider,
1220
- originalCharacters: result.originalCharacters,
1221
- compressedCharacters: result.compressedCharacters,
1222
- savedCharacters: result.savedCharacters,
1223
1759
  originalTokens: result.originalTokens,
1224
1760
  compressedTokens: result.compressedTokens,
1225
1761
  savedTokens: result.savedTokens,
1226
1762
  tokenSavingsRatio: result.tokenSavingsRatio,
1227
- savingsRatio: result.savingsRatio,
1228
1763
  techniques: result.techniques
1229
1764
  };
1230
1765
  }
1766
+ reserveRunCall(request, idempotencyKey) {
1767
+ const identity = this.resolveRunIdentity(request);
1768
+ if (!identity || !this.circuitBreaker) return;
1769
+ this.expireInactiveRuns();
1770
+ const now = Date.now();
1771
+ const state = this.circuitBreakerRuns.get(identity.key) ?? {
1772
+ calls: 0,
1773
+ reservationKeys: /* @__PURE__ */ new Set(),
1774
+ lastSeenAtMs: now
1775
+ };
1776
+ const reservationKey = idempotencyKey ?? this.idempotencyGenerator();
1777
+ state.lastSeenAtMs = now;
1778
+ if (state.reservationKeys.has(reservationKey)) {
1779
+ this.circuitBreakerRuns.set(identity.key, state);
1780
+ return;
1781
+ }
1782
+ const decision = this.createCircuitBreakerDecision(
1783
+ identity.customerId,
1784
+ identity.runId,
1785
+ state.calls
1786
+ );
1787
+ if (!decision.allowed) {
1788
+ throw new UsageTapError(
1789
+ "USAGETAP_CIRCUIT_OPEN",
1790
+ `Run ${identity.runId} reached its ${decision.limit}-call circuit-breaker limit`,
1791
+ {
1792
+ details: {
1793
+ reason: decision.reason,
1794
+ customerId: identity.customerId,
1795
+ runId: identity.runId,
1796
+ calls: decision.calls,
1797
+ limit: decision.limit,
1798
+ remaining: decision.remaining
1799
+ }
1800
+ }
1801
+ );
1802
+ }
1803
+ state.calls += 1;
1804
+ state.reservationKeys.add(reservationKey);
1805
+ this.circuitBreakerRuns.set(identity.key, state);
1806
+ }
1807
+ resolveRunIdentity(request) {
1808
+ const customerId = request.customerId?.trim();
1809
+ const runId = request.runId?.trim();
1810
+ if (!customerId || !runId) return void 0;
1811
+ return {
1812
+ key: `${customerId}\0${runId}`,
1813
+ customerId,
1814
+ runId
1815
+ };
1816
+ }
1817
+ createCircuitBreakerDecision(customerId, runId, calls) {
1818
+ const limit = this.circuitBreaker?.maxCallsPerRun ?? 0;
1819
+ const allowed = calls < limit;
1820
+ return {
1821
+ allowed,
1822
+ ...allowed ? {} : { reason: "max_calls_per_run" },
1823
+ customerId,
1824
+ runId,
1825
+ calls,
1826
+ limit,
1827
+ remaining: Math.max(0, limit - calls)
1828
+ };
1829
+ }
1830
+ expireInactiveRuns() {
1831
+ if (!this.circuitBreaker || this.circuitBreakerRuns.size === 0) return;
1832
+ const expiredBefore = Date.now() - this.circuitBreaker.runInactivityMs;
1833
+ for (const [key, state] of this.circuitBreakerRuns) {
1834
+ if (state.lastSeenAtMs < expiredBefore) {
1835
+ this.circuitBreakerRuns.delete(key);
1836
+ }
1837
+ }
1838
+ }
1231
1839
  async request(path, payload, options) {
1232
1840
  const url = new URL(path, this.baseUrl).toString();
1233
1841
  const body = payload !== void 0 ? JSON.stringify(payload) : void 0;
@@ -1415,7 +2023,7 @@ var UsageTapClient = class {
1415
2023
  ...this.defaultHeaders,
1416
2024
  [SDK_HEADER]: `js/${SDK_VERSION}`,
1417
2025
  "content-type": "application/json",
1418
- accept: CANONICAL_MEDIA_TYPE
2026
+ accept: CANONICAL_MEDIA_TYPE2
1419
2027
  };
1420
2028
  if (!HAS_WINDOW) {
1421
2029
  headers["user-agent"] = `${USER_AGENT}/${SDK_VERSION}`;
@@ -1616,17 +2224,18 @@ var OpenAIPromptCompressionStats = class {
1616
2224
  }
1617
2225
  };
1618
2226
  function createOpenAIAdapter(init) {
1619
- const { client, usageTap } = init;
2227
+ const { client, usageTap, provider = "openai" } = init;
1620
2228
  return {
1621
2229
  async invoke(params) {
1622
2230
  const result = await usageTap.withUsage(
1623
2231
  params.begin,
1624
2232
  async (ctx) => {
2233
+ ctx.setUsage({ providerUsed: provider });
1625
2234
  const response = await params.call(client, {
1626
2235
  hints: ctx.begin.data.vendorHints,
1627
2236
  begin: ctx.begin
1628
2237
  });
1629
- tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx);
2238
+ tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx, provider);
1630
2239
  return {
1631
2240
  data: response,
1632
2241
  begin: ctx.begin
@@ -1640,6 +2249,7 @@ function createOpenAIAdapter(init) {
1640
2249
  const result = await usageTap.withUsage(
1641
2250
  params.begin,
1642
2251
  async (ctx) => {
2252
+ ctx.setUsage({ providerUsed: provider });
1643
2253
  const { stream, onComplete } = await params.call(client, {
1644
2254
  hints: ctx.begin.data.vendorHints,
1645
2255
  begin: ctx.begin
@@ -1765,6 +2375,181 @@ async function pipeToResponse(stream, res, options = {}) {
1765
2375
  }
1766
2376
  }
1767
2377
  var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
2378
+ function withSampling(client, options = {}) {
2379
+ if (!client || !options) {
2380
+ throw new UsageTapError(
2381
+ "USAGETAP_BAD_REQUEST",
2382
+ "withSampling requires an OpenAI-compatible client and sampling options"
2383
+ );
2384
+ }
2385
+ const { apiKey, usageTapClient, provider = "openai", ...policy } = options;
2386
+ const localPolicy = typeof policy.rate === "number" ? policy : void 0;
2387
+ const usageTap = usageTapClient ?? new UsageTapClient({ apiKey, sampling: localPolicy });
2388
+ const wrapCreate = (create) => async (params, requestOptions) => {
2389
+ const { usageTap: callContextRaw, ...providerOptions } = requestOptions ?? {};
2390
+ const callContext = isObjectRecord(callContextRaw) ? callContextRaw : {};
2391
+ const streaming = params.stream === true;
2392
+ const decision = streaming ? Promise.resolve(false) : usageTap.shouldSampleAsync({
2393
+ customerId: readString(callContext.customerId),
2394
+ feature: readString(callContext.feature),
2395
+ input: params
2396
+ }, localPolicy);
2397
+ const startedAt = Date.now();
2398
+ try {
2399
+ const response = await create(
2400
+ params,
2401
+ Object.keys(providerOptions).length ? providerOptions : void 0
2402
+ );
2403
+ const selected = await decision;
2404
+ if (selected) {
2405
+ const record = isObjectRecord(response) ? response : {};
2406
+ await usageTap.captureSample({
2407
+ customerId: readString(callContext.customerId),
2408
+ feature: readString(callContext.feature),
2409
+ environment: readString(callContext.environment),
2410
+ tags: readStringArray(callContext.tags),
2411
+ provider,
2412
+ model: readString(record.model) ?? readString(params.model),
2413
+ input: params,
2414
+ output: response,
2415
+ usage: record.usage,
2416
+ latencyMs: Date.now() - startedAt
2417
+ }).catch(() => void 0);
2418
+ }
2419
+ return response;
2420
+ } catch (error) {
2421
+ const selected = await decision;
2422
+ if (selected) {
2423
+ await usageTap.captureSample({
2424
+ customerId: readString(callContext.customerId),
2425
+ feature: readString(callContext.feature),
2426
+ environment: readString(callContext.environment),
2427
+ tags: readStringArray(callContext.tags),
2428
+ provider,
2429
+ model: readString(params.model),
2430
+ input: params,
2431
+ latencyMs: Date.now() - startedAt,
2432
+ error: serializeSamplingError(error)
2433
+ }).catch(() => void 0);
2434
+ }
2435
+ throw error;
2436
+ }
2437
+ };
2438
+ const chat = client.chat?.completions ? new Proxy(client.chat, {
2439
+ get(target, prop, receiver) {
2440
+ if (prop !== "completions") return safeReflectGet(target, prop, receiver);
2441
+ const completions = target.completions;
2442
+ return new Proxy(completions, {
2443
+ get(completionTarget, completionProp, completionReceiver) {
2444
+ if (completionProp === "create") {
2445
+ return wrapCreate(
2446
+ completionTarget.create.bind(completionTarget)
2447
+ );
2448
+ }
2449
+ return safeReflectGet(
2450
+ completionTarget,
2451
+ completionProp,
2452
+ completionReceiver
2453
+ );
2454
+ }
2455
+ });
2456
+ }
2457
+ }) : void 0;
2458
+ const responses = typeof client.responses !== "undefined" && client.responses ? new Proxy(client.responses, {
2459
+ get(target, prop, receiver) {
2460
+ if (prop === "create") {
2461
+ const create = Reflect.get(target, prop, receiver);
2462
+ return wrapCreate(create.bind(target));
2463
+ }
2464
+ return safeReflectGet(target, prop, receiver);
2465
+ }
2466
+ }) : void 0;
2467
+ return new Proxy(client, {
2468
+ get(target, prop, receiver) {
2469
+ if (prop === "chat" && chat) return chat;
2470
+ if (prop === "responses" && responses) return responses;
2471
+ if (prop === "unwrap") return () => target;
2472
+ return safeReflectGet(target, prop, receiver);
2473
+ }
2474
+ });
2475
+ }
2476
+ function safeReflectGet(target, prop, receiver) {
2477
+ return Reflect.get(target, prop, receiver);
2478
+ }
2479
+ function readStringArray(value) {
2480
+ if (!Array.isArray(value)) return void 0;
2481
+ const strings = value.filter((item) => typeof item === "string");
2482
+ return strings.length ? strings : void 0;
2483
+ }
2484
+ function readString(value) {
2485
+ return typeof value === "string" && value.trim() ? value : void 0;
2486
+ }
2487
+ function serializeSamplingError(error) {
2488
+ if (error instanceof Error) {
2489
+ return { name: error.name, message: error.message };
2490
+ }
2491
+ return { message: String(error) };
2492
+ }
2493
+ function normalizeMeteredOpenAISampling(options) {
2494
+ if (!options) return void 0;
2495
+ if (options === true) return { provider: "openai" };
2496
+ const { provider = "openai", ...policyFields } = options;
2497
+ return {
2498
+ provider,
2499
+ policy: typeof policyFields.rate === "number" ? policyFields : void 0
2500
+ };
2501
+ }
2502
+ function startMeteredOpenAISampleDecision({
2503
+ usageTap,
2504
+ sampling,
2505
+ beginRequest,
2506
+ input
2507
+ }) {
2508
+ if (!sampling) return Promise.resolve(false);
2509
+ return usageTap.shouldSampleAsync(
2510
+ {
2511
+ customerId: beginRequest.customerId,
2512
+ feature: beginRequest.feature,
2513
+ input
2514
+ },
2515
+ sampling.policy
2516
+ );
2517
+ }
2518
+ async function captureMeteredOpenAISample({
2519
+ usageTap,
2520
+ sampling,
2521
+ decision,
2522
+ ctx,
2523
+ beginRequest,
2524
+ input,
2525
+ response,
2526
+ error,
2527
+ startedAt
2528
+ }) {
2529
+ if (!sampling) return;
2530
+ let selected = false;
2531
+ try {
2532
+ selected = await decision;
2533
+ } catch {
2534
+ return;
2535
+ }
2536
+ if (!selected) return;
2537
+ const record = isObjectRecord(response) ? response : {};
2538
+ await usageTap.captureSample({
2539
+ sampleId: ctx.begin.data.callId,
2540
+ callId: ctx.begin.data.callId,
2541
+ customerId: beginRequest.customerId,
2542
+ feature: beginRequest.feature,
2543
+ tags: beginRequest.tags,
2544
+ provider: sampling.provider,
2545
+ model: readString(record.model) ?? readString(input.model),
2546
+ input,
2547
+ ...response === void 0 ? {} : { output: response },
2548
+ usage: record.usage,
2549
+ latencyMs: Date.now() - startedAt,
2550
+ ...error === void 0 ? {} : { error: serializeSamplingError(error) }
2551
+ }).catch(() => void 0);
2552
+ }
1768
2553
  function withCompression(client, options = {}) {
1769
2554
  if (!client) {
1770
2555
  throw new UsageTapError(
@@ -1775,11 +2560,13 @@ function withCompression(client, options = {}) {
1775
2560
  const {
1776
2561
  apiKey,
1777
2562
  usageTapClient,
2563
+ sampling,
1778
2564
  ...compressionOverrides
1779
2565
  } = options;
1780
2566
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
1781
2567
  const compression = {
1782
2568
  provider: "usagetap",
2569
+ roles: { user: { enabled: true, aggressiveness: 0.2 } },
1783
2570
  minContextTokens: DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS,
1784
2571
  ...compressionOverrides
1785
2572
  };
@@ -1789,7 +2576,7 @@ function withCompression(client, options = {}) {
1789
2576
  usageTap,
1790
2577
  compression
1791
2578
  ) : void 0;
1792
- return new Proxy(client, {
2579
+ const compressed = new Proxy(client, {
1793
2580
  get(target, prop, receiver) {
1794
2581
  if (prop === "chat" && proxiedChat) {
1795
2582
  return proxiedChat;
@@ -1803,6 +2590,11 @@ function withCompression(client, options = {}) {
1803
2590
  return Reflect.get(target, prop, receiver);
1804
2591
  }
1805
2592
  });
2593
+ return sampling ? withSampling(compressed, {
2594
+ ...sampling === true ? {} : sampling,
2595
+ apiKey,
2596
+ usageTapClient
2597
+ }) : compressed;
1806
2598
  }
1807
2599
  function withMetering(client, customer) {
1808
2600
  const config = typeof customer === "string" ? { customerId: customer } : customer;
@@ -1817,6 +2609,8 @@ function withMetering(client, customer) {
1817
2609
  usageTapClient,
1818
2610
  applyVendorHints,
1819
2611
  promptCompression,
2612
+ sampling,
2613
+ provider,
1820
2614
  ...defaultContext
1821
2615
  } = config;
1822
2616
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
@@ -1824,7 +2618,9 @@ function withMetering(client, customer) {
1824
2618
  return wrapOpenAI(client, usageTap, {
1825
2619
  defaultContext,
1826
2620
  applyVendorHints,
1827
- promptCompression: normalizedCompression
2621
+ promptCompression: normalizedCompression,
2622
+ sampling,
2623
+ provider
1828
2624
  });
1829
2625
  }
1830
2626
  function wrapOpenAI(client, usageTap, options = {}) {
@@ -1834,6 +2630,8 @@ function wrapOpenAI(client, usageTap, options = {}) {
1834
2630
  const defaultContext = options.defaultContext;
1835
2631
  const applyVendorHints = options.applyVendorHints !== false;
1836
2632
  const defaultPromptCompression = normalizePromptCompressionOptions(options.promptCompression);
2633
+ const defaultSampling = normalizeMeteredOpenAISampling(options.sampling);
2634
+ const provider = options.provider ?? "openai";
1837
2635
  const promptCompressionStats = new OpenAIPromptCompressionStats();
1838
2636
  const proxiedChat = client.chat ? createChatProxy(
1839
2637
  client.chat,
@@ -1841,7 +2639,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
1841
2639
  defaultContext,
1842
2640
  applyVendorHints,
1843
2641
  defaultPromptCompression,
1844
- promptCompressionStats
2642
+ promptCompressionStats,
2643
+ defaultSampling,
2644
+ provider
1845
2645
  ) : void 0;
1846
2646
  const proxiedResponses = typeof client.responses !== "undefined" ? createResponsesProxy(
1847
2647
  client.responses,
@@ -1849,7 +2649,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
1849
2649
  defaultContext,
1850
2650
  applyVendorHints,
1851
2651
  defaultPromptCompression,
1852
- promptCompressionStats
2652
+ promptCompressionStats,
2653
+ defaultSampling,
2654
+ provider
1853
2655
  ) : void 0;
1854
2656
  const handler = {
1855
2657
  get(target, prop, receiver) {
@@ -1968,14 +2770,16 @@ function streamOpenAIRoute(usageTap, openai, options) {
1968
2770
  });
1969
2771
  };
1970
2772
  }
1971
- function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2773
+ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1972
2774
  const completions = createChatCompletionsProxy(
1973
2775
  resource.completions,
1974
2776
  usageTap,
1975
2777
  defaultContext,
1976
2778
  applyVendorHints,
1977
2779
  defaultPromptCompression,
1978
- promptCompressionStats
2780
+ promptCompressionStats,
2781
+ defaultSampling,
2782
+ provider
1979
2783
  );
1980
2784
  const handler = {
1981
2785
  get(target, prop, receiver) {
@@ -1987,7 +2791,7 @@ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, d
1987
2791
  };
1988
2792
  return new Proxy(resource, handler);
1989
2793
  }
1990
- function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2794
+ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1991
2795
  if (!resource || typeof resource !== "object") {
1992
2796
  return void 0;
1993
2797
  }
@@ -2002,9 +2806,20 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2002
2806
  withUsage,
2003
2807
  promptCompression
2004
2808
  } = splitUsageOptions(options);
2005
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2809
+ const beginRequest = withRuntimeCompressionContext(
2810
+ resolveBeginRequest(defaultContext, usageContext),
2811
+ "openai",
2812
+ String(params.model)
2813
+ );
2006
2814
  const wantsStream = isStreamingRequest(params);
2007
2815
  return usageTap.withUsage(beginRequest, async (ctx) => {
2816
+ const sampleStartedAt = Date.now();
2817
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2818
+ usageTap,
2819
+ sampling: defaultSampling,
2820
+ beginRequest,
2821
+ input: params
2822
+ });
2008
2823
  const hintedParams = applyVendorHints ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints) : params;
2009
2824
  const finalParams = await compressResponsesParamsForCall({
2010
2825
  params: hintedParams,
@@ -2016,13 +2831,14 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2016
2831
  withUsage,
2017
2832
  operation: "responses.create"
2018
2833
  });
2834
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2019
2835
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2020
2836
  if (wantsStream) {
2021
2837
  const apiPromise2 = originalCreate(finalParams, request);
2022
2838
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
2023
2839
  ensureAsyncIterable(rawStream, "responses.create");
2024
2840
  const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
2025
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2841
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2026
2842
  if (usage) {
2027
2843
  ctx.setUsage(usage);
2028
2844
  }
@@ -2032,9 +2848,31 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2032
2848
  return wrappedPromise2;
2033
2849
  }
2034
2850
  const apiPromise = originalCreate(finalParams, request);
2035
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
2036
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2851
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2852
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2853
+ await captureMeteredOpenAISample({
2854
+ usageTap,
2855
+ sampling: defaultSampling,
2856
+ decision: sampleDecision,
2857
+ ctx,
2858
+ beginRequest,
2859
+ input: params,
2860
+ response,
2861
+ startedAt: sampleStartedAt
2862
+ });
2037
2863
  return response;
2864
+ }, async (error) => {
2865
+ await captureMeteredOpenAISample({
2866
+ usageTap,
2867
+ sampling: defaultSampling,
2868
+ decision: sampleDecision,
2869
+ ctx,
2870
+ beginRequest,
2871
+ input: params,
2872
+ error,
2873
+ startedAt: sampleStartedAt
2874
+ });
2875
+ throw error;
2038
2876
  });
2039
2877
  return wrappedPromise;
2040
2878
  }, withUsage);
@@ -2049,7 +2887,7 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2049
2887
  };
2050
2888
  return new Proxy(resource, handler);
2051
2889
  }
2052
- function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2890
+ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
2053
2891
  const originalCreate = resource.create.bind(resource);
2054
2892
  const streamCandidate = resource.stream;
2055
2893
  const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(resource) : void 0;
@@ -2060,9 +2898,20 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2060
2898
  withUsage,
2061
2899
  promptCompression
2062
2900
  } = splitUsageOptions(options);
2063
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2901
+ const beginRequest = withRuntimeCompressionContext(
2902
+ resolveBeginRequest(defaultContext, usageContext),
2903
+ "openai",
2904
+ String(params.model)
2905
+ );
2064
2906
  const wantsStream = isStreamingRequest(params);
2065
2907
  return usageTap.withUsage(beginRequest, async (ctx) => {
2908
+ const sampleStartedAt = Date.now();
2909
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2910
+ usageTap,
2911
+ sampling: defaultSampling,
2912
+ beginRequest,
2913
+ input: params
2914
+ });
2066
2915
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2067
2916
  const finalParams = await compressChatParamsForCall({
2068
2917
  params: hintedParams,
@@ -2074,13 +2923,14 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2074
2923
  withUsage,
2075
2924
  operation: "chat.completions.create"
2076
2925
  });
2926
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2077
2927
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2078
2928
  if (wantsStream) {
2079
2929
  const apiPromise2 = originalCreate(finalParams, request);
2080
2930
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
2081
2931
  ensureAsyncIterable(rawStream, "chat.completions.create");
2082
2932
  const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
2083
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2933
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2084
2934
  if (usage) {
2085
2935
  ctx.setUsage(usage);
2086
2936
  }
@@ -2090,9 +2940,31 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2090
2940
  return wrappedPromise2;
2091
2941
  }
2092
2942
  const apiPromise = originalCreate(finalParams, request);
2093
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
2094
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2943
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2944
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2945
+ await captureMeteredOpenAISample({
2946
+ usageTap,
2947
+ sampling: defaultSampling,
2948
+ decision: sampleDecision,
2949
+ ctx,
2950
+ beginRequest,
2951
+ input: params,
2952
+ response,
2953
+ startedAt: sampleStartedAt
2954
+ });
2095
2955
  return response;
2956
+ }, async (error) => {
2957
+ await captureMeteredOpenAISample({
2958
+ usageTap,
2959
+ sampling: defaultSampling,
2960
+ decision: sampleDecision,
2961
+ ctx,
2962
+ beginRequest,
2963
+ input: params,
2964
+ error,
2965
+ startedAt: sampleStartedAt
2966
+ });
2967
+ throw error;
2096
2968
  });
2097
2969
  return wrappedPromise;
2098
2970
  }, withUsage);
@@ -2104,7 +2976,11 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2104
2976
  withUsage,
2105
2977
  promptCompression
2106
2978
  } = splitUsageOptions(options);
2107
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2979
+ const beginRequest = withRuntimeCompressionContext(
2980
+ resolveBeginRequest(defaultContext, usageContext),
2981
+ "openai",
2982
+ String(params.model)
2983
+ );
2108
2984
  return usageTap.withUsage(beginRequest, async (ctx) => {
2109
2985
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2110
2986
  const finalParams = await compressChatParamsForCall({
@@ -2117,12 +2993,17 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2117
2993
  withUsage,
2118
2994
  operation: "chat.completions.stream"
2119
2995
  });
2996
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2120
2997
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2121
2998
  const apiPromise = originalStream(finalParams, request);
2122
2999
  const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
2123
3000
  ensureAsyncIterable(rawStream, "chat.completions.stream");
2124
3001
  const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
2125
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
3002
+ const usage = await extractUsageFromStream(
3003
+ rawStream,
3004
+ ctx.begin.data.vendorHints,
3005
+ provider
3006
+ );
2126
3007
  if (usage) {
2127
3008
  ctx.setUsage(usage);
2128
3009
  }
@@ -2146,19 +3027,70 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2146
3027
  return new Proxy(resource, handler);
2147
3028
  }
2148
3029
  async function compressChatParamsForCall(args) {
2149
- const compression = resolveEffectivePromptCompressionOptions(
3030
+ let compression = resolveEffectivePromptCompressionOptions(
2150
3031
  args.defaultPromptCompression,
2151
3032
  args.callPromptCompression
2152
3033
  );
3034
+ const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
3035
+ const policyDriven = !compression && Boolean(runtimePolicy);
3036
+ if (policyDriven && runtimePolicy) {
3037
+ if (!runtimePolicy.selected) {
3038
+ args.ctx.setUsage({
3039
+ runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
3040
+ });
3041
+ return args.params;
3042
+ }
3043
+ compression = openAIPolicyCompressionOptions(runtimePolicy);
3044
+ if (runtimePolicy.mode === "SHADOW") {
3045
+ const startedAt2 = Date.now();
3046
+ void compressChatParams(
3047
+ args.params,
3048
+ args.usageTap,
3049
+ compression,
3050
+ args.withUsage?.signal
3051
+ ).then((outcome2) => {
3052
+ args.ctx.setUsage({
3053
+ runtimeCompression: openAIRuntimeMeasurement({
3054
+ policy: runtimePolicy,
3055
+ telemetry: buildPromptCompressionTelemetry(outcome2.segments),
3056
+ latencyMs: Date.now() - startedAt2,
3057
+ shadow: true,
3058
+ originalPayload: args.params,
3059
+ resultPayload: outcome2.params
3060
+ })
3061
+ });
3062
+ }).catch(() => {
3063
+ args.ctx.setUsage({
3064
+ runtimeCompression: failedRuntimeMeasurement(
3065
+ runtimePolicy,
3066
+ Date.now() - startedAt2
3067
+ )
3068
+ });
3069
+ });
3070
+ return args.params;
3071
+ }
3072
+ }
2153
3073
  if (!compression) {
2154
3074
  return args.params;
2155
3075
  }
3076
+ const startedAt = Date.now();
2156
3077
  const outcome = await compressChatParams(
2157
3078
  args.params,
2158
3079
  args.usageTap,
2159
3080
  compression,
2160
3081
  args.withUsage?.signal
2161
3082
  );
3083
+ if (policyDriven && runtimePolicy) {
3084
+ const runtimeMeasurement = openAIRuntimeMeasurement({
3085
+ policy: runtimePolicy,
3086
+ telemetry: buildPromptCompressionTelemetry(outcome.segments),
3087
+ latencyMs: Date.now() - startedAt,
3088
+ originalPayload: args.params,
3089
+ resultPayload: outcome.params
3090
+ });
3091
+ args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
3092
+ if (runtimeMeasurement.decision !== "compressed") return args.params;
3093
+ }
2162
3094
  await recordCompressionOutcome({
2163
3095
  outcome,
2164
3096
  compression,
@@ -2171,19 +3103,70 @@ async function compressChatParamsForCall(args) {
2171
3103
  return outcome.params;
2172
3104
  }
2173
3105
  async function compressResponsesParamsForCall(args) {
2174
- const compression = resolveEffectivePromptCompressionOptions(
3106
+ let compression = resolveEffectivePromptCompressionOptions(
2175
3107
  args.defaultPromptCompression,
2176
3108
  args.callPromptCompression
2177
3109
  );
3110
+ const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
3111
+ const policyDriven = !compression && Boolean(runtimePolicy);
3112
+ if (policyDriven && runtimePolicy) {
3113
+ if (!runtimePolicy.selected) {
3114
+ args.ctx.setUsage({
3115
+ runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
3116
+ });
3117
+ return args.params;
3118
+ }
3119
+ compression = openAIPolicyCompressionOptions(runtimePolicy);
3120
+ if (runtimePolicy.mode === "SHADOW") {
3121
+ const startedAt2 = Date.now();
3122
+ void compressResponsesParams(
3123
+ args.params,
3124
+ args.usageTap,
3125
+ compression,
3126
+ args.withUsage?.signal
3127
+ ).then((outcome2) => {
3128
+ args.ctx.setUsage({
3129
+ runtimeCompression: openAIRuntimeMeasurement({
3130
+ policy: runtimePolicy,
3131
+ telemetry: buildPromptCompressionTelemetry(outcome2.segments),
3132
+ latencyMs: Date.now() - startedAt2,
3133
+ shadow: true,
3134
+ originalPayload: args.params,
3135
+ resultPayload: outcome2.params
3136
+ })
3137
+ });
3138
+ }).catch(() => {
3139
+ args.ctx.setUsage({
3140
+ runtimeCompression: failedRuntimeMeasurement(
3141
+ runtimePolicy,
3142
+ Date.now() - startedAt2
3143
+ )
3144
+ });
3145
+ });
3146
+ return args.params;
3147
+ }
3148
+ }
2178
3149
  if (!compression) {
2179
3150
  return args.params;
2180
3151
  }
3152
+ const startedAt = Date.now();
2181
3153
  const outcome = await compressResponsesParams(
2182
3154
  args.params,
2183
3155
  args.usageTap,
2184
3156
  compression,
2185
3157
  args.withUsage?.signal
2186
3158
  );
3159
+ if (policyDriven && runtimePolicy) {
3160
+ const runtimeMeasurement = openAIRuntimeMeasurement({
3161
+ policy: runtimePolicy,
3162
+ telemetry: buildPromptCompressionTelemetry(outcome.segments),
3163
+ latencyMs: Date.now() - startedAt,
3164
+ originalPayload: args.params,
3165
+ resultPayload: outcome.params
3166
+ });
3167
+ args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
3168
+ if (runtimeMeasurement.decision !== "compressed") return args.params;
3169
+ }
2187
3170
  await recordCompressionOutcome({
2188
3171
  outcome,
2189
3172
  compression,
@@ -2195,6 +3178,79 @@ async function compressResponsesParamsForCall(args) {
2195
3178
  });
2196
3179
  return outcome.params;
2197
3180
  }
3181
+ function openAIPolicyCompressionOptions(policy) {
3182
+ const roles = Object.fromEntries(
3183
+ ["system", "user", "tool", "assistant"].filter((role) => policy.snapshot.scope.messageRoles.includes(role)).map((role) => [role, true])
3184
+ );
3185
+ return {
3186
+ provider: "usagetap",
3187
+ roles,
3188
+ minContextTokens: policy.snapshot.scope.minimumTokens,
3189
+ failOpen: true
3190
+ };
3191
+ }
3192
+ function bypassedRuntimeMeasurement(policy) {
3193
+ return {
3194
+ policyId: policy.policyId,
3195
+ policyVersionId: policy.policyVersionId,
3196
+ policyVersion: policy.version,
3197
+ rolloutMode: policy.mode,
3198
+ decision: "bypassed",
3199
+ reasonCode: "not_in_rollout_sample",
3200
+ methodsAttempted: [],
3201
+ methodsApplied: [],
3202
+ originalTokens: 0,
3203
+ resultTokens: 0,
3204
+ savedTokens: 0,
3205
+ reductionPercentage: 0,
3206
+ compressionLatencyMs: 0,
3207
+ compressionComputeCostUsd: 0,
3208
+ financialsEstimated: true
3209
+ };
3210
+ }
3211
+ function failedRuntimeMeasurement(policy, latencyMs) {
3212
+ return {
3213
+ ...bypassedRuntimeMeasurement(policy),
3214
+ decision: "fallback",
3215
+ reasonCode: "transformation_failed",
3216
+ methodsAttempted: ["deterministic"],
3217
+ compressionLatencyMs: latencyMs
3218
+ };
3219
+ }
3220
+ function openAIRuntimeMeasurement(input) {
3221
+ const telemetry = input.telemetry;
3222
+ const originalTokens = telemetry?.originalTokens ?? estimatePromptTokens(JSON.stringify(input.originalPayload));
3223
+ const resultTokens = telemetry?.compressedTokens ?? originalTokens;
3224
+ const savedTokens = Math.max(0, telemetry?.savedTokens ?? 0);
3225
+ const reductionPercentage = originalTokens > 0 ? savedTokens / originalTokens * 100 : 0;
3226
+ const belowMinimumSize = originalTokens < input.policy.snapshot.scope.minimumTokens;
3227
+ const latencyExceeded = input.latencyMs > input.policy.snapshot.gates.maximumLatencyMs;
3228
+ const belowSavings = savedTokens < input.policy.snapshot.gates.minimumTokensRemoved || reductionPercentage < input.policy.snapshot.gates.minimumReductionPercentage;
3229
+ const accepted = !belowMinimumSize && !latencyExceeded && !belowSavings;
3230
+ const deepText = input.policy.snapshot.methods.deepText.enabled && !input.policy.snapshot.rollout.deterministicOnly && (input.policy.mode !== "CANARY" || input.policy.snapshot.rollout.deepTextCanaryAllowed);
3231
+ const reasonCode = belowMinimumSize ? "below_minimum_size" : latencyExceeded ? "latency_budget_exceeded" : belowSavings ? "below_minimum_savings" : void 0;
3232
+ const measurement = {
3233
+ policyId: input.policy.policyId,
3234
+ policyVersionId: input.policy.policyVersionId,
3235
+ policyVersion: input.policy.version,
3236
+ rolloutMode: input.policy.mode,
3237
+ decision: accepted ? input.shadow ? "bypassed" : "compressed" : latencyExceeded ? "fallback" : "skipped",
3238
+ ...reasonCode ? { reasonCode } : {},
3239
+ methodsAttempted: [
3240
+ "deterministic",
3241
+ ...deepText ? ["deep_text"] : []
3242
+ ],
3243
+ methodsApplied: accepted ? ["deterministic"] : [],
3244
+ originalTokens,
3245
+ resultTokens: accepted ? resultTokens : originalTokens,
3246
+ savedTokens: accepted ? savedTokens : 0,
3247
+ reductionPercentage: accepted ? reductionPercentage : 0,
3248
+ compressionLatencyMs: input.latencyMs,
3249
+ compressionComputeCostUsd: 0,
3250
+ financialsEstimated: true
3251
+ };
3252
+ return measurement;
3253
+ }
2198
3254
  async function recordCompressionOutcome(args) {
2199
3255
  const telemetry = buildPromptCompressionTelemetry(args.outcome.segments);
2200
3256
  if (!telemetry) {
@@ -2248,6 +3304,10 @@ async function compressChatParams(params, usageTap, compression, signal) {
2248
3304
  const result = await usageTap.compressPromptMessages(source, {
2249
3305
  provider: "usagetap",
2250
3306
  failOpen: compression.failOpen,
3307
+ mode: compression.mode,
3308
+ latencyBudgetMs: compression.latencyBudgetMs,
3309
+ compactEmptyUserMessages: compression.compactEmptyUserMessages,
3310
+ compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
2251
3311
  aggressiveness: resolveMessageEndpointAggressiveness(compression),
2252
3312
  signal
2253
3313
  });
@@ -2713,23 +3773,53 @@ function resolveBeginRequest(defaults, override) {
2713
3773
  }
2714
3774
  const tags = mergeTags(base.tags, current.tags);
2715
3775
  const begin = { customerId };
3776
+ const runtimeCompressionContext = current.runtimeCompressionContext ?? base.runtimeCompressionContext;
3777
+ if (runtimeCompressionContext) {
3778
+ begin.runtimeCompressionContext = runtimeCompressionContext;
3779
+ }
2716
3780
  const requested = current.requested ?? base.requested;
2717
3781
  if (requested) begin.requested = requested;
2718
3782
  const feature = current.feature ?? base.feature;
2719
3783
  if (feature) begin.feature = feature;
3784
+ const runId = current.runId ?? base.runId;
3785
+ if (runId) begin.runId = runId;
2720
3786
  const idempotency = current.idempotency ?? base.idempotency;
2721
3787
  if (idempotency) begin.idempotency = idempotency;
2722
3788
  const customerName = current.customerName ?? base.customerName;
2723
3789
  if (customerName) begin.customerName = customerName;
2724
3790
  const customerEmail = current.customerEmail ?? base.customerEmail;
2725
3791
  if (customerEmail) begin.customerEmail = customerEmail;
3792
+ const customerUserId = current.customerUserId ?? base.customerUserId;
3793
+ if (customerUserId) begin.customerUserId = customerUserId;
3794
+ const customerUserName = current.customerUserName ?? base.customerUserName;
3795
+ if (customerUserName) begin.customerUserName = customerUserName;
3796
+ const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
3797
+ if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
3798
+ const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
3799
+ if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
3800
+ const holdUsd = current.holdUsd ?? base.holdUsd;
3801
+ if (typeof holdUsd === "number") begin.holdUsd = holdUsd;
3802
+ const batch = current.batch ?? base.batch;
3803
+ if (typeof batch === "boolean") begin.batch = batch;
3804
+ const pricingMode = current.pricingMode ?? base.pricingMode;
3805
+ if (pricingMode) begin.pricingMode = pricingMode;
2726
3806
  if (tags?.length) {
2727
3807
  begin.tags = tags;
2728
3808
  }
2729
3809
  return begin;
2730
3810
  }
2731
- function transformApiPromise(apiPromise, onResolve) {
2732
- const resolvedPromise = Promise.resolve(apiPromise).then(onResolve);
3811
+ function withRuntimeCompressionContext(begin, provider, model) {
3812
+ return {
3813
+ ...begin,
3814
+ runtimeCompressionContext: {
3815
+ ...begin.runtimeCompressionContext ?? {},
3816
+ provider,
3817
+ model
3818
+ }
3819
+ };
3820
+ }
3821
+ function transformApiPromise(apiPromise, onResolve, onReject) {
3822
+ const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
2733
3823
  if (isObjectRecord(apiPromise)) {
2734
3824
  const proto = Object.getPrototypeOf(apiPromise);
2735
3825
  if (proto) {
@@ -2864,12 +3954,12 @@ function applyResponsesVendorHints(params, hints) {
2864
3954
  }
2865
3955
  return next;
2866
3956
  }
2867
- async function extractUsageFromStream(stream, hints) {
3957
+ async function extractUsageFromStream(stream, hints, provider = "openai") {
2868
3958
  const finalPayload = await resolveStreamFinalPayload(stream);
2869
3959
  if (!finalPayload) {
2870
3960
  return void 0;
2871
3961
  }
2872
- return inferUsageFromResponse(finalPayload, hints);
3962
+ return inferUsageFromResponse(finalPayload, hints, provider);
2873
3963
  }
2874
3964
  async function resolveStreamFinalPayload(stream) {
2875
3965
  if (!stream || typeof stream !== "object") {
@@ -2942,14 +4032,14 @@ function setHeaderIfPossible(res, key, value) {
2942
4032
  res.setHeader(key, value);
2943
4033
  }
2944
4034
  }
2945
- function tryInferUsage(response, hints, extractor, ctx) {
4035
+ function tryInferUsage(response, hints, extractor, ctx, provider = "openai") {
2946
4036
  const explicit = extractor?.(response);
2947
- const inferred = explicit ?? inferUsageFromResponse(response, hints);
4037
+ const inferred = explicit ?? inferUsageFromResponse(response, hints, provider);
2948
4038
  if (inferred) {
2949
4039
  ctx.setUsage(inferred);
2950
4040
  }
2951
4041
  }
2952
- function inferUsageFromResponse(response, hints) {
4042
+ function inferUsageFromResponse(response, hints, provider = "openai") {
2953
4043
  if (!response || typeof response !== "object") {
2954
4044
  return void 0;
2955
4045
  }
@@ -2957,12 +4047,41 @@ function inferUsageFromResponse(response, hints) {
2957
4047
  if (!candidate.usage) {
2958
4048
  return void 0;
2959
4049
  }
2960
- const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
4050
+ const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
4051
+ const responseEffort = normalizeExecutionReasoningEffort(
4052
+ candidate.reasoning?.effort
4053
+ );
2961
4054
  return {
4055
+ providerUsed: provider,
2962
4056
  modelUsed: candidate.model ?? hints?.preferredModel,
2963
- inputTokens: candidate.usage.prompt_tokens,
2964
- responseTokens: candidate.usage.completion_tokens,
2965
- cachedInputTokens
4057
+ inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
4058
+ responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
4059
+ cachedInputTokens,
4060
+ reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
4061
+ ...responseEffort ? {
4062
+ reasoningEffort: responseEffort,
4063
+ reasoningEffortSource: "provider_response"
4064
+ } : {},
4065
+ ...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {}
4066
+ };
4067
+ }
4068
+ function normalizeExecutionReasoningEffort(value) {
4069
+ return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
4070
+ }
4071
+ function openAIRequestExecutionMetadata(params, provider) {
4072
+ const record = params && typeof params === "object" ? params : {};
4073
+ const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
4074
+ const effort = normalizeExecutionReasoningEffort(
4075
+ record.reasoning_effort ?? reasoning?.effort ?? record.thinking_level
4076
+ );
4077
+ const mode = typeof reasoning?.type === "string" ? reasoning.type : typeof reasoning?.mode === "string" ? reasoning.mode : void 0;
4078
+ const rawBudget = reasoning?.budget_tokens ?? record.thinking_budget ?? record.thinking_budget_tokens;
4079
+ const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
4080
+ return {
4081
+ providerUsed: provider,
4082
+ ...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
4083
+ ...mode ? { reasoningMode: mode } : {},
4084
+ ...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
2966
4085
  };
2967
4086
  }
2968
4087
  function wrapStreamForUsageTap(source, finalize, ctx) {
@@ -3064,7 +4183,7 @@ function isIteratorResult(value) {
3064
4183
 
3065
4184
  // src/adapters/openrouter.ts
3066
4185
  function createOpenRouterAdapter(init) {
3067
- return createOpenAIAdapter(init);
4186
+ return createOpenAIAdapter({ ...init, provider: "openrouter" });
3068
4187
  }
3069
4188
 
3070
4189
  exports.OpenAIPromptCompressionStats = OpenAIPromptCompressionStats;
@@ -3075,6 +4194,7 @@ exports.streamOpenAIRoute = streamOpenAIRoute;
3075
4194
  exports.toNextResponse = toNextResponse;
3076
4195
  exports.withCompression = withCompression;
3077
4196
  exports.withMetering = withMetering;
4197
+ exports.withSampling = withSampling;
3078
4198
  exports.wrapOpenAI = wrapOpenAI;
3079
4199
  //# sourceMappingURL=index.cjs.map
3080
4200
  //# sourceMappingURL=index.cjs.map