@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
@@ -240,6 +240,11 @@ async function compressMessagesWithUsageTap(options) {
240
240
  aggressiveness,
241
241
  "UsageTap prompt message compression"
242
242
  );
243
+ if (options.latencyBudgetMs !== void 0 && (!Number.isFinite(options.latencyBudgetMs) || options.latencyBudgetMs < 0)) {
244
+ throw new Error(
245
+ "UsageTap prompt message compression latencyBudgetMs must be a non-negative number"
246
+ );
247
+ }
243
248
  const original = stableStringifyInput(options.input);
244
249
  const headers = {
245
250
  "content-type": "application/json"
@@ -254,7 +259,15 @@ async function compressMessagesWithUsageTap(options) {
254
259
  headers,
255
260
  body: JSON.stringify({
256
261
  ...cloneInputRecord(options.input),
257
- compression_settings: { aggressiveness }
262
+ compression_settings: {
263
+ aggressiveness,
264
+ ...options.mode === void 0 ? {} : { mode: options.mode },
265
+ ...options.latencyBudgetMs === void 0 ? {} : { latency_budget_ms: options.latencyBudgetMs },
266
+ ...options.compactEmptyUserMessages === void 0 ? {} : { compact_empty_user_messages: options.compactEmptyUserMessages },
267
+ ...options.compactDuplicateUserTextParts === void 0 ? {} : {
268
+ compact_duplicate_user_text_parts: options.compactDuplicateUserTextParts
269
+ }
270
+ }
258
271
  }),
259
272
  signal: options.signal
260
273
  }
@@ -750,10 +763,358 @@ function scalarToToon(value) {
750
763
  return JSON.stringify(text);
751
764
  }
752
765
 
766
+ // src/resources.ts
767
+ var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
768
+ var DEFAULT_GATEWAY_BASE_URL = "https://gateway.usagetap.com";
769
+ function normalizedBaseUrl(value) {
770
+ return `${value.replace(/\/+$/, "")}/`;
771
+ }
772
+ function errorMessage(payload, status) {
773
+ if (payload && typeof payload === "object") {
774
+ const record = payload;
775
+ const error = record.error;
776
+ if (error && typeof error === "object") {
777
+ const message2 = error.message;
778
+ if (typeof message2 === "string" && message2) return message2;
779
+ }
780
+ const message = record.message;
781
+ if (typeof message === "string" && message) return message;
782
+ }
783
+ return `UsageTap request failed with HTTP ${status}`;
784
+ }
785
+ function errorCode(status) {
786
+ if (status === 401 || status === 403) return "USAGETAP_AUTH_ERROR";
787
+ if (status === 429) return "USAGETAP_RATE_LIMITED";
788
+ if (status >= 500) return "USAGETAP_SERVER_ERROR";
789
+ return "USAGETAP_BAD_REQUEST";
790
+ }
791
+ var ResourceTransport = class {
792
+ baseUrl;
793
+ apiKey;
794
+ fetchImpl;
795
+ defaultHeaders;
796
+ sdkVersion;
797
+ constructor(baseUrl, config) {
798
+ this.baseUrl = normalizedBaseUrl(baseUrl);
799
+ this.apiKey = config.apiKey;
800
+ this.fetchImpl = config.fetchImpl;
801
+ this.defaultHeaders = config.headers ?? {};
802
+ this.sdkVersion = config.sdkVersion;
803
+ }
804
+ async request(request) {
805
+ const body = request.body === void 0 ? void 0 : JSON.stringify(request.body);
806
+ const headers = {
807
+ ...this.defaultHeaders,
808
+ accept: request.response === "data" ? CANONICAL_MEDIA_TYPE : request.response === "ndjson" ? "application/x-ndjson" : "application/json",
809
+ authorization: `Bearer ${this.apiKey}`,
810
+ "x-usage-sdk": `js/${this.sdkVersion}`,
811
+ ...body ? { "content-type": "application/json" } : {},
812
+ ...request.options?.idempotencyKey ? { "idempotency-key": request.options.idempotencyKey } : {},
813
+ ...request.options?.headers
814
+ };
815
+ let response;
816
+ try {
817
+ response = await this.fetchImpl(
818
+ new URL(request.path.replace(/^\/+/, ""), this.baseUrl),
819
+ {
820
+ method: request.method,
821
+ headers,
822
+ body,
823
+ signal: request.options?.signal
824
+ }
825
+ );
826
+ } catch (error) {
827
+ throw new UsageTapError(
828
+ "USAGETAP_NETWORK_ERROR",
829
+ "Failed to reach UsageTap",
830
+ { retryable: true, cause: error }
831
+ );
832
+ }
833
+ const requestId = response.headers.get("x-request-id") ?? response.headers.get("x-usage-correlation-id") ?? void 0;
834
+ const text = await response.text();
835
+ let payload;
836
+ if (text && request.response !== "ndjson") {
837
+ try {
838
+ payload = JSON.parse(text);
839
+ } catch (error) {
840
+ throw new UsageTapError(
841
+ "USAGETAP_INVALID_RESPONSE",
842
+ "UsageTap returned invalid JSON",
843
+ { status: response.status, correlationId: requestId, cause: error }
844
+ );
845
+ }
846
+ }
847
+ if (!response.ok) {
848
+ throw new UsageTapError(
849
+ errorCode(response.status),
850
+ errorMessage(payload, response.status),
851
+ {
852
+ status: response.status,
853
+ retryable: response.status === 429 || response.status >= 500,
854
+ correlationId: requestId,
855
+ details: payload && typeof payload === "object" ? payload : void 0
856
+ }
857
+ );
858
+ }
859
+ if (request.response === "ndjson") {
860
+ if (!text.trim()) return [];
861
+ try {
862
+ return text.trim().split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
863
+ } catch (error) {
864
+ throw new UsageTapError(
865
+ "USAGETAP_INVALID_RESPONSE",
866
+ "UsageTap returned invalid NDJSON",
867
+ { status: response.status, correlationId: requestId, cause: error }
868
+ );
869
+ }
870
+ }
871
+ if (request.response === "data") {
872
+ if (!payload || typeof payload !== "object" || !("data" in payload)) {
873
+ throw new UsageTapError(
874
+ "USAGETAP_INVALID_RESPONSE",
875
+ "UsageTap response missing data",
876
+ { status: response.status, correlationId: requestId }
877
+ );
878
+ }
879
+ return payload.data;
880
+ }
881
+ if (payload === void 0) {
882
+ throw new UsageTapError(
883
+ "USAGETAP_INVALID_RESPONSE",
884
+ "UsageTap response was empty",
885
+ { status: response.status, correlationId: requestId }
886
+ );
887
+ }
888
+ return payload;
889
+ }
890
+ };
891
+ function resourceId(value, keys, label) {
892
+ const id = typeof value === "string" ? value : keys.map((key) => value[key]).find((candidate) => Boolean(candidate?.trim()));
893
+ if (!id?.trim()) {
894
+ throw new UsageTapError(
895
+ "USAGETAP_BAD_REQUEST",
896
+ `${label} requires a non-empty ID`
897
+ );
898
+ }
899
+ return id.trim();
900
+ }
901
+ function terminalSummary(status) {
902
+ return status === "COMPLETE" || status === "FAILED";
903
+ }
904
+ function terminalGatewayBatch(status) {
905
+ return ["completed", "failed", "expired", "cancelled"].includes(status);
906
+ }
907
+ function validateWaitOptions(options) {
908
+ const pollIntervalMs = options.pollIntervalMs ?? 1500;
909
+ const timeoutMs = options.timeoutMs ?? 3 * 6e4;
910
+ if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 0) {
911
+ throw new UsageTapError(
912
+ "USAGETAP_BAD_REQUEST",
913
+ "pollIntervalMs must be a non-negative number"
914
+ );
915
+ }
916
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
917
+ throw new UsageTapError(
918
+ "USAGETAP_BAD_REQUEST",
919
+ "timeoutMs must be a positive number"
920
+ );
921
+ }
922
+ return { pollIntervalMs, timeoutMs };
923
+ }
924
+ var SummarizationResource = class {
925
+ summaries;
926
+ batches;
927
+ profiles;
928
+ measurements;
929
+ transport;
930
+ constructor(config) {
931
+ this.transport = new ResourceTransport(config.apiBaseUrl, config);
932
+ this.summaries = {
933
+ create: (params, options) => this.transport.request({
934
+ method: "POST",
935
+ path: "/v1/compression/summaries",
936
+ body: params,
937
+ options,
938
+ response: "data"
939
+ }),
940
+ retrieve: (jobId, options) => this.transport.request({
941
+ method: "GET",
942
+ path: `/v1/compression/jobs/${encodeURIComponent(
943
+ resourceId(jobId, ["jobId"], "summaries.retrieve")
944
+ )}`,
945
+ options,
946
+ response: "data"
947
+ }),
948
+ wait: (job, options) => this.waitForSummary(job, options)
949
+ };
950
+ this.batches = {
951
+ create: (params, options) => this.transport.request({
952
+ method: "POST",
953
+ path: "/v1/compression/batches",
954
+ body: params,
955
+ options,
956
+ response: "data"
957
+ }),
958
+ retrieve: (batchId, options) => this.transport.request({
959
+ method: "GET",
960
+ path: `/v1/compression/batches/${encodeURIComponent(
961
+ resourceId(batchId, ["batchId"], "summarization.batches.retrieve")
962
+ )}`,
963
+ options,
964
+ response: "data"
965
+ }),
966
+ wait: (batch, options) => this.waitForBatch(batch, options)
967
+ };
968
+ this.profiles = {
969
+ retrieve: (profile, options) => this.transport.request({
970
+ method: "GET",
971
+ path: `/v1/compression/profiles/${encodeURIComponent(
972
+ resourceId(profile, [], "summarization.profiles.retrieve")
973
+ )}`,
974
+ options,
975
+ response: "data"
976
+ })
977
+ };
978
+ this.measurements = {
979
+ create: (params, options) => this.transport.request({
980
+ method: "POST",
981
+ path: "/v1/compression/measurements",
982
+ body: params,
983
+ options,
984
+ response: "data"
985
+ })
986
+ };
987
+ }
988
+ async waitForSummary(value, options = {}) {
989
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
990
+ const deadline = Date.now() + timeoutMs;
991
+ let job = typeof value === "string" ? await this.summaries.retrieve(value, options) : value;
992
+ while (!terminalSummary(job.status)) {
993
+ if (Date.now() >= deadline) {
994
+ throw new UsageTapError(
995
+ "USAGETAP_RETRY_EXHAUSTED",
996
+ `Summarization job ${job.jobId} did not finish before timeout`,
997
+ { retryable: true }
998
+ );
999
+ }
1000
+ await sleep(pollIntervalMs, options.signal);
1001
+ job = await this.summaries.retrieve(job.jobId, options);
1002
+ }
1003
+ return job;
1004
+ }
1005
+ async waitForBatch(value, options = {}) {
1006
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
1007
+ const deadline = Date.now() + timeoutMs;
1008
+ let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
1009
+ while (!terminalSummary(batch.status)) {
1010
+ if (Date.now() >= deadline) {
1011
+ throw new UsageTapError(
1012
+ "USAGETAP_RETRY_EXHAUSTED",
1013
+ `Summarization batch ${batch.batchId} did not finish before timeout`,
1014
+ { retryable: true }
1015
+ );
1016
+ }
1017
+ await sleep(pollIntervalMs, options.signal);
1018
+ batch = await this.batches.retrieve(batch.batchId, options);
1019
+ }
1020
+ return batch;
1021
+ }
1022
+ };
1023
+ var GatewayResource = class {
1024
+ chat;
1025
+ models;
1026
+ batches;
1027
+ transport;
1028
+ idempotencyGenerator;
1029
+ constructor(config) {
1030
+ this.transport = new ResourceTransport(
1031
+ config.gatewayBaseUrl ?? DEFAULT_GATEWAY_BASE_URL,
1032
+ config
1033
+ );
1034
+ this.idempotencyGenerator = config.idempotencyGenerator ?? createIdempotencyKey;
1035
+ this.chat = {
1036
+ completions: {
1037
+ create: (params, options) => this.transport.request({
1038
+ method: "POST",
1039
+ path: "/v1/chat/completions",
1040
+ body: params,
1041
+ options,
1042
+ response: "json"
1043
+ })
1044
+ }
1045
+ };
1046
+ this.models = {
1047
+ list: (options) => this.transport.request({
1048
+ method: "GET",
1049
+ path: "/v1/models",
1050
+ options,
1051
+ response: "json"
1052
+ })
1053
+ };
1054
+ this.batches = {
1055
+ create: (params, options = {}) => this.transport.request({
1056
+ method: "POST",
1057
+ path: "/v1/batches",
1058
+ body: params,
1059
+ options: {
1060
+ ...options,
1061
+ idempotencyKey: options.idempotencyKey ?? this.idempotencyGenerator()
1062
+ },
1063
+ response: "json"
1064
+ }),
1065
+ retrieve: (batchId, options) => this.transport.request({
1066
+ method: "GET",
1067
+ path: `/v1/batches/${encodeURIComponent(
1068
+ resourceId(batchId, ["id"], "gateway.batches.retrieve")
1069
+ )}`,
1070
+ options,
1071
+ response: "json"
1072
+ }),
1073
+ wait: (batch, options) => this.waitForBatch(batch, options),
1074
+ cancel: (batchId, options) => this.transport.request({
1075
+ method: "POST",
1076
+ path: `/v1/batches/${encodeURIComponent(
1077
+ resourceId(batchId, ["id"], "gateway.batches.cancel")
1078
+ )}/cancel`,
1079
+ options,
1080
+ response: "json"
1081
+ }),
1082
+ results: (batchId, options) => this.transport.request({
1083
+ method: "GET",
1084
+ path: `/v1/batches/${encodeURIComponent(
1085
+ resourceId(batchId, ["id"], "gateway.batches.results")
1086
+ )}/results`,
1087
+ options,
1088
+ response: "ndjson"
1089
+ })
1090
+ };
1091
+ }
1092
+ async waitForBatch(value, options = {}) {
1093
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
1094
+ const deadline = Date.now() + timeoutMs;
1095
+ let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
1096
+ while (!terminalGatewayBatch(batch.status)) {
1097
+ if (Date.now() >= deadline) {
1098
+ throw new UsageTapError(
1099
+ "USAGETAP_RETRY_EXHAUSTED",
1100
+ `Gateway batch ${batch.id} did not finish before timeout`,
1101
+ { retryable: true }
1102
+ );
1103
+ }
1104
+ await sleep(pollIntervalMs, options.signal);
1105
+ batch = await this.batches.retrieve(batch.id, options);
1106
+ }
1107
+ return batch;
1108
+ }
1109
+ };
1110
+
753
1111
  // src/client.ts
754
1112
  var CALL_BEGIN_PATH = "call_begin";
755
1113
  var CALL_END_PATH = "call_end";
756
1114
  var COMPRESS_PROMPT_PATH = "compress_prompt";
1115
+ var SAMPLES_PATH = "samples";
1116
+ var SAMPLING_SETTINGS_PATH = "sampling/settings";
1117
+ var SAMPLING_DECIDE_PATH = "sampling/decide";
757
1118
  var CHECK_USAGE_PATH = "customers/{customerId}/usage";
758
1119
  var CREATE_CUSTOMER_PATH = "customers";
759
1120
  var CHANGE_PLAN_PATH = "customers/{customerId}/change_plan";
@@ -764,11 +1125,16 @@ var CORRELATION_HEADER = "x-usage-correlation-id";
764
1125
  var IDEMPOTENCY_HEADER = "idempotency-key";
765
1126
  var SDK_HEADER = "x-usage-sdk";
766
1127
  var USER_AGENT = "UsageTapClient";
767
- var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
1128
+ var CANONICAL_MEDIA_TYPE2 = "application/vnd.usagetap.v1+json";
768
1129
  var DEFAULT_BASE_URL = "https://api.usagetap.com";
769
- var SDK_VERSION = "1.3.1" ;
1130
+ var DEFAULT_RUN_INACTIVITY_MS = 60 * 60 * 1e3;
1131
+ var SDK_VERSION = "1.4.0" ;
770
1132
  var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
771
1133
  var UsageTapClient = class {
1134
+ /** OpenAI-compatible chat, model, and native batch operations. */
1135
+ gateway;
1136
+ /** Published-profile context summarization operations. */
1137
+ summarization;
772
1138
  apiKey;
773
1139
  baseUrl;
774
1140
  fetchImpl;
@@ -793,6 +1159,11 @@ var UsageTapClient = class {
793
1159
  usageTapCompressionMessagesEndpoint;
794
1160
  usageTapCompressionModel;
795
1161
  usageTapCompressionAggressiveness;
1162
+ sampling;
1163
+ samplingSettingsCacheMs;
1164
+ circuitBreaker;
1165
+ circuitBreakerRuns = /* @__PURE__ */ new Map();
1166
+ samplingSettingsCache;
796
1167
  constructor(options = {}) {
797
1168
  const apiKey = options.apiKey?.trim() || readEnvironmentVariable("USAGETAP_API_KEY");
798
1169
  const baseUrl = options.baseUrl?.trim() || readEnvironmentVariable("USAGETAP_BASE_URL") || DEFAULT_BASE_URL;
@@ -815,10 +1186,21 @@ var UsageTapClient = class {
815
1186
  "A global fetch implementation was not found. Pass fetchImpl in UsageTapClientOptions."
816
1187
  );
817
1188
  }
818
- const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
819
- this.baseUrl = new URL(normalizedBaseUrl);
1189
+ const normalizedBaseUrl2 = normalizeBaseUrl(baseUrl);
1190
+ this.baseUrl = new URL(normalizedBaseUrl2);
820
1191
  this.apiKey = apiKey;
821
1192
  this.fetchImpl = wrapFetchImplementation(fetchCandidate, !options.fetchImpl);
1193
+ const resourceConfig = {
1194
+ apiKey,
1195
+ apiBaseUrl: normalizedBaseUrl2,
1196
+ gatewayBaseUrl: options.gatewayBaseUrl?.trim() || readEnvironmentVariable("USAGETAP_GATEWAY_URL"),
1197
+ fetchImpl: this.fetchImpl,
1198
+ headers: options.headers,
1199
+ sdkVersion: SDK_VERSION,
1200
+ idempotencyGenerator: options.idempotencyGenerator
1201
+ };
1202
+ this.gateway = new GatewayResource(resourceConfig);
1203
+ this.summarization = new SummarizationResource(resourceConfig);
822
1204
  this.defaultFeature = options.defaultFeature;
823
1205
  this.defaultTags = options.defaultTags?.length ? dedupeStrings(options.defaultTags) : void 0;
824
1206
  this.defaultHeaders = options.headers ? normalizeHeaderDictionary(options.headers) : {};
@@ -840,11 +1222,122 @@ var UsageTapClient = class {
840
1222
  this.usageTapCompressionMessagesEndpoint = options.usageTapCompressionMessagesEndpoint;
841
1223
  this.usageTapCompressionModel = options.usageTapCompressionModel;
842
1224
  this.usageTapCompressionAggressiveness = options.usageTapCompressionAggressiveness;
1225
+ this.sampling = options.sampling;
1226
+ this.samplingSettingsCacheMs = Number.isFinite(options.samplingSettingsCacheMs) ? Math.max(0, Number(options.samplingSettingsCacheMs)) : 5 * 60 * 1e3;
1227
+ if (options.circuitBreaker) {
1228
+ const maxCallsPerRun = options.circuitBreaker.maxCallsPerRun;
1229
+ if (!Number.isInteger(maxCallsPerRun) || maxCallsPerRun < 1) {
1230
+ throw new UsageTapError(
1231
+ "USAGETAP_BAD_REQUEST",
1232
+ "circuitBreaker.maxCallsPerRun must be a positive integer"
1233
+ );
1234
+ }
1235
+ const runInactivityMs = options.circuitBreaker.runInactivityMs ?? DEFAULT_RUN_INACTIVITY_MS;
1236
+ if (!Number.isFinite(runInactivityMs) || runInactivityMs < 1) {
1237
+ throw new UsageTapError(
1238
+ "USAGETAP_BAD_REQUEST",
1239
+ "circuitBreaker.runInactivityMs must be a positive number"
1240
+ );
1241
+ }
1242
+ this.circuitBreaker = {
1243
+ maxCallsPerRun,
1244
+ runInactivityMs
1245
+ };
1246
+ }
1247
+ }
1248
+ shouldSample(request, policy = this.sampling || void 0) {
1249
+ if (!policy) return false;
1250
+ const rate = Math.min(1, Math.max(0, Number(policy.rate) || 0));
1251
+ if (rate <= 0) return false;
1252
+ const customerId = request.customerId?.trim();
1253
+ if (customerId && policy.customers?.exclude?.includes(customerId)) return false;
1254
+ const feature = request.feature?.trim();
1255
+ if (feature && policy.features?.exclude?.includes(feature)) return false;
1256
+ const included = policy.features?.include?.filter(Boolean) ?? [];
1257
+ if (included.length > 0 && (!feature || !included.includes(feature))) return false;
1258
+ const minimum = Math.max(0, Math.round(policy.minInputTokens ?? 0));
1259
+ if (minimum > 0 && estimatePromptTokens(request.input) < minimum) return false;
1260
+ return (policy.random ?? Math.random)() < rate;
1261
+ }
1262
+ async getSamplingSettings(options = {}) {
1263
+ const now = Date.now();
1264
+ if (!options.forceRefresh && this.samplingSettingsCache && this.samplingSettingsCache.expiresAtMs > now) {
1265
+ return {
1266
+ result: { status: "ACCEPTED", code: "SAMPLING_SETTINGS_CACHED" },
1267
+ data: this.samplingSettingsCache.settings,
1268
+ correlationId: options.correlationId ?? "local-cache"
1269
+ };
1270
+ }
1271
+ const response = await this.requestGet(
1272
+ SAMPLING_SETTINGS_PATH,
1273
+ {
1274
+ signal: options.signal,
1275
+ headers: options.headers,
1276
+ retries: options.retries,
1277
+ correlationId: options.correlationId
1278
+ }
1279
+ );
1280
+ const serverCacheMs = Math.max(0, Number(response.data.cacheSeconds) || 0) * 1e3;
1281
+ const cacheMs = Math.min(this.samplingSettingsCacheMs, serverCacheMs);
1282
+ this.samplingSettingsCache = {
1283
+ settings: response.data,
1284
+ expiresAtMs: now + cacheMs
1285
+ };
1286
+ return response;
1287
+ }
1288
+ async shouldSampleAsync(request, policy) {
1289
+ if (policy) return this.shouldSample(request, policy);
1290
+ if (this.sampling === false) return false;
1291
+ if (this.sampling) return this.shouldSample(request, this.sampling);
1292
+ try {
1293
+ const settings = await this.getSamplingSettings();
1294
+ return this.shouldSample(request, settings.data);
1295
+ } catch {
1296
+ return false;
1297
+ }
1298
+ }
1299
+ async decideSample(request, options = {}) {
1300
+ const hasTokens = Number.isFinite(request.inputTokens) && Number(request.inputTokens) >= 0;
1301
+ const hasCharacters = Number.isFinite(request.inputCharacters) && Number(request.inputCharacters) >= 0;
1302
+ if (!hasTokens && !hasCharacters) {
1303
+ throw new UsageTapError(
1304
+ "USAGETAP_BAD_REQUEST",
1305
+ "decideSample requires inputTokens or inputCharacters"
1306
+ );
1307
+ }
1308
+ return this.request(
1309
+ SAMPLING_DECIDE_PATH,
1310
+ request,
1311
+ options
1312
+ );
1313
+ }
1314
+ async captureSample(request, options = {}) {
1315
+ if (!request || request.input === void 0) {
1316
+ throw new UsageTapError(
1317
+ "USAGETAP_BAD_REQUEST",
1318
+ "captureSample requires input"
1319
+ );
1320
+ }
1321
+ if (!request.provider?.trim()) {
1322
+ throw new UsageTapError(
1323
+ "USAGETAP_BAD_REQUEST",
1324
+ "captureSample requires provider"
1325
+ );
1326
+ }
1327
+ const sampleId = request.sampleId?.trim() || this.idempotencyGenerator();
1328
+ return this.request(
1329
+ SAMPLES_PATH,
1330
+ { ...request, sampleId },
1331
+ { ...options, idempotencyKey: sampleId }
1332
+ );
843
1333
  }
844
1334
  async beginCall(request, options = {}) {
845
1335
  const idempotencyKey = request.idempotencyKey ?? request.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
1336
+ this.reserveRunCall(request, idempotencyKey);
1337
+ const apiRequest = { ...request };
1338
+ delete apiRequest.runId;
846
1339
  const payload = {
847
- ...request,
1340
+ ...apiRequest,
848
1341
  feature: request.feature ?? this.defaultFeature,
849
1342
  tags: this.mergeTags(request.tags)
850
1343
  };
@@ -862,6 +1355,28 @@ var UsageTapClient = class {
862
1355
  );
863
1356
  return response;
864
1357
  }
1358
+ /**
1359
+ * Inspect a configured run circuit breaker without consuming another call.
1360
+ */
1361
+ canRunContinue(request) {
1362
+ const identity = this.resolveRunIdentity(request);
1363
+ if (!identity || !this.circuitBreaker) {
1364
+ throw new UsageTapError(
1365
+ "USAGETAP_BAD_REQUEST",
1366
+ "canRunContinue requires circuitBreaker configuration and a non-empty runId"
1367
+ );
1368
+ }
1369
+ this.expireInactiveRuns();
1370
+ const calls = this.circuitBreakerRuns.get(identity.key)?.calls ?? 0;
1371
+ return this.createCircuitBreakerDecision(identity.customerId, identity.runId, calls);
1372
+ }
1373
+ /**
1374
+ * Release local state after a workflow finishes. Returns true when state existed.
1375
+ */
1376
+ resetRun(request) {
1377
+ const identity = this.resolveRunIdentity(request);
1378
+ return identity ? this.circuitBreakerRuns.delete(identity.key) : false;
1379
+ }
865
1380
  async promptCompress(request, options = {}) {
866
1381
  if (!request?.callId) {
867
1382
  throw new UsageTapError(
@@ -959,6 +1474,10 @@ var UsageTapClient = class {
959
1474
  usageTapCompressionMessagesEndpoint: this.usageTapCompressionMessagesEndpoint,
960
1475
  aggressiveness: options.aggressiveness ?? this.aggressiveness,
961
1476
  usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
1477
+ mode: options.mode,
1478
+ latencyBudgetMs: options.latencyBudgetMs,
1479
+ compactEmptyUserMessages: options.compactEmptyUserMessages,
1480
+ compactDuplicateUserTextParts: options.compactDuplicateUserTextParts,
962
1481
  fetchImpl: this.fetchImpl,
963
1482
  signal: options.signal,
964
1483
  failOpen: options.failOpen
@@ -1000,11 +1519,17 @@ var UsageTapClient = class {
1000
1519
  callId: request.callId,
1001
1520
  feature: feature ?? this.defaultFeature,
1002
1521
  tags: tags ?? this.defaultTags,
1522
+ providerUsed: request.providerUsed,
1003
1523
  modelUsed: request.modelUsed,
1524
+ reasoningEffort: request.reasoningEffort,
1525
+ reasoningEffortSource: request.reasoningEffortSource,
1526
+ reasoningMode: request.reasoningMode,
1527
+ reasoningBudgetTokens: request.reasoningBudgetTokens,
1004
1528
  metrics: {
1005
1529
  inputTokens: request.inputTokens,
1006
1530
  responseTokens: request.responseTokens,
1007
1531
  cachedInputTokens: request.cachedInputTokens,
1532
+ cacheWriteInputTokens: request.cacheWriteInputTokens,
1008
1533
  reasoningTokens: request.reasoningTokens,
1009
1534
  searches: request.searches,
1010
1535
  audioSeconds: request.audioSeconds,
@@ -1112,6 +1637,15 @@ var UsageTapClient = class {
1112
1637
  meterSlot: request.meterSlot,
1113
1638
  amount: request.amount
1114
1639
  };
1640
+ if (request.customerUserId) {
1641
+ payload.customerUserId = request.customerUserId;
1642
+ }
1643
+ if (request.customerUserName) {
1644
+ payload.customerUserName = request.customerUserName;
1645
+ }
1646
+ if (request.customerUserEmail) {
1647
+ payload.customerUserEmail = request.customerUserEmail;
1648
+ }
1115
1649
  if (request.feature) {
1116
1650
  payload.feature = request.feature;
1117
1651
  }
@@ -1148,6 +1682,11 @@ var UsageTapClient = class {
1148
1682
  const beginPayload = idempotencyKey ? { ...beginRequest, idempotencyKey, idempotency: idempotencyKey } : { ...beginRequest };
1149
1683
  const beginResponse = await this.beginCall(beginPayload, options);
1150
1684
  let usage = {};
1685
+ const pricingMode = beginResponse.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
1686
+ if (pricingMode) {
1687
+ usage.pricingMode = pricingMode;
1688
+ usage.batch = pricingMode === "batch";
1689
+ }
1151
1690
  const initialStripeCustomerId = typeof beginResponse.data.stripeCustomerId === "string" ? beginResponse.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
1152
1691
  if (initialStripeCustomerId) {
1153
1692
  usage = { ...usage, stripeCustomerId: initialStripeCustomerId };
@@ -1215,17 +1754,86 @@ var UsageTapClient = class {
1215
1754
  toPromptCompressionTelemetry(result) {
1216
1755
  return {
1217
1756
  provider: result.provider,
1218
- originalCharacters: result.originalCharacters,
1219
- compressedCharacters: result.compressedCharacters,
1220
- savedCharacters: result.savedCharacters,
1221
1757
  originalTokens: result.originalTokens,
1222
1758
  compressedTokens: result.compressedTokens,
1223
1759
  savedTokens: result.savedTokens,
1224
1760
  tokenSavingsRatio: result.tokenSavingsRatio,
1225
- savingsRatio: result.savingsRatio,
1226
1761
  techniques: result.techniques
1227
1762
  };
1228
1763
  }
1764
+ reserveRunCall(request, idempotencyKey) {
1765
+ const identity = this.resolveRunIdentity(request);
1766
+ if (!identity || !this.circuitBreaker) return;
1767
+ this.expireInactiveRuns();
1768
+ const now = Date.now();
1769
+ const state = this.circuitBreakerRuns.get(identity.key) ?? {
1770
+ calls: 0,
1771
+ reservationKeys: /* @__PURE__ */ new Set(),
1772
+ lastSeenAtMs: now
1773
+ };
1774
+ const reservationKey = idempotencyKey ?? this.idempotencyGenerator();
1775
+ state.lastSeenAtMs = now;
1776
+ if (state.reservationKeys.has(reservationKey)) {
1777
+ this.circuitBreakerRuns.set(identity.key, state);
1778
+ return;
1779
+ }
1780
+ const decision = this.createCircuitBreakerDecision(
1781
+ identity.customerId,
1782
+ identity.runId,
1783
+ state.calls
1784
+ );
1785
+ if (!decision.allowed) {
1786
+ throw new UsageTapError(
1787
+ "USAGETAP_CIRCUIT_OPEN",
1788
+ `Run ${identity.runId} reached its ${decision.limit}-call circuit-breaker limit`,
1789
+ {
1790
+ details: {
1791
+ reason: decision.reason,
1792
+ customerId: identity.customerId,
1793
+ runId: identity.runId,
1794
+ calls: decision.calls,
1795
+ limit: decision.limit,
1796
+ remaining: decision.remaining
1797
+ }
1798
+ }
1799
+ );
1800
+ }
1801
+ state.calls += 1;
1802
+ state.reservationKeys.add(reservationKey);
1803
+ this.circuitBreakerRuns.set(identity.key, state);
1804
+ }
1805
+ resolveRunIdentity(request) {
1806
+ const customerId = request.customerId?.trim();
1807
+ const runId = request.runId?.trim();
1808
+ if (!customerId || !runId) return void 0;
1809
+ return {
1810
+ key: `${customerId}\0${runId}`,
1811
+ customerId,
1812
+ runId
1813
+ };
1814
+ }
1815
+ createCircuitBreakerDecision(customerId, runId, calls) {
1816
+ const limit = this.circuitBreaker?.maxCallsPerRun ?? 0;
1817
+ const allowed = calls < limit;
1818
+ return {
1819
+ allowed,
1820
+ ...allowed ? {} : { reason: "max_calls_per_run" },
1821
+ customerId,
1822
+ runId,
1823
+ calls,
1824
+ limit,
1825
+ remaining: Math.max(0, limit - calls)
1826
+ };
1827
+ }
1828
+ expireInactiveRuns() {
1829
+ if (!this.circuitBreaker || this.circuitBreakerRuns.size === 0) return;
1830
+ const expiredBefore = Date.now() - this.circuitBreaker.runInactivityMs;
1831
+ for (const [key, state] of this.circuitBreakerRuns) {
1832
+ if (state.lastSeenAtMs < expiredBefore) {
1833
+ this.circuitBreakerRuns.delete(key);
1834
+ }
1835
+ }
1836
+ }
1229
1837
  async request(path, payload, options) {
1230
1838
  const url = new URL(path, this.baseUrl).toString();
1231
1839
  const body = payload !== void 0 ? JSON.stringify(payload) : void 0;
@@ -1413,7 +2021,7 @@ var UsageTapClient = class {
1413
2021
  ...this.defaultHeaders,
1414
2022
  [SDK_HEADER]: `js/${SDK_VERSION}`,
1415
2023
  "content-type": "application/json",
1416
- accept: CANONICAL_MEDIA_TYPE
2024
+ accept: CANONICAL_MEDIA_TYPE2
1417
2025
  };
1418
2026
  if (!HAS_WINDOW) {
1419
2027
  headers["user-agent"] = `${USER_AGENT}/${SDK_VERSION}`;
@@ -1614,17 +2222,18 @@ var OpenAIPromptCompressionStats = class {
1614
2222
  }
1615
2223
  };
1616
2224
  function createOpenAIAdapter(init) {
1617
- const { client, usageTap } = init;
2225
+ const { client, usageTap, provider = "openai" } = init;
1618
2226
  return {
1619
2227
  async invoke(params) {
1620
2228
  const result = await usageTap.withUsage(
1621
2229
  params.begin,
1622
2230
  async (ctx) => {
2231
+ ctx.setUsage({ providerUsed: provider });
1623
2232
  const response = await params.call(client, {
1624
2233
  hints: ctx.begin.data.vendorHints,
1625
2234
  begin: ctx.begin
1626
2235
  });
1627
- tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx);
2236
+ tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx, provider);
1628
2237
  return {
1629
2238
  data: response,
1630
2239
  begin: ctx.begin
@@ -1638,6 +2247,7 @@ function createOpenAIAdapter(init) {
1638
2247
  const result = await usageTap.withUsage(
1639
2248
  params.begin,
1640
2249
  async (ctx) => {
2250
+ ctx.setUsage({ providerUsed: provider });
1641
2251
  const { stream, onComplete } = await params.call(client, {
1642
2252
  hints: ctx.begin.data.vendorHints,
1643
2253
  begin: ctx.begin
@@ -1763,6 +2373,181 @@ async function pipeToResponse(stream, res, options = {}) {
1763
2373
  }
1764
2374
  }
1765
2375
  var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
2376
+ function withSampling(client, options = {}) {
2377
+ if (!client || !options) {
2378
+ throw new UsageTapError(
2379
+ "USAGETAP_BAD_REQUEST",
2380
+ "withSampling requires an OpenAI-compatible client and sampling options"
2381
+ );
2382
+ }
2383
+ const { apiKey, usageTapClient, provider = "openai", ...policy } = options;
2384
+ const localPolicy = typeof policy.rate === "number" ? policy : void 0;
2385
+ const usageTap = usageTapClient ?? new UsageTapClient({ apiKey, sampling: localPolicy });
2386
+ const wrapCreate = (create) => async (params, requestOptions) => {
2387
+ const { usageTap: callContextRaw, ...providerOptions } = requestOptions ?? {};
2388
+ const callContext = isObjectRecord(callContextRaw) ? callContextRaw : {};
2389
+ const streaming = params.stream === true;
2390
+ const decision = streaming ? Promise.resolve(false) : usageTap.shouldSampleAsync({
2391
+ customerId: readString(callContext.customerId),
2392
+ feature: readString(callContext.feature),
2393
+ input: params
2394
+ }, localPolicy);
2395
+ const startedAt = Date.now();
2396
+ try {
2397
+ const response = await create(
2398
+ params,
2399
+ Object.keys(providerOptions).length ? providerOptions : void 0
2400
+ );
2401
+ const selected = await decision;
2402
+ if (selected) {
2403
+ const record = isObjectRecord(response) ? response : {};
2404
+ await usageTap.captureSample({
2405
+ customerId: readString(callContext.customerId),
2406
+ feature: readString(callContext.feature),
2407
+ environment: readString(callContext.environment),
2408
+ tags: readStringArray(callContext.tags),
2409
+ provider,
2410
+ model: readString(record.model) ?? readString(params.model),
2411
+ input: params,
2412
+ output: response,
2413
+ usage: record.usage,
2414
+ latencyMs: Date.now() - startedAt
2415
+ }).catch(() => void 0);
2416
+ }
2417
+ return response;
2418
+ } catch (error) {
2419
+ const selected = await decision;
2420
+ if (selected) {
2421
+ await usageTap.captureSample({
2422
+ customerId: readString(callContext.customerId),
2423
+ feature: readString(callContext.feature),
2424
+ environment: readString(callContext.environment),
2425
+ tags: readStringArray(callContext.tags),
2426
+ provider,
2427
+ model: readString(params.model),
2428
+ input: params,
2429
+ latencyMs: Date.now() - startedAt,
2430
+ error: serializeSamplingError(error)
2431
+ }).catch(() => void 0);
2432
+ }
2433
+ throw error;
2434
+ }
2435
+ };
2436
+ const chat = client.chat?.completions ? new Proxy(client.chat, {
2437
+ get(target, prop, receiver) {
2438
+ if (prop !== "completions") return safeReflectGet(target, prop, receiver);
2439
+ const completions = target.completions;
2440
+ return new Proxy(completions, {
2441
+ get(completionTarget, completionProp, completionReceiver) {
2442
+ if (completionProp === "create") {
2443
+ return wrapCreate(
2444
+ completionTarget.create.bind(completionTarget)
2445
+ );
2446
+ }
2447
+ return safeReflectGet(
2448
+ completionTarget,
2449
+ completionProp,
2450
+ completionReceiver
2451
+ );
2452
+ }
2453
+ });
2454
+ }
2455
+ }) : void 0;
2456
+ const responses = typeof client.responses !== "undefined" && client.responses ? new Proxy(client.responses, {
2457
+ get(target, prop, receiver) {
2458
+ if (prop === "create") {
2459
+ const create = Reflect.get(target, prop, receiver);
2460
+ return wrapCreate(create.bind(target));
2461
+ }
2462
+ return safeReflectGet(target, prop, receiver);
2463
+ }
2464
+ }) : void 0;
2465
+ return new Proxy(client, {
2466
+ get(target, prop, receiver) {
2467
+ if (prop === "chat" && chat) return chat;
2468
+ if (prop === "responses" && responses) return responses;
2469
+ if (prop === "unwrap") return () => target;
2470
+ return safeReflectGet(target, prop, receiver);
2471
+ }
2472
+ });
2473
+ }
2474
+ function safeReflectGet(target, prop, receiver) {
2475
+ return Reflect.get(target, prop, receiver);
2476
+ }
2477
+ function readStringArray(value) {
2478
+ if (!Array.isArray(value)) return void 0;
2479
+ const strings = value.filter((item) => typeof item === "string");
2480
+ return strings.length ? strings : void 0;
2481
+ }
2482
+ function readString(value) {
2483
+ return typeof value === "string" && value.trim() ? value : void 0;
2484
+ }
2485
+ function serializeSamplingError(error) {
2486
+ if (error instanceof Error) {
2487
+ return { name: error.name, message: error.message };
2488
+ }
2489
+ return { message: String(error) };
2490
+ }
2491
+ function normalizeMeteredOpenAISampling(options) {
2492
+ if (!options) return void 0;
2493
+ if (options === true) return { provider: "openai" };
2494
+ const { provider = "openai", ...policyFields } = options;
2495
+ return {
2496
+ provider,
2497
+ policy: typeof policyFields.rate === "number" ? policyFields : void 0
2498
+ };
2499
+ }
2500
+ function startMeteredOpenAISampleDecision({
2501
+ usageTap,
2502
+ sampling,
2503
+ beginRequest,
2504
+ input
2505
+ }) {
2506
+ if (!sampling) return Promise.resolve(false);
2507
+ return usageTap.shouldSampleAsync(
2508
+ {
2509
+ customerId: beginRequest.customerId,
2510
+ feature: beginRequest.feature,
2511
+ input
2512
+ },
2513
+ sampling.policy
2514
+ );
2515
+ }
2516
+ async function captureMeteredOpenAISample({
2517
+ usageTap,
2518
+ sampling,
2519
+ decision,
2520
+ ctx,
2521
+ beginRequest,
2522
+ input,
2523
+ response,
2524
+ error,
2525
+ startedAt
2526
+ }) {
2527
+ if (!sampling) return;
2528
+ let selected = false;
2529
+ try {
2530
+ selected = await decision;
2531
+ } catch {
2532
+ return;
2533
+ }
2534
+ if (!selected) return;
2535
+ const record = isObjectRecord(response) ? response : {};
2536
+ await usageTap.captureSample({
2537
+ sampleId: ctx.begin.data.callId,
2538
+ callId: ctx.begin.data.callId,
2539
+ customerId: beginRequest.customerId,
2540
+ feature: beginRequest.feature,
2541
+ tags: beginRequest.tags,
2542
+ provider: sampling.provider,
2543
+ model: readString(record.model) ?? readString(input.model),
2544
+ input,
2545
+ ...response === void 0 ? {} : { output: response },
2546
+ usage: record.usage,
2547
+ latencyMs: Date.now() - startedAt,
2548
+ ...error === void 0 ? {} : { error: serializeSamplingError(error) }
2549
+ }).catch(() => void 0);
2550
+ }
1766
2551
  function withCompression(client, options = {}) {
1767
2552
  if (!client) {
1768
2553
  throw new UsageTapError(
@@ -1773,11 +2558,13 @@ function withCompression(client, options = {}) {
1773
2558
  const {
1774
2559
  apiKey,
1775
2560
  usageTapClient,
2561
+ sampling,
1776
2562
  ...compressionOverrides
1777
2563
  } = options;
1778
2564
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
1779
2565
  const compression = {
1780
2566
  provider: "usagetap",
2567
+ roles: { user: { enabled: true, aggressiveness: 0.2 } },
1781
2568
  minContextTokens: DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS,
1782
2569
  ...compressionOverrides
1783
2570
  };
@@ -1787,7 +2574,7 @@ function withCompression(client, options = {}) {
1787
2574
  usageTap,
1788
2575
  compression
1789
2576
  ) : void 0;
1790
- return new Proxy(client, {
2577
+ const compressed = new Proxy(client, {
1791
2578
  get(target, prop, receiver) {
1792
2579
  if (prop === "chat" && proxiedChat) {
1793
2580
  return proxiedChat;
@@ -1801,6 +2588,11 @@ function withCompression(client, options = {}) {
1801
2588
  return Reflect.get(target, prop, receiver);
1802
2589
  }
1803
2590
  });
2591
+ return sampling ? withSampling(compressed, {
2592
+ ...sampling === true ? {} : sampling,
2593
+ apiKey,
2594
+ usageTapClient
2595
+ }) : compressed;
1804
2596
  }
1805
2597
  function withMetering(client, customer) {
1806
2598
  const config = typeof customer === "string" ? { customerId: customer } : customer;
@@ -1815,6 +2607,8 @@ function withMetering(client, customer) {
1815
2607
  usageTapClient,
1816
2608
  applyVendorHints,
1817
2609
  promptCompression,
2610
+ sampling,
2611
+ provider,
1818
2612
  ...defaultContext
1819
2613
  } = config;
1820
2614
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
@@ -1822,7 +2616,9 @@ function withMetering(client, customer) {
1822
2616
  return wrapOpenAI(client, usageTap, {
1823
2617
  defaultContext,
1824
2618
  applyVendorHints,
1825
- promptCompression: normalizedCompression
2619
+ promptCompression: normalizedCompression,
2620
+ sampling,
2621
+ provider
1826
2622
  });
1827
2623
  }
1828
2624
  function wrapOpenAI(client, usageTap, options = {}) {
@@ -1832,6 +2628,8 @@ function wrapOpenAI(client, usageTap, options = {}) {
1832
2628
  const defaultContext = options.defaultContext;
1833
2629
  const applyVendorHints = options.applyVendorHints !== false;
1834
2630
  const defaultPromptCompression = normalizePromptCompressionOptions(options.promptCompression);
2631
+ const defaultSampling = normalizeMeteredOpenAISampling(options.sampling);
2632
+ const provider = options.provider ?? "openai";
1835
2633
  const promptCompressionStats = new OpenAIPromptCompressionStats();
1836
2634
  const proxiedChat = client.chat ? createChatProxy(
1837
2635
  client.chat,
@@ -1839,7 +2637,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
1839
2637
  defaultContext,
1840
2638
  applyVendorHints,
1841
2639
  defaultPromptCompression,
1842
- promptCompressionStats
2640
+ promptCompressionStats,
2641
+ defaultSampling,
2642
+ provider
1843
2643
  ) : void 0;
1844
2644
  const proxiedResponses = typeof client.responses !== "undefined" ? createResponsesProxy(
1845
2645
  client.responses,
@@ -1847,7 +2647,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
1847
2647
  defaultContext,
1848
2648
  applyVendorHints,
1849
2649
  defaultPromptCompression,
1850
- promptCompressionStats
2650
+ promptCompressionStats,
2651
+ defaultSampling,
2652
+ provider
1851
2653
  ) : void 0;
1852
2654
  const handler = {
1853
2655
  get(target, prop, receiver) {
@@ -1915,14 +2717,16 @@ function createCompressionOnlyResponsesProxy(resource, usageTap, compression) {
1915
2717
  }
1916
2718
  });
1917
2719
  }
1918
- function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2720
+ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1919
2721
  const completions = createChatCompletionsProxy(
1920
2722
  resource.completions,
1921
2723
  usageTap,
1922
2724
  defaultContext,
1923
2725
  applyVendorHints,
1924
2726
  defaultPromptCompression,
1925
- promptCompressionStats
2727
+ promptCompressionStats,
2728
+ defaultSampling,
2729
+ provider
1926
2730
  );
1927
2731
  const handler = {
1928
2732
  get(target, prop, receiver) {
@@ -1934,7 +2738,7 @@ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, d
1934
2738
  };
1935
2739
  return new Proxy(resource, handler);
1936
2740
  }
1937
- function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2741
+ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1938
2742
  if (!resource || typeof resource !== "object") {
1939
2743
  return void 0;
1940
2744
  }
@@ -1949,9 +2753,20 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1949
2753
  withUsage,
1950
2754
  promptCompression
1951
2755
  } = splitUsageOptions(options);
1952
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2756
+ const beginRequest = withRuntimeCompressionContext(
2757
+ resolveBeginRequest(defaultContext, usageContext),
2758
+ "openai",
2759
+ String(params.model)
2760
+ );
1953
2761
  const wantsStream = isStreamingRequest(params);
1954
2762
  return usageTap.withUsage(beginRequest, async (ctx) => {
2763
+ const sampleStartedAt = Date.now();
2764
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2765
+ usageTap,
2766
+ sampling: defaultSampling,
2767
+ beginRequest,
2768
+ input: params
2769
+ });
1955
2770
  const hintedParams = applyVendorHints ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints) : params;
1956
2771
  const finalParams = await compressResponsesParamsForCall({
1957
2772
  params: hintedParams,
@@ -1963,13 +2778,14 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1963
2778
  withUsage,
1964
2779
  operation: "responses.create"
1965
2780
  });
2781
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
1966
2782
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
1967
2783
  if (wantsStream) {
1968
2784
  const apiPromise2 = originalCreate(finalParams, request);
1969
2785
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
1970
2786
  ensureAsyncIterable(rawStream, "responses.create");
1971
2787
  const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
1972
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2788
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
1973
2789
  if (usage) {
1974
2790
  ctx.setUsage(usage);
1975
2791
  }
@@ -1979,9 +2795,31 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1979
2795
  return wrappedPromise2;
1980
2796
  }
1981
2797
  const apiPromise = originalCreate(finalParams, request);
1982
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
1983
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2798
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2799
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2800
+ await captureMeteredOpenAISample({
2801
+ usageTap,
2802
+ sampling: defaultSampling,
2803
+ decision: sampleDecision,
2804
+ ctx,
2805
+ beginRequest,
2806
+ input: params,
2807
+ response,
2808
+ startedAt: sampleStartedAt
2809
+ });
1984
2810
  return response;
2811
+ }, async (error) => {
2812
+ await captureMeteredOpenAISample({
2813
+ usageTap,
2814
+ sampling: defaultSampling,
2815
+ decision: sampleDecision,
2816
+ ctx,
2817
+ beginRequest,
2818
+ input: params,
2819
+ error,
2820
+ startedAt: sampleStartedAt
2821
+ });
2822
+ throw error;
1985
2823
  });
1986
2824
  return wrappedPromise;
1987
2825
  }, withUsage);
@@ -1996,7 +2834,7 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
1996
2834
  };
1997
2835
  return new Proxy(resource, handler);
1998
2836
  }
1999
- function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2837
+ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
2000
2838
  const originalCreate = resource.create.bind(resource);
2001
2839
  const streamCandidate = resource.stream;
2002
2840
  const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(resource) : void 0;
@@ -2007,9 +2845,20 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2007
2845
  withUsage,
2008
2846
  promptCompression
2009
2847
  } = splitUsageOptions(options);
2010
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2848
+ const beginRequest = withRuntimeCompressionContext(
2849
+ resolveBeginRequest(defaultContext, usageContext),
2850
+ "openai",
2851
+ String(params.model)
2852
+ );
2011
2853
  const wantsStream = isStreamingRequest(params);
2012
2854
  return usageTap.withUsage(beginRequest, async (ctx) => {
2855
+ const sampleStartedAt = Date.now();
2856
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2857
+ usageTap,
2858
+ sampling: defaultSampling,
2859
+ beginRequest,
2860
+ input: params
2861
+ });
2013
2862
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2014
2863
  const finalParams = await compressChatParamsForCall({
2015
2864
  params: hintedParams,
@@ -2021,13 +2870,14 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2021
2870
  withUsage,
2022
2871
  operation: "chat.completions.create"
2023
2872
  });
2873
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2024
2874
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2025
2875
  if (wantsStream) {
2026
2876
  const apiPromise2 = originalCreate(finalParams, request);
2027
2877
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
2028
2878
  ensureAsyncIterable(rawStream, "chat.completions.create");
2029
2879
  const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
2030
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2880
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2031
2881
  if (usage) {
2032
2882
  ctx.setUsage(usage);
2033
2883
  }
@@ -2037,9 +2887,31 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2037
2887
  return wrappedPromise2;
2038
2888
  }
2039
2889
  const apiPromise = originalCreate(finalParams, request);
2040
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
2041
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2890
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2891
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2892
+ await captureMeteredOpenAISample({
2893
+ usageTap,
2894
+ sampling: defaultSampling,
2895
+ decision: sampleDecision,
2896
+ ctx,
2897
+ beginRequest,
2898
+ input: params,
2899
+ response,
2900
+ startedAt: sampleStartedAt
2901
+ });
2042
2902
  return response;
2903
+ }, async (error) => {
2904
+ await captureMeteredOpenAISample({
2905
+ usageTap,
2906
+ sampling: defaultSampling,
2907
+ decision: sampleDecision,
2908
+ ctx,
2909
+ beginRequest,
2910
+ input: params,
2911
+ error,
2912
+ startedAt: sampleStartedAt
2913
+ });
2914
+ throw error;
2043
2915
  });
2044
2916
  return wrappedPromise;
2045
2917
  }, withUsage);
@@ -2051,7 +2923,11 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2051
2923
  withUsage,
2052
2924
  promptCompression
2053
2925
  } = splitUsageOptions(options);
2054
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2926
+ const beginRequest = withRuntimeCompressionContext(
2927
+ resolveBeginRequest(defaultContext, usageContext),
2928
+ "openai",
2929
+ String(params.model)
2930
+ );
2055
2931
  return usageTap.withUsage(beginRequest, async (ctx) => {
2056
2932
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2057
2933
  const finalParams = await compressChatParamsForCall({
@@ -2064,12 +2940,17 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2064
2940
  withUsage,
2065
2941
  operation: "chat.completions.stream"
2066
2942
  });
2943
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2067
2944
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2068
2945
  const apiPromise = originalStream(finalParams, request);
2069
2946
  const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
2070
2947
  ensureAsyncIterable(rawStream, "chat.completions.stream");
2071
2948
  const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
2072
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2949
+ const usage = await extractUsageFromStream(
2950
+ rawStream,
2951
+ ctx.begin.data.vendorHints,
2952
+ provider
2953
+ );
2073
2954
  if (usage) {
2074
2955
  ctx.setUsage(usage);
2075
2956
  }
@@ -2093,19 +2974,70 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2093
2974
  return new Proxy(resource, handler);
2094
2975
  }
2095
2976
  async function compressChatParamsForCall(args) {
2096
- const compression = resolveEffectivePromptCompressionOptions(
2977
+ let compression = resolveEffectivePromptCompressionOptions(
2097
2978
  args.defaultPromptCompression,
2098
2979
  args.callPromptCompression
2099
2980
  );
2981
+ const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
2982
+ const policyDriven = !compression && Boolean(runtimePolicy);
2983
+ if (policyDriven && runtimePolicy) {
2984
+ if (!runtimePolicy.selected) {
2985
+ args.ctx.setUsage({
2986
+ runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
2987
+ });
2988
+ return args.params;
2989
+ }
2990
+ compression = openAIPolicyCompressionOptions(runtimePolicy);
2991
+ if (runtimePolicy.mode === "SHADOW") {
2992
+ const startedAt2 = Date.now();
2993
+ void compressChatParams(
2994
+ args.params,
2995
+ args.usageTap,
2996
+ compression,
2997
+ args.withUsage?.signal
2998
+ ).then((outcome2) => {
2999
+ args.ctx.setUsage({
3000
+ runtimeCompression: openAIRuntimeMeasurement({
3001
+ policy: runtimePolicy,
3002
+ telemetry: buildPromptCompressionTelemetry(outcome2.segments),
3003
+ latencyMs: Date.now() - startedAt2,
3004
+ shadow: true,
3005
+ originalPayload: args.params,
3006
+ resultPayload: outcome2.params
3007
+ })
3008
+ });
3009
+ }).catch(() => {
3010
+ args.ctx.setUsage({
3011
+ runtimeCompression: failedRuntimeMeasurement(
3012
+ runtimePolicy,
3013
+ Date.now() - startedAt2
3014
+ )
3015
+ });
3016
+ });
3017
+ return args.params;
3018
+ }
3019
+ }
2100
3020
  if (!compression) {
2101
3021
  return args.params;
2102
3022
  }
3023
+ const startedAt = Date.now();
2103
3024
  const outcome = await compressChatParams(
2104
3025
  args.params,
2105
3026
  args.usageTap,
2106
3027
  compression,
2107
3028
  args.withUsage?.signal
2108
3029
  );
3030
+ if (policyDriven && runtimePolicy) {
3031
+ const runtimeMeasurement = openAIRuntimeMeasurement({
3032
+ policy: runtimePolicy,
3033
+ telemetry: buildPromptCompressionTelemetry(outcome.segments),
3034
+ latencyMs: Date.now() - startedAt,
3035
+ originalPayload: args.params,
3036
+ resultPayload: outcome.params
3037
+ });
3038
+ args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
3039
+ if (runtimeMeasurement.decision !== "compressed") return args.params;
3040
+ }
2109
3041
  await recordCompressionOutcome({
2110
3042
  outcome,
2111
3043
  compression,
@@ -2118,19 +3050,70 @@ async function compressChatParamsForCall(args) {
2118
3050
  return outcome.params;
2119
3051
  }
2120
3052
  async function compressResponsesParamsForCall(args) {
2121
- const compression = resolveEffectivePromptCompressionOptions(
3053
+ let compression = resolveEffectivePromptCompressionOptions(
2122
3054
  args.defaultPromptCompression,
2123
3055
  args.callPromptCompression
2124
3056
  );
3057
+ const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
3058
+ const policyDriven = !compression && Boolean(runtimePolicy);
3059
+ if (policyDriven && runtimePolicy) {
3060
+ if (!runtimePolicy.selected) {
3061
+ args.ctx.setUsage({
3062
+ runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
3063
+ });
3064
+ return args.params;
3065
+ }
3066
+ compression = openAIPolicyCompressionOptions(runtimePolicy);
3067
+ if (runtimePolicy.mode === "SHADOW") {
3068
+ const startedAt2 = Date.now();
3069
+ void compressResponsesParams(
3070
+ args.params,
3071
+ args.usageTap,
3072
+ compression,
3073
+ args.withUsage?.signal
3074
+ ).then((outcome2) => {
3075
+ args.ctx.setUsage({
3076
+ runtimeCompression: openAIRuntimeMeasurement({
3077
+ policy: runtimePolicy,
3078
+ telemetry: buildPromptCompressionTelemetry(outcome2.segments),
3079
+ latencyMs: Date.now() - startedAt2,
3080
+ shadow: true,
3081
+ originalPayload: args.params,
3082
+ resultPayload: outcome2.params
3083
+ })
3084
+ });
3085
+ }).catch(() => {
3086
+ args.ctx.setUsage({
3087
+ runtimeCompression: failedRuntimeMeasurement(
3088
+ runtimePolicy,
3089
+ Date.now() - startedAt2
3090
+ )
3091
+ });
3092
+ });
3093
+ return args.params;
3094
+ }
3095
+ }
2125
3096
  if (!compression) {
2126
3097
  return args.params;
2127
3098
  }
3099
+ const startedAt = Date.now();
2128
3100
  const outcome = await compressResponsesParams(
2129
3101
  args.params,
2130
3102
  args.usageTap,
2131
3103
  compression,
2132
3104
  args.withUsage?.signal
2133
3105
  );
3106
+ if (policyDriven && runtimePolicy) {
3107
+ const runtimeMeasurement = openAIRuntimeMeasurement({
3108
+ policy: runtimePolicy,
3109
+ telemetry: buildPromptCompressionTelemetry(outcome.segments),
3110
+ latencyMs: Date.now() - startedAt,
3111
+ originalPayload: args.params,
3112
+ resultPayload: outcome.params
3113
+ });
3114
+ args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
3115
+ if (runtimeMeasurement.decision !== "compressed") return args.params;
3116
+ }
2134
3117
  await recordCompressionOutcome({
2135
3118
  outcome,
2136
3119
  compression,
@@ -2142,6 +3125,79 @@ async function compressResponsesParamsForCall(args) {
2142
3125
  });
2143
3126
  return outcome.params;
2144
3127
  }
3128
+ function openAIPolicyCompressionOptions(policy) {
3129
+ const roles = Object.fromEntries(
3130
+ ["system", "user", "tool", "assistant"].filter((role) => policy.snapshot.scope.messageRoles.includes(role)).map((role) => [role, true])
3131
+ );
3132
+ return {
3133
+ provider: "usagetap",
3134
+ roles,
3135
+ minContextTokens: policy.snapshot.scope.minimumTokens,
3136
+ failOpen: true
3137
+ };
3138
+ }
3139
+ function bypassedRuntimeMeasurement(policy) {
3140
+ return {
3141
+ policyId: policy.policyId,
3142
+ policyVersionId: policy.policyVersionId,
3143
+ policyVersion: policy.version,
3144
+ rolloutMode: policy.mode,
3145
+ decision: "bypassed",
3146
+ reasonCode: "not_in_rollout_sample",
3147
+ methodsAttempted: [],
3148
+ methodsApplied: [],
3149
+ originalTokens: 0,
3150
+ resultTokens: 0,
3151
+ savedTokens: 0,
3152
+ reductionPercentage: 0,
3153
+ compressionLatencyMs: 0,
3154
+ compressionComputeCostUsd: 0,
3155
+ financialsEstimated: true
3156
+ };
3157
+ }
3158
+ function failedRuntimeMeasurement(policy, latencyMs) {
3159
+ return {
3160
+ ...bypassedRuntimeMeasurement(policy),
3161
+ decision: "fallback",
3162
+ reasonCode: "transformation_failed",
3163
+ methodsAttempted: ["deterministic"],
3164
+ compressionLatencyMs: latencyMs
3165
+ };
3166
+ }
3167
+ function openAIRuntimeMeasurement(input) {
3168
+ const telemetry = input.telemetry;
3169
+ const originalTokens = telemetry?.originalTokens ?? estimatePromptTokens(JSON.stringify(input.originalPayload));
3170
+ const resultTokens = telemetry?.compressedTokens ?? originalTokens;
3171
+ const savedTokens = Math.max(0, telemetry?.savedTokens ?? 0);
3172
+ const reductionPercentage = originalTokens > 0 ? savedTokens / originalTokens * 100 : 0;
3173
+ const belowMinimumSize = originalTokens < input.policy.snapshot.scope.minimumTokens;
3174
+ const latencyExceeded = input.latencyMs > input.policy.snapshot.gates.maximumLatencyMs;
3175
+ const belowSavings = savedTokens < input.policy.snapshot.gates.minimumTokensRemoved || reductionPercentage < input.policy.snapshot.gates.minimumReductionPercentage;
3176
+ const accepted = !belowMinimumSize && !latencyExceeded && !belowSavings;
3177
+ const deepText = input.policy.snapshot.methods.deepText.enabled && !input.policy.snapshot.rollout.deterministicOnly && (input.policy.mode !== "CANARY" || input.policy.snapshot.rollout.deepTextCanaryAllowed);
3178
+ const reasonCode = belowMinimumSize ? "below_minimum_size" : latencyExceeded ? "latency_budget_exceeded" : belowSavings ? "below_minimum_savings" : void 0;
3179
+ const measurement = {
3180
+ policyId: input.policy.policyId,
3181
+ policyVersionId: input.policy.policyVersionId,
3182
+ policyVersion: input.policy.version,
3183
+ rolloutMode: input.policy.mode,
3184
+ decision: accepted ? input.shadow ? "bypassed" : "compressed" : latencyExceeded ? "fallback" : "skipped",
3185
+ ...reasonCode ? { reasonCode } : {},
3186
+ methodsAttempted: [
3187
+ "deterministic",
3188
+ ...deepText ? ["deep_text"] : []
3189
+ ],
3190
+ methodsApplied: accepted ? ["deterministic"] : [],
3191
+ originalTokens,
3192
+ resultTokens: accepted ? resultTokens : originalTokens,
3193
+ savedTokens: accepted ? savedTokens : 0,
3194
+ reductionPercentage: accepted ? reductionPercentage : 0,
3195
+ compressionLatencyMs: input.latencyMs,
3196
+ compressionComputeCostUsd: 0,
3197
+ financialsEstimated: true
3198
+ };
3199
+ return measurement;
3200
+ }
2145
3201
  async function recordCompressionOutcome(args) {
2146
3202
  const telemetry = buildPromptCompressionTelemetry(args.outcome.segments);
2147
3203
  if (!telemetry) {
@@ -2195,6 +3251,10 @@ async function compressChatParams(params, usageTap, compression, signal) {
2195
3251
  const result = await usageTap.compressPromptMessages(source, {
2196
3252
  provider: "usagetap",
2197
3253
  failOpen: compression.failOpen,
3254
+ mode: compression.mode,
3255
+ latencyBudgetMs: compression.latencyBudgetMs,
3256
+ compactEmptyUserMessages: compression.compactEmptyUserMessages,
3257
+ compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
2198
3258
  aggressiveness: resolveMessageEndpointAggressiveness(compression),
2199
3259
  signal
2200
3260
  });
@@ -2660,23 +3720,53 @@ function resolveBeginRequest(defaults, override) {
2660
3720
  }
2661
3721
  const tags = mergeTags(base.tags, current.tags);
2662
3722
  const begin = { customerId };
3723
+ const runtimeCompressionContext = current.runtimeCompressionContext ?? base.runtimeCompressionContext;
3724
+ if (runtimeCompressionContext) {
3725
+ begin.runtimeCompressionContext = runtimeCompressionContext;
3726
+ }
2663
3727
  const requested = current.requested ?? base.requested;
2664
3728
  if (requested) begin.requested = requested;
2665
3729
  const feature = current.feature ?? base.feature;
2666
3730
  if (feature) begin.feature = feature;
3731
+ const runId = current.runId ?? base.runId;
3732
+ if (runId) begin.runId = runId;
2667
3733
  const idempotency = current.idempotency ?? base.idempotency;
2668
3734
  if (idempotency) begin.idempotency = idempotency;
2669
3735
  const customerName = current.customerName ?? base.customerName;
2670
3736
  if (customerName) begin.customerName = customerName;
2671
3737
  const customerEmail = current.customerEmail ?? base.customerEmail;
2672
3738
  if (customerEmail) begin.customerEmail = customerEmail;
3739
+ const customerUserId = current.customerUserId ?? base.customerUserId;
3740
+ if (customerUserId) begin.customerUserId = customerUserId;
3741
+ const customerUserName = current.customerUserName ?? base.customerUserName;
3742
+ if (customerUserName) begin.customerUserName = customerUserName;
3743
+ const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
3744
+ if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
3745
+ const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
3746
+ if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
3747
+ const holdUsd = current.holdUsd ?? base.holdUsd;
3748
+ if (typeof holdUsd === "number") begin.holdUsd = holdUsd;
3749
+ const batch = current.batch ?? base.batch;
3750
+ if (typeof batch === "boolean") begin.batch = batch;
3751
+ const pricingMode = current.pricingMode ?? base.pricingMode;
3752
+ if (pricingMode) begin.pricingMode = pricingMode;
2673
3753
  if (tags?.length) {
2674
3754
  begin.tags = tags;
2675
3755
  }
2676
3756
  return begin;
2677
3757
  }
2678
- function transformApiPromise(apiPromise, onResolve) {
2679
- const resolvedPromise = Promise.resolve(apiPromise).then(onResolve);
3758
+ function withRuntimeCompressionContext(begin, provider, model) {
3759
+ return {
3760
+ ...begin,
3761
+ runtimeCompressionContext: {
3762
+ ...begin.runtimeCompressionContext ?? {},
3763
+ provider,
3764
+ model
3765
+ }
3766
+ };
3767
+ }
3768
+ function transformApiPromise(apiPromise, onResolve, onReject) {
3769
+ const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
2680
3770
  if (isObjectRecord(apiPromise)) {
2681
3771
  const proto = Object.getPrototypeOf(apiPromise);
2682
3772
  if (proto) {
@@ -2811,12 +3901,12 @@ function applyResponsesVendorHints(params, hints) {
2811
3901
  }
2812
3902
  return next;
2813
3903
  }
2814
- async function extractUsageFromStream(stream, hints) {
3904
+ async function extractUsageFromStream(stream, hints, provider = "openai") {
2815
3905
  const finalPayload = await resolveStreamFinalPayload(stream);
2816
3906
  if (!finalPayload) {
2817
3907
  return void 0;
2818
3908
  }
2819
- return inferUsageFromResponse(finalPayload, hints);
3909
+ return inferUsageFromResponse(finalPayload, hints, provider);
2820
3910
  }
2821
3911
  async function resolveStreamFinalPayload(stream) {
2822
3912
  if (!stream || typeof stream !== "object") {
@@ -2889,14 +3979,14 @@ function setHeaderIfPossible(res, key, value) {
2889
3979
  res.setHeader(key, value);
2890
3980
  }
2891
3981
  }
2892
- function tryInferUsage(response, hints, extractor, ctx) {
3982
+ function tryInferUsage(response, hints, extractor, ctx, provider = "openai") {
2893
3983
  const explicit = extractor?.(response);
2894
- const inferred = explicit ?? inferUsageFromResponse(response, hints);
3984
+ const inferred = explicit ?? inferUsageFromResponse(response, hints, provider);
2895
3985
  if (inferred) {
2896
3986
  ctx.setUsage(inferred);
2897
3987
  }
2898
3988
  }
2899
- function inferUsageFromResponse(response, hints) {
3989
+ function inferUsageFromResponse(response, hints, provider = "openai") {
2900
3990
  if (!response || typeof response !== "object") {
2901
3991
  return void 0;
2902
3992
  }
@@ -2904,12 +3994,41 @@ function inferUsageFromResponse(response, hints) {
2904
3994
  if (!candidate.usage) {
2905
3995
  return void 0;
2906
3996
  }
2907
- const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
3997
+ const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
3998
+ const responseEffort = normalizeExecutionReasoningEffort(
3999
+ candidate.reasoning?.effort
4000
+ );
2908
4001
  return {
4002
+ providerUsed: provider,
2909
4003
  modelUsed: candidate.model ?? hints?.preferredModel,
2910
- inputTokens: candidate.usage.prompt_tokens,
2911
- responseTokens: candidate.usage.completion_tokens,
2912
- cachedInputTokens
4004
+ inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
4005
+ responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
4006
+ cachedInputTokens,
4007
+ reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
4008
+ ...responseEffort ? {
4009
+ reasoningEffort: responseEffort,
4010
+ reasoningEffortSource: "provider_response"
4011
+ } : {},
4012
+ ...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {}
4013
+ };
4014
+ }
4015
+ function normalizeExecutionReasoningEffort(value) {
4016
+ return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
4017
+ }
4018
+ function openAIRequestExecutionMetadata(params, provider) {
4019
+ const record = params && typeof params === "object" ? params : {};
4020
+ const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
4021
+ const effort = normalizeExecutionReasoningEffort(
4022
+ record.reasoning_effort ?? reasoning?.effort ?? record.thinking_level
4023
+ );
4024
+ const mode = typeof reasoning?.type === "string" ? reasoning.type : typeof reasoning?.mode === "string" ? reasoning.mode : void 0;
4025
+ const rawBudget = reasoning?.budget_tokens ?? record.thinking_budget ?? record.thinking_budget_tokens;
4026
+ const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
4027
+ return {
4028
+ providerUsed: provider,
4029
+ ...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
4030
+ ...mode ? { reasoningMode: mode } : {},
4031
+ ...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
2913
4032
  };
2914
4033
  }
2915
4034
  function wrapStreamForUsageTap(source, finalize, ctx) {
@@ -3011,9 +4130,24 @@ function isIteratorResult(value) {
3011
4130
 
3012
4131
  // src/adapters/openrouter.ts
3013
4132
  function createOpenRouterAdapter(init) {
3014
- return createOpenAIAdapter(init);
4133
+ return createOpenAIAdapter({ ...init, provider: "openrouter" });
4134
+ }
4135
+ function withMetering2(client, customer) {
4136
+ return withMetering(
4137
+ client,
4138
+ typeof customer === "string" ? { customerId: customer, provider: "openrouter" } : { ...customer, provider: "openrouter" }
4139
+ );
4140
+ }
4141
+ function wrapOpenAI2(client, usageTap, options = {}) {
4142
+ return wrapOpenAI(client, usageTap, {
4143
+ ...options,
4144
+ provider: "openrouter"
4145
+ });
4146
+ }
4147
+ function withSampling2(client, options = {}) {
4148
+ return withSampling(client, { ...options, provider: "openrouter" });
3015
4149
  }
3016
4150
 
3017
- export { createOpenRouterAdapter, withCompression, withMetering, wrapOpenAI };
4151
+ export { createOpenRouterAdapter, withCompression, withMetering2 as withMetering, withSampling2 as withSampling, wrapOpenAI2 as wrapOpenAI };
3018
4152
  //# sourceMappingURL=index.mjs.map
3019
4153
  //# sourceMappingURL=index.mjs.map