@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) {
@@ -1966,14 +2768,16 @@ function streamOpenAIRoute(usageTap, openai, options) {
1966
2768
  });
1967
2769
  };
1968
2770
  }
1969
- function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2771
+ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1970
2772
  const completions = createChatCompletionsProxy(
1971
2773
  resource.completions,
1972
2774
  usageTap,
1973
2775
  defaultContext,
1974
2776
  applyVendorHints,
1975
2777
  defaultPromptCompression,
1976
- promptCompressionStats
2778
+ promptCompressionStats,
2779
+ defaultSampling,
2780
+ provider
1977
2781
  );
1978
2782
  const handler = {
1979
2783
  get(target, prop, receiver) {
@@ -1985,7 +2789,7 @@ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, d
1985
2789
  };
1986
2790
  return new Proxy(resource, handler);
1987
2791
  }
1988
- function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2792
+ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
1989
2793
  if (!resource || typeof resource !== "object") {
1990
2794
  return void 0;
1991
2795
  }
@@ -2000,9 +2804,20 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2000
2804
  withUsage,
2001
2805
  promptCompression
2002
2806
  } = splitUsageOptions(options);
2003
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2807
+ const beginRequest = withRuntimeCompressionContext(
2808
+ resolveBeginRequest(defaultContext, usageContext),
2809
+ "openai",
2810
+ String(params.model)
2811
+ );
2004
2812
  const wantsStream = isStreamingRequest(params);
2005
2813
  return usageTap.withUsage(beginRequest, async (ctx) => {
2814
+ const sampleStartedAt = Date.now();
2815
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2816
+ usageTap,
2817
+ sampling: defaultSampling,
2818
+ beginRequest,
2819
+ input: params
2820
+ });
2006
2821
  const hintedParams = applyVendorHints ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints) : params;
2007
2822
  const finalParams = await compressResponsesParamsForCall({
2008
2823
  params: hintedParams,
@@ -2014,13 +2829,14 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2014
2829
  withUsage,
2015
2830
  operation: "responses.create"
2016
2831
  });
2832
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2017
2833
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2018
2834
  if (wantsStream) {
2019
2835
  const apiPromise2 = originalCreate(finalParams, request);
2020
2836
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
2021
2837
  ensureAsyncIterable(rawStream, "responses.create");
2022
2838
  const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
2023
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2839
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2024
2840
  if (usage) {
2025
2841
  ctx.setUsage(usage);
2026
2842
  }
@@ -2030,9 +2846,31 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2030
2846
  return wrappedPromise2;
2031
2847
  }
2032
2848
  const apiPromise = originalCreate(finalParams, request);
2033
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
2034
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2849
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2850
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2851
+ await captureMeteredOpenAISample({
2852
+ usageTap,
2853
+ sampling: defaultSampling,
2854
+ decision: sampleDecision,
2855
+ ctx,
2856
+ beginRequest,
2857
+ input: params,
2858
+ response,
2859
+ startedAt: sampleStartedAt
2860
+ });
2035
2861
  return response;
2862
+ }, async (error) => {
2863
+ await captureMeteredOpenAISample({
2864
+ usageTap,
2865
+ sampling: defaultSampling,
2866
+ decision: sampleDecision,
2867
+ ctx,
2868
+ beginRequest,
2869
+ input: params,
2870
+ error,
2871
+ startedAt: sampleStartedAt
2872
+ });
2873
+ throw error;
2036
2874
  });
2037
2875
  return wrappedPromise;
2038
2876
  }, withUsage);
@@ -2047,7 +2885,7 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
2047
2885
  };
2048
2886
  return new Proxy(resource, handler);
2049
2887
  }
2050
- function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2888
+ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
2051
2889
  const originalCreate = resource.create.bind(resource);
2052
2890
  const streamCandidate = resource.stream;
2053
2891
  const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(resource) : void 0;
@@ -2058,9 +2896,20 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2058
2896
  withUsage,
2059
2897
  promptCompression
2060
2898
  } = splitUsageOptions(options);
2061
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2899
+ const beginRequest = withRuntimeCompressionContext(
2900
+ resolveBeginRequest(defaultContext, usageContext),
2901
+ "openai",
2902
+ String(params.model)
2903
+ );
2062
2904
  const wantsStream = isStreamingRequest(params);
2063
2905
  return usageTap.withUsage(beginRequest, async (ctx) => {
2906
+ const sampleStartedAt = Date.now();
2907
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
2908
+ usageTap,
2909
+ sampling: defaultSampling,
2910
+ beginRequest,
2911
+ input: params
2912
+ });
2064
2913
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2065
2914
  const finalParams = await compressChatParamsForCall({
2066
2915
  params: hintedParams,
@@ -2072,13 +2921,14 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2072
2921
  withUsage,
2073
2922
  operation: "chat.completions.create"
2074
2923
  });
2924
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2075
2925
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2076
2926
  if (wantsStream) {
2077
2927
  const apiPromise2 = originalCreate(finalParams, request);
2078
2928
  const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
2079
2929
  ensureAsyncIterable(rawStream, "chat.completions.create");
2080
2930
  const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
2081
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
2931
+ const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
2082
2932
  if (usage) {
2083
2933
  ctx.setUsage(usage);
2084
2934
  }
@@ -2088,9 +2938,31 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2088
2938
  return wrappedPromise2;
2089
2939
  }
2090
2940
  const apiPromise = originalCreate(finalParams, request);
2091
- const wrappedPromise = transformApiPromise(apiPromise, (response) => {
2092
- tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
2941
+ const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
2942
+ tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
2943
+ await captureMeteredOpenAISample({
2944
+ usageTap,
2945
+ sampling: defaultSampling,
2946
+ decision: sampleDecision,
2947
+ ctx,
2948
+ beginRequest,
2949
+ input: params,
2950
+ response,
2951
+ startedAt: sampleStartedAt
2952
+ });
2093
2953
  return response;
2954
+ }, async (error) => {
2955
+ await captureMeteredOpenAISample({
2956
+ usageTap,
2957
+ sampling: defaultSampling,
2958
+ decision: sampleDecision,
2959
+ ctx,
2960
+ beginRequest,
2961
+ input: params,
2962
+ error,
2963
+ startedAt: sampleStartedAt
2964
+ });
2965
+ throw error;
2094
2966
  });
2095
2967
  return wrappedPromise;
2096
2968
  }, withUsage);
@@ -2102,7 +2974,11 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2102
2974
  withUsage,
2103
2975
  promptCompression
2104
2976
  } = splitUsageOptions(options);
2105
- const beginRequest = resolveBeginRequest(defaultContext, usageContext);
2977
+ const beginRequest = withRuntimeCompressionContext(
2978
+ resolveBeginRequest(defaultContext, usageContext),
2979
+ "openai",
2980
+ String(params.model)
2981
+ );
2106
2982
  return usageTap.withUsage(beginRequest, async (ctx) => {
2107
2983
  const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
2108
2984
  const finalParams = await compressChatParamsForCall({
@@ -2115,12 +2991,17 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2115
2991
  withUsage,
2116
2992
  operation: "chat.completions.stream"
2117
2993
  });
2994
+ ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
2118
2995
  const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
2119
2996
  const apiPromise = originalStream(finalParams, request);
2120
2997
  const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
2121
2998
  ensureAsyncIterable(rawStream, "chat.completions.stream");
2122
2999
  const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
2123
- const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
3000
+ const usage = await extractUsageFromStream(
3001
+ rawStream,
3002
+ ctx.begin.data.vendorHints,
3003
+ provider
3004
+ );
2124
3005
  if (usage) {
2125
3006
  ctx.setUsage(usage);
2126
3007
  }
@@ -2144,19 +3025,70 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
2144
3025
  return new Proxy(resource, handler);
2145
3026
  }
2146
3027
  async function compressChatParamsForCall(args) {
2147
- const compression = resolveEffectivePromptCompressionOptions(
3028
+ let compression = resolveEffectivePromptCompressionOptions(
2148
3029
  args.defaultPromptCompression,
2149
3030
  args.callPromptCompression
2150
3031
  );
3032
+ const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
3033
+ const policyDriven = !compression && Boolean(runtimePolicy);
3034
+ if (policyDriven && runtimePolicy) {
3035
+ if (!runtimePolicy.selected) {
3036
+ args.ctx.setUsage({
3037
+ runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
3038
+ });
3039
+ return args.params;
3040
+ }
3041
+ compression = openAIPolicyCompressionOptions(runtimePolicy);
3042
+ if (runtimePolicy.mode === "SHADOW") {
3043
+ const startedAt2 = Date.now();
3044
+ void compressChatParams(
3045
+ args.params,
3046
+ args.usageTap,
3047
+ compression,
3048
+ args.withUsage?.signal
3049
+ ).then((outcome2) => {
3050
+ args.ctx.setUsage({
3051
+ runtimeCompression: openAIRuntimeMeasurement({
3052
+ policy: runtimePolicy,
3053
+ telemetry: buildPromptCompressionTelemetry(outcome2.segments),
3054
+ latencyMs: Date.now() - startedAt2,
3055
+ shadow: true,
3056
+ originalPayload: args.params,
3057
+ resultPayload: outcome2.params
3058
+ })
3059
+ });
3060
+ }).catch(() => {
3061
+ args.ctx.setUsage({
3062
+ runtimeCompression: failedRuntimeMeasurement(
3063
+ runtimePolicy,
3064
+ Date.now() - startedAt2
3065
+ )
3066
+ });
3067
+ });
3068
+ return args.params;
3069
+ }
3070
+ }
2151
3071
  if (!compression) {
2152
3072
  return args.params;
2153
3073
  }
3074
+ const startedAt = Date.now();
2154
3075
  const outcome = await compressChatParams(
2155
3076
  args.params,
2156
3077
  args.usageTap,
2157
3078
  compression,
2158
3079
  args.withUsage?.signal
2159
3080
  );
3081
+ if (policyDriven && runtimePolicy) {
3082
+ const runtimeMeasurement = openAIRuntimeMeasurement({
3083
+ policy: runtimePolicy,
3084
+ telemetry: buildPromptCompressionTelemetry(outcome.segments),
3085
+ latencyMs: Date.now() - startedAt,
3086
+ originalPayload: args.params,
3087
+ resultPayload: outcome.params
3088
+ });
3089
+ args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
3090
+ if (runtimeMeasurement.decision !== "compressed") return args.params;
3091
+ }
2160
3092
  await recordCompressionOutcome({
2161
3093
  outcome,
2162
3094
  compression,
@@ -2169,19 +3101,70 @@ async function compressChatParamsForCall(args) {
2169
3101
  return outcome.params;
2170
3102
  }
2171
3103
  async function compressResponsesParamsForCall(args) {
2172
- const compression = resolveEffectivePromptCompressionOptions(
3104
+ let compression = resolveEffectivePromptCompressionOptions(
2173
3105
  args.defaultPromptCompression,
2174
3106
  args.callPromptCompression
2175
3107
  );
3108
+ const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
3109
+ const policyDriven = !compression && Boolean(runtimePolicy);
3110
+ if (policyDriven && runtimePolicy) {
3111
+ if (!runtimePolicy.selected) {
3112
+ args.ctx.setUsage({
3113
+ runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
3114
+ });
3115
+ return args.params;
3116
+ }
3117
+ compression = openAIPolicyCompressionOptions(runtimePolicy);
3118
+ if (runtimePolicy.mode === "SHADOW") {
3119
+ const startedAt2 = Date.now();
3120
+ void compressResponsesParams(
3121
+ args.params,
3122
+ args.usageTap,
3123
+ compression,
3124
+ args.withUsage?.signal
3125
+ ).then((outcome2) => {
3126
+ args.ctx.setUsage({
3127
+ runtimeCompression: openAIRuntimeMeasurement({
3128
+ policy: runtimePolicy,
3129
+ telemetry: buildPromptCompressionTelemetry(outcome2.segments),
3130
+ latencyMs: Date.now() - startedAt2,
3131
+ shadow: true,
3132
+ originalPayload: args.params,
3133
+ resultPayload: outcome2.params
3134
+ })
3135
+ });
3136
+ }).catch(() => {
3137
+ args.ctx.setUsage({
3138
+ runtimeCompression: failedRuntimeMeasurement(
3139
+ runtimePolicy,
3140
+ Date.now() - startedAt2
3141
+ )
3142
+ });
3143
+ });
3144
+ return args.params;
3145
+ }
3146
+ }
2176
3147
  if (!compression) {
2177
3148
  return args.params;
2178
3149
  }
3150
+ const startedAt = Date.now();
2179
3151
  const outcome = await compressResponsesParams(
2180
3152
  args.params,
2181
3153
  args.usageTap,
2182
3154
  compression,
2183
3155
  args.withUsage?.signal
2184
3156
  );
3157
+ if (policyDriven && runtimePolicy) {
3158
+ const runtimeMeasurement = openAIRuntimeMeasurement({
3159
+ policy: runtimePolicy,
3160
+ telemetry: buildPromptCompressionTelemetry(outcome.segments),
3161
+ latencyMs: Date.now() - startedAt,
3162
+ originalPayload: args.params,
3163
+ resultPayload: outcome.params
3164
+ });
3165
+ args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
3166
+ if (runtimeMeasurement.decision !== "compressed") return args.params;
3167
+ }
2185
3168
  await recordCompressionOutcome({
2186
3169
  outcome,
2187
3170
  compression,
@@ -2193,6 +3176,79 @@ async function compressResponsesParamsForCall(args) {
2193
3176
  });
2194
3177
  return outcome.params;
2195
3178
  }
3179
+ function openAIPolicyCompressionOptions(policy) {
3180
+ const roles = Object.fromEntries(
3181
+ ["system", "user", "tool", "assistant"].filter((role) => policy.snapshot.scope.messageRoles.includes(role)).map((role) => [role, true])
3182
+ );
3183
+ return {
3184
+ provider: "usagetap",
3185
+ roles,
3186
+ minContextTokens: policy.snapshot.scope.minimumTokens,
3187
+ failOpen: true
3188
+ };
3189
+ }
3190
+ function bypassedRuntimeMeasurement(policy) {
3191
+ return {
3192
+ policyId: policy.policyId,
3193
+ policyVersionId: policy.policyVersionId,
3194
+ policyVersion: policy.version,
3195
+ rolloutMode: policy.mode,
3196
+ decision: "bypassed",
3197
+ reasonCode: "not_in_rollout_sample",
3198
+ methodsAttempted: [],
3199
+ methodsApplied: [],
3200
+ originalTokens: 0,
3201
+ resultTokens: 0,
3202
+ savedTokens: 0,
3203
+ reductionPercentage: 0,
3204
+ compressionLatencyMs: 0,
3205
+ compressionComputeCostUsd: 0,
3206
+ financialsEstimated: true
3207
+ };
3208
+ }
3209
+ function failedRuntimeMeasurement(policy, latencyMs) {
3210
+ return {
3211
+ ...bypassedRuntimeMeasurement(policy),
3212
+ decision: "fallback",
3213
+ reasonCode: "transformation_failed",
3214
+ methodsAttempted: ["deterministic"],
3215
+ compressionLatencyMs: latencyMs
3216
+ };
3217
+ }
3218
+ function openAIRuntimeMeasurement(input) {
3219
+ const telemetry = input.telemetry;
3220
+ const originalTokens = telemetry?.originalTokens ?? estimatePromptTokens(JSON.stringify(input.originalPayload));
3221
+ const resultTokens = telemetry?.compressedTokens ?? originalTokens;
3222
+ const savedTokens = Math.max(0, telemetry?.savedTokens ?? 0);
3223
+ const reductionPercentage = originalTokens > 0 ? savedTokens / originalTokens * 100 : 0;
3224
+ const belowMinimumSize = originalTokens < input.policy.snapshot.scope.minimumTokens;
3225
+ const latencyExceeded = input.latencyMs > input.policy.snapshot.gates.maximumLatencyMs;
3226
+ const belowSavings = savedTokens < input.policy.snapshot.gates.minimumTokensRemoved || reductionPercentage < input.policy.snapshot.gates.minimumReductionPercentage;
3227
+ const accepted = !belowMinimumSize && !latencyExceeded && !belowSavings;
3228
+ const deepText = input.policy.snapshot.methods.deepText.enabled && !input.policy.snapshot.rollout.deterministicOnly && (input.policy.mode !== "CANARY" || input.policy.snapshot.rollout.deepTextCanaryAllowed);
3229
+ const reasonCode = belowMinimumSize ? "below_minimum_size" : latencyExceeded ? "latency_budget_exceeded" : belowSavings ? "below_minimum_savings" : void 0;
3230
+ const measurement = {
3231
+ policyId: input.policy.policyId,
3232
+ policyVersionId: input.policy.policyVersionId,
3233
+ policyVersion: input.policy.version,
3234
+ rolloutMode: input.policy.mode,
3235
+ decision: accepted ? input.shadow ? "bypassed" : "compressed" : latencyExceeded ? "fallback" : "skipped",
3236
+ ...reasonCode ? { reasonCode } : {},
3237
+ methodsAttempted: [
3238
+ "deterministic",
3239
+ ...deepText ? ["deep_text"] : []
3240
+ ],
3241
+ methodsApplied: accepted ? ["deterministic"] : [],
3242
+ originalTokens,
3243
+ resultTokens: accepted ? resultTokens : originalTokens,
3244
+ savedTokens: accepted ? savedTokens : 0,
3245
+ reductionPercentage: accepted ? reductionPercentage : 0,
3246
+ compressionLatencyMs: input.latencyMs,
3247
+ compressionComputeCostUsd: 0,
3248
+ financialsEstimated: true
3249
+ };
3250
+ return measurement;
3251
+ }
2196
3252
  async function recordCompressionOutcome(args) {
2197
3253
  const telemetry = buildPromptCompressionTelemetry(args.outcome.segments);
2198
3254
  if (!telemetry) {
@@ -2246,6 +3302,10 @@ async function compressChatParams(params, usageTap, compression, signal) {
2246
3302
  const result = await usageTap.compressPromptMessages(source, {
2247
3303
  provider: "usagetap",
2248
3304
  failOpen: compression.failOpen,
3305
+ mode: compression.mode,
3306
+ latencyBudgetMs: compression.latencyBudgetMs,
3307
+ compactEmptyUserMessages: compression.compactEmptyUserMessages,
3308
+ compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
2249
3309
  aggressiveness: resolveMessageEndpointAggressiveness(compression),
2250
3310
  signal
2251
3311
  });
@@ -2711,23 +3771,53 @@ function resolveBeginRequest(defaults, override) {
2711
3771
  }
2712
3772
  const tags = mergeTags(base.tags, current.tags);
2713
3773
  const begin = { customerId };
3774
+ const runtimeCompressionContext = current.runtimeCompressionContext ?? base.runtimeCompressionContext;
3775
+ if (runtimeCompressionContext) {
3776
+ begin.runtimeCompressionContext = runtimeCompressionContext;
3777
+ }
2714
3778
  const requested = current.requested ?? base.requested;
2715
3779
  if (requested) begin.requested = requested;
2716
3780
  const feature = current.feature ?? base.feature;
2717
3781
  if (feature) begin.feature = feature;
3782
+ const runId = current.runId ?? base.runId;
3783
+ if (runId) begin.runId = runId;
2718
3784
  const idempotency = current.idempotency ?? base.idempotency;
2719
3785
  if (idempotency) begin.idempotency = idempotency;
2720
3786
  const customerName = current.customerName ?? base.customerName;
2721
3787
  if (customerName) begin.customerName = customerName;
2722
3788
  const customerEmail = current.customerEmail ?? base.customerEmail;
2723
3789
  if (customerEmail) begin.customerEmail = customerEmail;
3790
+ const customerUserId = current.customerUserId ?? base.customerUserId;
3791
+ if (customerUserId) begin.customerUserId = customerUserId;
3792
+ const customerUserName = current.customerUserName ?? base.customerUserName;
3793
+ if (customerUserName) begin.customerUserName = customerUserName;
3794
+ const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
3795
+ if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
3796
+ const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
3797
+ if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
3798
+ const holdUsd = current.holdUsd ?? base.holdUsd;
3799
+ if (typeof holdUsd === "number") begin.holdUsd = holdUsd;
3800
+ const batch = current.batch ?? base.batch;
3801
+ if (typeof batch === "boolean") begin.batch = batch;
3802
+ const pricingMode = current.pricingMode ?? base.pricingMode;
3803
+ if (pricingMode) begin.pricingMode = pricingMode;
2724
3804
  if (tags?.length) {
2725
3805
  begin.tags = tags;
2726
3806
  }
2727
3807
  return begin;
2728
3808
  }
2729
- function transformApiPromise(apiPromise, onResolve) {
2730
- const resolvedPromise = Promise.resolve(apiPromise).then(onResolve);
3809
+ function withRuntimeCompressionContext(begin, provider, model) {
3810
+ return {
3811
+ ...begin,
3812
+ runtimeCompressionContext: {
3813
+ ...begin.runtimeCompressionContext ?? {},
3814
+ provider,
3815
+ model
3816
+ }
3817
+ };
3818
+ }
3819
+ function transformApiPromise(apiPromise, onResolve, onReject) {
3820
+ const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
2731
3821
  if (isObjectRecord(apiPromise)) {
2732
3822
  const proto = Object.getPrototypeOf(apiPromise);
2733
3823
  if (proto) {
@@ -2862,12 +3952,12 @@ function applyResponsesVendorHints(params, hints) {
2862
3952
  }
2863
3953
  return next;
2864
3954
  }
2865
- async function extractUsageFromStream(stream, hints) {
3955
+ async function extractUsageFromStream(stream, hints, provider = "openai") {
2866
3956
  const finalPayload = await resolveStreamFinalPayload(stream);
2867
3957
  if (!finalPayload) {
2868
3958
  return void 0;
2869
3959
  }
2870
- return inferUsageFromResponse(finalPayload, hints);
3960
+ return inferUsageFromResponse(finalPayload, hints, provider);
2871
3961
  }
2872
3962
  async function resolveStreamFinalPayload(stream) {
2873
3963
  if (!stream || typeof stream !== "object") {
@@ -2940,14 +4030,14 @@ function setHeaderIfPossible(res, key, value) {
2940
4030
  res.setHeader(key, value);
2941
4031
  }
2942
4032
  }
2943
- function tryInferUsage(response, hints, extractor, ctx) {
4033
+ function tryInferUsage(response, hints, extractor, ctx, provider = "openai") {
2944
4034
  const explicit = extractor?.(response);
2945
- const inferred = explicit ?? inferUsageFromResponse(response, hints);
4035
+ const inferred = explicit ?? inferUsageFromResponse(response, hints, provider);
2946
4036
  if (inferred) {
2947
4037
  ctx.setUsage(inferred);
2948
4038
  }
2949
4039
  }
2950
- function inferUsageFromResponse(response, hints) {
4040
+ function inferUsageFromResponse(response, hints, provider = "openai") {
2951
4041
  if (!response || typeof response !== "object") {
2952
4042
  return void 0;
2953
4043
  }
@@ -2955,12 +4045,41 @@ function inferUsageFromResponse(response, hints) {
2955
4045
  if (!candidate.usage) {
2956
4046
  return void 0;
2957
4047
  }
2958
- const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
4048
+ const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
4049
+ const responseEffort = normalizeExecutionReasoningEffort(
4050
+ candidate.reasoning?.effort
4051
+ );
2959
4052
  return {
4053
+ providerUsed: provider,
2960
4054
  modelUsed: candidate.model ?? hints?.preferredModel,
2961
- inputTokens: candidate.usage.prompt_tokens,
2962
- responseTokens: candidate.usage.completion_tokens,
2963
- cachedInputTokens
4055
+ inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
4056
+ responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
4057
+ cachedInputTokens,
4058
+ reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
4059
+ ...responseEffort ? {
4060
+ reasoningEffort: responseEffort,
4061
+ reasoningEffortSource: "provider_response"
4062
+ } : {},
4063
+ ...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {}
4064
+ };
4065
+ }
4066
+ function normalizeExecutionReasoningEffort(value) {
4067
+ return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
4068
+ }
4069
+ function openAIRequestExecutionMetadata(params, provider) {
4070
+ const record = params && typeof params === "object" ? params : {};
4071
+ const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
4072
+ const effort = normalizeExecutionReasoningEffort(
4073
+ record.reasoning_effort ?? reasoning?.effort ?? record.thinking_level
4074
+ );
4075
+ const mode = typeof reasoning?.type === "string" ? reasoning.type : typeof reasoning?.mode === "string" ? reasoning.mode : void 0;
4076
+ const rawBudget = reasoning?.budget_tokens ?? record.thinking_budget ?? record.thinking_budget_tokens;
4077
+ const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
4078
+ return {
4079
+ providerUsed: provider,
4080
+ ...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
4081
+ ...mode ? { reasoningMode: mode } : {},
4082
+ ...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
2964
4083
  };
2965
4084
  }
2966
4085
  function wrapStreamForUsageTap(source, finalize, ctx) {
@@ -3060,6 +4179,6 @@ function isIteratorResult(value) {
3060
4179
  return isObjectRecord(value) && "done" in value;
3061
4180
  }
3062
4181
 
3063
- export { OpenAIPromptCompressionStats, createOpenAIAdapter, pipeToResponse, streamOpenAIRoute, toNextResponse, withCompression, withMetering, wrapOpenAI };
4182
+ export { OpenAIPromptCompressionStats, createOpenAIAdapter, pipeToResponse, streamOpenAIRoute, toNextResponse, withCompression, withMetering, withSampling, wrapOpenAI };
3064
4183
  //# sourceMappingURL=openai.mjs.map
3065
4184
  //# sourceMappingURL=openai.mjs.map