@usagetap/sdk 1.3.2 → 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 +219 -24
  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 +1 -1
  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.2" ;
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) {
@@ -1917,14 +2719,16 @@ function createCompressionOnlyResponsesProxy(resource, usageTap, compression) {
1917
2719
  }
1918
2720
  });
1919
2721
  }
1920
- function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2722
+ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1921
2723
  const completions = createChatCompletionsProxy(
1922
2724
  resource.completions,
1923
2725
  usageTap,
1924
2726
  defaultContext,
1925
2727
  applyVendorHints,
1926
2728
  defaultPromptCompression,
1927
- promptCompressionStats
2729
+ promptCompressionStats,
2730
+ defaultSampling,
2731
+ provider
1928
2732
  );
1929
2733
  const handler = {
1930
2734
  get(target, prop, receiver) {
@@ -1936,7 +2740,7 @@ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, d
1936
2740
  };
1937
2741
  return new Proxy(resource, handler);
1938
2742
  }
1939
- function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2743
+ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1940
2744
  if (!resource || typeof resource !== "object") {
1941
2745
  return void 0;
1942
2746
  }
@@ -1951,9 +2755,20 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1951
2755
  withUsage,
1952
2756
  promptCompression
1953
2757
  } = splitUsageOptions(options);
1954
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2758
+ const beginRequest = withRuntimeCompressionContext(
2759
+ resolveBeginRequest(defaultContext, usageContext),
2760
+ "openai",
2761
+ String(params.model)
2762
+ );
1955
2763
  const wantsStream = isStreamingRequest(params);
1956
2764
  return usageTap.withUsage(beginRequest, async (ctx) => {
2765
+ const sampleStartedAt = Date.now();
2766
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2767
+ usageTap,
2768
+ sampling: defaultSampling,
2769
+ beginRequest,
2770
+ input: params
2771
+ });
1957
2772
  const hintedParams = applyVendorHints ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints) : params;
1958
2773
  const finalParams = await compressResponsesParamsForCall({
1959
2774
  params: hintedParams,
@@ -1965,13 +2780,14 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1965
2780
  withUsage,
1966
2781
  operation: "responses.create"
1967
2782
  });
2783
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
1968
2784
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
1969
2785
  if (wantsStream) {
1970
2786
  const apiPromise2 = originalCreate(finalParams, request);
1971
2787
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
1972
2788
  ensureAsyncIterable(rawStream, "responses.create");
1973
2789
  const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
1974
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2790
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
1975
2791
  if (usage) {
1976
2792
  ctx.setUsage(usage);
1977
2793
  }
@@ -1981,9 +2797,31 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1981
2797
  return wrappedPromise2;
1982
2798
  }
1983
2799
  const apiPromise = originalCreate(finalParams, request);
1984
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
1985
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2800
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2801
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2802
+ await captureMeteredOpenAISample({
2803
+ usageTap,
2804
+ sampling: defaultSampling,
2805
+ decision: sampleDecision,
2806
+ ctx,
2807
+ beginRequest,
2808
+ input: params,
2809
+ response,
2810
+ startedAt: sampleStartedAt
2811
+ });
1986
2812
  return response;
2813
+ }, async (error) => {
2814
+ await captureMeteredOpenAISample({
2815
+ usageTap,
2816
+ sampling: defaultSampling,
2817
+ decision: sampleDecision,
2818
+ ctx,
2819
+ beginRequest,
2820
+ input: params,
2821
+ error,
2822
+ startedAt: sampleStartedAt
2823
+ });
2824
+ throw error;
1987
2825
  });
1988
2826
  return wrappedPromise;
1989
2827
  }, withUsage);
@@ -1998,7 +2836,7 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1998
2836
  };
1999
2837
  return new Proxy(resource, handler);
2000
2838
  }
2001
- function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2839
+ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
2002
2840
  const originalCreate = resource.create.bind(resource);
2003
2841
  const streamCandidate = resource.stream;
2004
2842
  const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(resource) : void 0;
@@ -2009,9 +2847,20 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2009
2847
  withUsage,
2010
2848
  promptCompression
2011
2849
  } = splitUsageOptions(options);
2012
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2850
+ const beginRequest = withRuntimeCompressionContext(
2851
+ resolveBeginRequest(defaultContext, usageContext),
2852
+ "openai",
2853
+ String(params.model)
2854
+ );
2013
2855
  const wantsStream = isStreamingRequest(params);
2014
2856
  return usageTap.withUsage(beginRequest, async (ctx) => {
2857
+ const sampleStartedAt = Date.now();
2858
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2859
+ usageTap,
2860
+ sampling: defaultSampling,
2861
+ beginRequest,
2862
+ input: params
2863
+ });
2015
2864
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2016
2865
  const finalParams = await compressChatParamsForCall({
2017
2866
  params: hintedParams,
@@ -2023,13 +2872,14 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2023
2872
  withUsage,
2024
2873
  operation: "chat.completions.create"
2025
2874
  });
2875
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2026
2876
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2027
2877
  if (wantsStream) {
2028
2878
  const apiPromise2 = originalCreate(finalParams, request);
2029
2879
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
2030
2880
  ensureAsyncIterable(rawStream, "chat.completions.create");
2031
2881
  const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
2032
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2882
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2033
2883
  if (usage) {
2034
2884
  ctx.setUsage(usage);
2035
2885
  }
@@ -2039,9 +2889,31 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2039
2889
  return wrappedPromise2;
2040
2890
  }
2041
2891
  const apiPromise = originalCreate(finalParams, request);
2042
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
2043
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2892
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2893
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2894
+ await captureMeteredOpenAISample({
2895
+ usageTap,
2896
+ sampling: defaultSampling,
2897
+ decision: sampleDecision,
2898
+ ctx,
2899
+ beginRequest,
2900
+ input: params,
2901
+ response,
2902
+ startedAt: sampleStartedAt
2903
+ });
2044
2904
  return response;
2905
+ }, async (error) => {
2906
+ await captureMeteredOpenAISample({
2907
+ usageTap,
2908
+ sampling: defaultSampling,
2909
+ decision: sampleDecision,
2910
+ ctx,
2911
+ beginRequest,
2912
+ input: params,
2913
+ error,
2914
+ startedAt: sampleStartedAt
2915
+ });
2916
+ throw error;
2045
2917
  });
2046
2918
  return wrappedPromise;
2047
2919
  }, withUsage);
@@ -2053,7 +2925,11 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2053
2925
  withUsage,
2054
2926
  promptCompression
2055
2927
  } = splitUsageOptions(options);
2056
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2928
+ const beginRequest = withRuntimeCompressionContext(
2929
+ resolveBeginRequest(defaultContext, usageContext),
2930
+ "openai",
2931
+ String(params.model)
2932
+ );
2057
2933
  return usageTap.withUsage(beginRequest, async (ctx) => {
2058
2934
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2059
2935
  const finalParams = await compressChatParamsForCall({
@@ -2066,12 +2942,17 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2066
2942
  withUsage,
2067
2943
  operation: "chat.completions.stream"
2068
2944
  });
2945
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2069
2946
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2070
2947
  const apiPromise = originalStream(finalParams, request);
2071
2948
  const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
2072
2949
  ensureAsyncIterable(rawStream, "chat.completions.stream");
2073
2950
  const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
2074
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2951
+ const usage = await extractUsageFromStream(
2952
+ rawStream,
2953
+ ctx.begin.data.vendorHints,
2954
+ provider
2955
+ );
2075
2956
  if (usage) {
2076
2957
  ctx.setUsage(usage);
2077
2958
  }
@@ -2095,19 +2976,70 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2095
2976
  return new Proxy(resource, handler);
2096
2977
  }
2097
2978
  async function compressChatParamsForCall(args) {
2098
- const compression = resolveEffectivePromptCompressionOptions(
2979
+ let compression = resolveEffectivePromptCompressionOptions(
2099
2980
  args.defaultPromptCompression,
2100
2981
  args.callPromptCompression
2101
2982
  );
2983
+ const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
2984
+ const policyDriven = !compression && Boolean(runtimePolicy);
2985
+ if (policyDriven && runtimePolicy) {
2986
+ if (!runtimePolicy.selected) {
2987
+ args.ctx.setUsage({
2988
+ runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
2989
+ });
2990
+ return args.params;
2991
+ }
2992
+ compression = openAIPolicyCompressionOptions(runtimePolicy);
2993
+ if (runtimePolicy.mode === "SHADOW") {
2994
+ const startedAt2 = Date.now();
2995
+ void compressChatParams(
2996
+ args.params,
2997
+ args.usageTap,
2998
+ compression,
2999
+ args.withUsage?.signal
3000
+ ).then((outcome2) => {
3001
+ args.ctx.setUsage({
3002
+ runtimeCompression: openAIRuntimeMeasurement({
3003
+ policy: runtimePolicy,
3004
+ telemetry: buildPromptCompressionTelemetry(outcome2.segments),
3005
+ latencyMs: Date.now() - startedAt2,
3006
+ shadow: true,
3007
+ originalPayload: args.params,
3008
+ resultPayload: outcome2.params
3009
+ })
3010
+ });
3011
+ }).catch(() => {
3012
+ args.ctx.setUsage({
3013
+ runtimeCompression: failedRuntimeMeasurement(
3014
+ runtimePolicy,
3015
+ Date.now() - startedAt2
3016
+ )
3017
+ });
3018
+ });
3019
+ return args.params;
3020
+ }
3021
+ }
2102
3022
  if (!compression) {
2103
3023
  return args.params;
2104
3024
  }
3025
+ const startedAt = Date.now();
2105
3026
  const outcome = await compressChatParams(
2106
3027
  args.params,
2107
3028
  args.usageTap,
2108
3029
  compression,
2109
3030
  args.withUsage?.signal
2110
3031
  );
3032
+ if (policyDriven && runtimePolicy) {
3033
+ const runtimeMeasurement = openAIRuntimeMeasurement({
3034
+ policy: runtimePolicy,
3035
+ telemetry: buildPromptCompressionTelemetry(outcome.segments),
3036
+ latencyMs: Date.now() - startedAt,
3037
+ originalPayload: args.params,
3038
+ resultPayload: outcome.params
3039
+ });
3040
+ args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
3041
+ if (runtimeMeasurement.decision !== "compressed") return args.params;
3042
+ }
2111
3043
  await recordCompressionOutcome({
2112
3044
  outcome,
2113
3045
  compression,
@@ -2120,19 +3052,70 @@ async function compressChatParamsForCall(args) {
2120
3052
  return outcome.params;
2121
3053
  }
2122
3054
  async function compressResponsesParamsForCall(args) {
2123
- const compression = resolveEffectivePromptCompressionOptions(
3055
+ let compression = resolveEffectivePromptCompressionOptions(
2124
3056
  args.defaultPromptCompression,
2125
3057
  args.callPromptCompression
2126
3058
  );
3059
+ const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
3060
+ const policyDriven = !compression && Boolean(runtimePolicy);
3061
+ if (policyDriven && runtimePolicy) {
3062
+ if (!runtimePolicy.selected) {
3063
+ args.ctx.setUsage({
3064
+ runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
3065
+ });
3066
+ return args.params;
3067
+ }
3068
+ compression = openAIPolicyCompressionOptions(runtimePolicy);
3069
+ if (runtimePolicy.mode === "SHADOW") {
3070
+ const startedAt2 = Date.now();
3071
+ void compressResponsesParams(
3072
+ args.params,
3073
+ args.usageTap,
3074
+ compression,
3075
+ args.withUsage?.signal
3076
+ ).then((outcome2) => {
3077
+ args.ctx.setUsage({
3078
+ runtimeCompression: openAIRuntimeMeasurement({
3079
+ policy: runtimePolicy,
3080
+ telemetry: buildPromptCompressionTelemetry(outcome2.segments),
3081
+ latencyMs: Date.now() - startedAt2,
3082
+ shadow: true,
3083
+ originalPayload: args.params,
3084
+ resultPayload: outcome2.params
3085
+ })
3086
+ });
3087
+ }).catch(() => {
3088
+ args.ctx.setUsage({
3089
+ runtimeCompression: failedRuntimeMeasurement(
3090
+ runtimePolicy,
3091
+ Date.now() - startedAt2
3092
+ )
3093
+ });
3094
+ });
3095
+ return args.params;
3096
+ }
3097
+ }
2127
3098
  if (!compression) {
2128
3099
  return args.params;
2129
3100
  }
3101
+ const startedAt = Date.now();
2130
3102
  const outcome = await compressResponsesParams(
2131
3103
  args.params,
2132
3104
  args.usageTap,
2133
3105
  compression,
2134
3106
  args.withUsage?.signal
2135
3107
  );
3108
+ if (policyDriven && runtimePolicy) {
3109
+ const runtimeMeasurement = openAIRuntimeMeasurement({
3110
+ policy: runtimePolicy,
3111
+ telemetry: buildPromptCompressionTelemetry(outcome.segments),
3112
+ latencyMs: Date.now() - startedAt,
3113
+ originalPayload: args.params,
3114
+ resultPayload: outcome.params
3115
+ });
3116
+ args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
3117
+ if (runtimeMeasurement.decision !== "compressed") return args.params;
3118
+ }
2136
3119
  await recordCompressionOutcome({
2137
3120
  outcome,
2138
3121
  compression,
@@ -2144,6 +3127,79 @@ async function compressResponsesParamsForCall(args) {
2144
3127
  });
2145
3128
  return outcome.params;
2146
3129
  }
3130
+ function openAIPolicyCompressionOptions(policy) {
3131
+ const roles = Object.fromEntries(
3132
+ ["system", "user", "tool", "assistant"].filter((role) => policy.snapshot.scope.messageRoles.includes(role)).map((role) => [role, true])
3133
+ );
3134
+ return {
3135
+ provider: "usagetap",
3136
+ roles,
3137
+ minContextTokens: policy.snapshot.scope.minimumTokens,
3138
+ failOpen: true
3139
+ };
3140
+ }
3141
+ function bypassedRuntimeMeasurement(policy) {
3142
+ return {
3143
+ policyId: policy.policyId,
3144
+ policyVersionId: policy.policyVersionId,
3145
+ policyVersion: policy.version,
3146
+ rolloutMode: policy.mode,
3147
+ decision: "bypassed",
3148
+ reasonCode: "not_in_rollout_sample",
3149
+ methodsAttempted: [],
3150
+ methodsApplied: [],
3151
+ originalTokens: 0,
3152
+ resultTokens: 0,
3153
+ savedTokens: 0,
3154
+ reductionPercentage: 0,
3155
+ compressionLatencyMs: 0,
3156
+ compressionComputeCostUsd: 0,
3157
+ financialsEstimated: true
3158
+ };
3159
+ }
3160
+ function failedRuntimeMeasurement(policy, latencyMs) {
3161
+ return {
3162
+ ...bypassedRuntimeMeasurement(policy),
3163
+ decision: "fallback",
3164
+ reasonCode: "transformation_failed",
3165
+ methodsAttempted: ["deterministic"],
3166
+ compressionLatencyMs: latencyMs
3167
+ };
3168
+ }
3169
+ function openAIRuntimeMeasurement(input) {
3170
+ const telemetry = input.telemetry;
3171
+ const originalTokens = telemetry?.originalTokens ?? estimatePromptTokens(JSON.stringify(input.originalPayload));
3172
+ const resultTokens = telemetry?.compressedTokens ?? originalTokens;
3173
+ const savedTokens = Math.max(0, telemetry?.savedTokens ?? 0);
3174
+ const reductionPercentage = originalTokens > 0 ? savedTokens / originalTokens * 100 : 0;
3175
+ const belowMinimumSize = originalTokens < input.policy.snapshot.scope.minimumTokens;
3176
+ const latencyExceeded = input.latencyMs > input.policy.snapshot.gates.maximumLatencyMs;
3177
+ const belowSavings = savedTokens < input.policy.snapshot.gates.minimumTokensRemoved || reductionPercentage < input.policy.snapshot.gates.minimumReductionPercentage;
3178
+ const accepted = !belowMinimumSize && !latencyExceeded && !belowSavings;
3179
+ const deepText = input.policy.snapshot.methods.deepText.enabled && !input.policy.snapshot.rollout.deterministicOnly && (input.policy.mode !== "CANARY" || input.policy.snapshot.rollout.deepTextCanaryAllowed);
3180
+ const reasonCode = belowMinimumSize ? "below_minimum_size" : latencyExceeded ? "latency_budget_exceeded" : belowSavings ? "below_minimum_savings" : void 0;
3181
+ const measurement = {
3182
+ policyId: input.policy.policyId,
3183
+ policyVersionId: input.policy.policyVersionId,
3184
+ policyVersion: input.policy.version,
3185
+ rolloutMode: input.policy.mode,
3186
+ decision: accepted ? input.shadow ? "bypassed" : "compressed" : latencyExceeded ? "fallback" : "skipped",
3187
+ ...reasonCode ? { reasonCode } : {},
3188
+ methodsAttempted: [
3189
+ "deterministic",
3190
+ ...deepText ? ["deep_text"] : []
3191
+ ],
3192
+ methodsApplied: accepted ? ["deterministic"] : [],
3193
+ originalTokens,
3194
+ resultTokens: accepted ? resultTokens : originalTokens,
3195
+ savedTokens: accepted ? savedTokens : 0,
3196
+ reductionPercentage: accepted ? reductionPercentage : 0,
3197
+ compressionLatencyMs: input.latencyMs,
3198
+ compressionComputeCostUsd: 0,
3199
+ financialsEstimated: true
3200
+ };
3201
+ return measurement;
3202
+ }
2147
3203
  async function recordCompressionOutcome(args) {
2148
3204
  const telemetry = buildPromptCompressionTelemetry(args.outcome.segments);
2149
3205
  if (!telemetry) {
@@ -2197,6 +3253,10 @@ async function compressChatParams(params, usageTap, compression, signal) {
2197
3253
  const result = await usageTap.compressPromptMessages(source, {
2198
3254
  provider: "usagetap",
2199
3255
  failOpen: compression.failOpen,
3256
+ mode: compression.mode,
3257
+ latencyBudgetMs: compression.latencyBudgetMs,
3258
+ compactEmptyUserMessages: compression.compactEmptyUserMessages,
3259
+ compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
2200
3260
  aggressiveness: resolveMessageEndpointAggressiveness(compression),
2201
3261
  signal
2202
3262
  });
@@ -2662,23 +3722,53 @@ function resolveBeginRequest(defaults, override) {
2662
3722
  }
2663
3723
  const tags = mergeTags(base.tags, current.tags);
2664
3724
  const begin = { customerId };
3725
+ const runtimeCompressionContext = current.runtimeCompressionContext ?? base.runtimeCompressionContext;
3726
+ if (runtimeCompressionContext) {
3727
+ begin.runtimeCompressionContext = runtimeCompressionContext;
3728
+ }
2665
3729
  const requested = current.requested ?? base.requested;
2666
3730
  if (requested) begin.requested = requested;
2667
3731
  const feature = current.feature ?? base.feature;
2668
3732
  if (feature) begin.feature = feature;
3733
+ const runId = current.runId ?? base.runId;
3734
+ if (runId) begin.runId = runId;
2669
3735
  const idempotency = current.idempotency ?? base.idempotency;
2670
3736
  if (idempotency) begin.idempotency = idempotency;
2671
3737
  const customerName = current.customerName ?? base.customerName;
2672
3738
  if (customerName) begin.customerName = customerName;
2673
3739
  const customerEmail = current.customerEmail ?? base.customerEmail;
2674
3740
  if (customerEmail) begin.customerEmail = customerEmail;
3741
+ const customerUserId = current.customerUserId ?? base.customerUserId;
3742
+ if (customerUserId) begin.customerUserId = customerUserId;
3743
+ const customerUserName = current.customerUserName ?? base.customerUserName;
3744
+ if (customerUserName) begin.customerUserName = customerUserName;
3745
+ const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
3746
+ if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
3747
+ const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
3748
+ if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
3749
+ const holdUsd = current.holdUsd ?? base.holdUsd;
3750
+ if (typeof holdUsd === "number") begin.holdUsd = holdUsd;
3751
+ const batch = current.batch ?? base.batch;
3752
+ if (typeof batch === "boolean") begin.batch = batch;
3753
+ const pricingMode = current.pricingMode ?? base.pricingMode;
3754
+ if (pricingMode) begin.pricingMode = pricingMode;
2675
3755
  if (tags?.length) {
2676
3756
  begin.tags = tags;
2677
3757
  }
2678
3758
  return begin;
2679
3759
  }
2680
- function transformApiPromise(apiPromise, onResolve) {
2681
- const resolvedPromise = Promise.resolve(apiPromise).then(onResolve);
3760
+ function withRuntimeCompressionContext(begin, provider, model) {
3761
+ return {
3762
+ ...begin,
3763
+ runtimeCompressionContext: {
3764
+ ...begin.runtimeCompressionContext ?? {},
3765
+ provider,
3766
+ model
3767
+ }
3768
+ };
3769
+ }
3770
+ function transformApiPromise(apiPromise, onResolve, onReject) {
3771
+ const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
2682
3772
  if (isObjectRecord(apiPromise)) {
2683
3773
  const proto = Object.getPrototypeOf(apiPromise);
2684
3774
  if (proto) {
@@ -2813,12 +3903,12 @@ function applyResponsesVendorHints(params, hints) {
2813
3903
  }
2814
3904
  return next;
2815
3905
  }
2816
- async function extractUsageFromStream(stream, hints) {
3906
+ async function extractUsageFromStream(stream, hints, provider = "openai") {
2817
3907
  const finalPayload = await resolveStreamFinalPayload(stream);
2818
3908
  if (!finalPayload) {
2819
3909
  return void 0;
2820
3910
  }
2821
- return inferUsageFromResponse(finalPayload, hints);
3911
+ return inferUsageFromResponse(finalPayload, hints, provider);
2822
3912
  }
2823
3913
  async function resolveStreamFinalPayload(stream) {
2824
3914
  if (!stream || typeof stream !== "object") {
@@ -2891,14 +3981,14 @@ function setHeaderIfPossible(res, key, value) {
2891
3981
  res.setHeader(key, value);
2892
3982
  }
2893
3983
  }
2894
- function tryInferUsage(response, hints, extractor, ctx) {
3984
+ function tryInferUsage(response, hints, extractor, ctx, provider = "openai") {
2895
3985
  const explicit = extractor?.(response);
2896
- const inferred = explicit ?? inferUsageFromResponse(response, hints);
3986
+ const inferred = explicit ?? inferUsageFromResponse(response, hints, provider);
2897
3987
  if (inferred) {
2898
3988
  ctx.setUsage(inferred);
2899
3989
  }
2900
3990
  }
2901
- function inferUsageFromResponse(response, hints) {
3991
+ function inferUsageFromResponse(response, hints, provider = "openai") {
2902
3992
  if (!response || typeof response !== "object") {
2903
3993
  return void 0;
2904
3994
  }
@@ -2906,12 +3996,41 @@ function inferUsageFromResponse(response, hints) {
2906
3996
  if (!candidate.usage) {
2907
3997
  return void 0;
2908
3998
  }
2909
- const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
3999
+ const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
4000
+ const responseEffort = normalizeExecutionReasoningEffort(
4001
+ candidate.reasoning?.effort
4002
+ );
2910
4003
  return {
4004
+ providerUsed: provider,
2911
4005
  modelUsed: candidate.model ?? hints?.preferredModel,
2912
- inputTokens: candidate.usage.prompt_tokens,
2913
- responseTokens: candidate.usage.completion_tokens,
2914
- cachedInputTokens
4006
+ inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
4007
+ responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
4008
+ cachedInputTokens,
4009
+ reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
4010
+ ...responseEffort ? {
4011
+ reasoningEffort: responseEffort,
4012
+ reasoningEffortSource: "provider_response"
4013
+ } : {},
4014
+ ...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {}
4015
+ };
4016
+ }
4017
+ function normalizeExecutionReasoningEffort(value) {
4018
+ return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
4019
+ }
4020
+ function openAIRequestExecutionMetadata(params, provider) {
4021
+ const record = params && typeof params === "object" ? params : {};
4022
+ const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
4023
+ const effort = normalizeExecutionReasoningEffort(
4024
+ record.reasoning_effort ?? reasoning?.effort ?? record.thinking_level
4025
+ );
4026
+ const mode = typeof reasoning?.type === "string" ? reasoning.type : typeof reasoning?.mode === "string" ? reasoning.mode : void 0;
4027
+ const rawBudget = reasoning?.budget_tokens ?? record.thinking_budget ?? record.thinking_budget_tokens;
4028
+ const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
4029
+ return {
4030
+ providerUsed: provider,
4031
+ ...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
4032
+ ...mode ? { reasoningMode: mode } : {},
4033
+ ...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
2915
4034
  };
2916
4035
  }
2917
4036
  function wrapStreamForUsageTap(source, finalize, ctx) {
@@ -3013,12 +4132,28 @@ function isIteratorResult(value) {
3013
4132
 
3014
4133
  // src/adapters/openrouter.ts
3015
4134
  function createOpenRouterAdapter(init) {
3016
- return createOpenAIAdapter(init);
4135
+ return createOpenAIAdapter({ ...init, provider: "openrouter" });
4136
+ }
4137
+ function withMetering2(client, customer) {
4138
+ return withMetering(
4139
+ client,
4140
+ typeof customer === "string" ? { customerId: customer, provider: "openrouter" } : { ...customer, provider: "openrouter" }
4141
+ );
4142
+ }
4143
+ function wrapOpenAI2(client, usageTap, options = {}) {
4144
+ return wrapOpenAI(client, usageTap, {
4145
+ ...options,
4146
+ provider: "openrouter"
4147
+ });
4148
+ }
4149
+ function withSampling2(client, options = {}) {
4150
+ return withSampling(client, { ...options, provider: "openrouter" });
3017
4151
  }
3018
4152
 
3019
4153
  exports.createOpenRouterAdapter = createOpenRouterAdapter;
3020
4154
  exports.withCompression = withCompression;
3021
- exports.withMetering = withMetering;
3022
- exports.wrapOpenAI = wrapOpenAI;
4155
+ exports.withMetering = withMetering2;
4156
+ exports.withSampling = withSampling2;
4157
+ exports.wrapOpenAI = wrapOpenAI2;
3023
4158
  //# sourceMappingURL=index.cjs.map
3024
4159
  //# sourceMappingURL=index.cjs.map