@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,6 +2222,82 @@ var AnthropicPromptCompressionStats = class {
1614
2222
  }
1615
2223
  };
1616
2224
  var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
2225
+ function withSampling(client, options = {}) {
2226
+ if (!client?.messages || typeof client.messages.create !== "function" || !options) {
2227
+ throw new UsageTapError(
2228
+ "USAGETAP_BAD_REQUEST",
2229
+ "withSampling requires an Anthropic client and sampling options"
2230
+ );
2231
+ }
2232
+ const { apiKey, usageTapClient, ...policy } = options;
2233
+ const localPolicy = typeof policy.rate === "number" ? policy : void 0;
2234
+ const usageTap = usageTapClient ?? new UsageTapClient({ apiKey, sampling: localPolicy });
2235
+ const originalCreate = client.messages.create.bind(client.messages);
2236
+ const create = async (params, requestOptions) => {
2237
+ const { usageTap: callContext = {}, ...providerOptions } = requestOptions ?? {};
2238
+ const decision = params.stream === true ? Promise.resolve(false) : usageTap.shouldSampleAsync({
2239
+ customerId: callContext.customerId,
2240
+ feature: callContext.feature,
2241
+ input: params
2242
+ }, localPolicy);
2243
+ const startedAt = Date.now();
2244
+ try {
2245
+ const response = await originalCreate(
2246
+ params,
2247
+ Object.keys(providerOptions).length ? providerOptions : void 0
2248
+ );
2249
+ const selected = await decision;
2250
+ if (selected) {
2251
+ const record = isObjectRecord(response) ? response : {};
2252
+ await usageTap.captureSample({
2253
+ customerId: callContext.customerId,
2254
+ feature: callContext.feature,
2255
+ environment: callContext.environment,
2256
+ tags: callContext.tags,
2257
+ provider: "anthropic",
2258
+ model: readString(record.model) ?? readString(params.model),
2259
+ input: params,
2260
+ output: response,
2261
+ usage: record.usage,
2262
+ latencyMs: Date.now() - startedAt
2263
+ }).catch(() => void 0);
2264
+ }
2265
+ return response;
2266
+ } catch (error) {
2267
+ const selected = await decision;
2268
+ if (selected) {
2269
+ await usageTap.captureSample({
2270
+ customerId: callContext.customerId,
2271
+ feature: callContext.feature,
2272
+ environment: callContext.environment,
2273
+ tags: callContext.tags,
2274
+ provider: "anthropic",
2275
+ model: readString(params.model),
2276
+ input: params,
2277
+ latencyMs: Date.now() - startedAt,
2278
+ error: error instanceof Error ? { name: error.name, message: error.message } : { message: String(error) }
2279
+ }).catch(() => void 0);
2280
+ }
2281
+ throw error;
2282
+ }
2283
+ };
2284
+ const messages = new Proxy(client.messages, {
2285
+ get(target, prop, receiver) {
2286
+ if (prop === "create") return create;
2287
+ return safeReflectGet(target, prop, receiver);
2288
+ }
2289
+ });
2290
+ return new Proxy(client, {
2291
+ get(target, prop, receiver) {
2292
+ if (prop === "messages") return messages;
2293
+ if (prop === "unwrap") return () => target;
2294
+ return safeReflectGet(target, prop, receiver);
2295
+ }
2296
+ });
2297
+ }
2298
+ function safeReflectGet(target, prop, receiver) {
2299
+ return Reflect.get(target, prop, receiver);
2300
+ }
1617
2301
  function withCompression(client, options = {}) {
1618
2302
  if (!client?.messages || typeof client.messages.create !== "function") {
1619
2303
  throw new UsageTapError(
@@ -1624,11 +2308,13 @@ function withCompression(client, options = {}) {
1624
2308
  const {
1625
2309
  apiKey,
1626
2310
  usageTapClient,
2311
+ sampling,
1627
2312
  ...compressionOverrides
1628
2313
  } = options;
1629
2314
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
1630
2315
  const compression = {
1631
2316
  provider: "usagetap",
2317
+ roles: { user: { enabled: true, aggressiveness: 0.2 } },
1632
2318
  minContextTokens: DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS,
1633
2319
  ...compressionOverrides
1634
2320
  };
@@ -1642,13 +2328,18 @@ function withCompression(client, options = {}) {
1642
2328
  return Reflect.get(target, prop, receiver);
1643
2329
  }
1644
2330
  });
1645
- return new Proxy(client, {
2331
+ const compressed = new Proxy(client, {
1646
2332
  get(target, prop, receiver) {
1647
2333
  if (prop === "messages") return proxiedMessages;
1648
2334
  if (prop === "unwrap") return () => target;
1649
2335
  return Reflect.get(target, prop, receiver);
1650
2336
  }
1651
2337
  });
2338
+ return sampling ? withSampling(compressed, {
2339
+ ...sampling === true ? {} : sampling,
2340
+ apiKey,
2341
+ usageTapClient
2342
+ }) : compressed;
1652
2343
  }
1653
2344
  function withMetering(client, customer) {
1654
2345
  const config = typeof customer === "string" ? { customerId: customer } : customer;
@@ -1663,6 +2354,7 @@ function withMetering(client, customer) {
1663
2354
  usageTapClient,
1664
2355
  applyVendorHints,
1665
2356
  promptCompression,
2357
+ sampling,
1666
2358
  ...defaultContext
1667
2359
  } = config;
1668
2360
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
@@ -1670,7 +2362,8 @@ function withMetering(client, customer) {
1670
2362
  return wrapAnthropic(client, usageTap, {
1671
2363
  defaultContext,
1672
2364
  applyVendorHints,
1673
- promptCompression: normalizedCompression
2365
+ promptCompression: normalizedCompression,
2366
+ sampling
1674
2367
  });
1675
2368
  }
1676
2369
  function wrapAnthropic(client, usageTap, options = {}) {
@@ -1683,6 +2376,7 @@ function wrapAnthropic(client, usageTap, options = {}) {
1683
2376
  const defaultContext = options.defaultContext;
1684
2377
  const applyVendorHints = options.applyVendorHints !== false;
1685
2378
  const defaultPromptCompression = normalizePromptCompressionOptions(options.promptCompression);
2379
+ const defaultSampling = normalizeMeteredAnthropicSampling(options.sampling);
1686
2380
  const promptCompressionStats = new AnthropicPromptCompressionStats();
1687
2381
  const proxiedMessages = createMessagesProxy(
1688
2382
  client.messages,
@@ -1690,7 +2384,8 @@ function wrapAnthropic(client, usageTap, options = {}) {
1690
2384
  defaultContext,
1691
2385
  applyVendorHints,
1692
2386
  defaultPromptCompression,
1693
- promptCompressionStats
2387
+ promptCompressionStats,
2388
+ defaultSampling
1694
2389
  );
1695
2390
  const handler = {
1696
2391
  get(target, prop, receiver) {
@@ -1708,7 +2403,7 @@ function wrapAnthropic(client, usageTap, options = {}) {
1708
2403
  };
1709
2404
  return new Proxy(client, handler);
1710
2405
  }
1711
- function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2406
+ function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling) {
1712
2407
  if (typeof resource.create !== "function") {
1713
2408
  throw new UsageTapError("USAGETAP_BAD_REQUEST", "wrapAnthropic requires client.messages.create");
1714
2409
  }
@@ -1731,7 +2426,8 @@ function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHint
1731
2426
  defaultContext,
1732
2427
  applyVendorHints,
1733
2428
  defaultPromptCompression,
1734
- promptCompressionStats
2429
+ promptCompressionStats,
2430
+ defaultSampling
1735
2431
  });
1736
2432
  });
1737
2433
  const handler = {
@@ -1745,13 +2441,26 @@ function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHint
1745
2441
  return new Proxy(resource, handler);
1746
2442
  }
1747
2443
  async function invokeMessagesCreate(args) {
1748
- const beginRequest = resolveBeginRequest(args.defaultContext, args.usageContext);
2444
+ const beginRequest = withRuntimeCompressionContext(
2445
+ resolveBeginRequest(args.defaultContext, args.usageContext),
2446
+ "anthropic",
2447
+ String(args.params.model)
2448
+ );
1749
2449
  const begin = await args.usageTap.beginCall(
1750
2450
  beginRequest,
1751
2451
  beginCallOptions(args.withUsage)
1752
2452
  );
1753
2453
  const state = createCallState(beginRequest, begin);
1754
2454
  const ctx = createUsageContext(state);
2455
+ const sampleStartedAt = Date.now();
2456
+ const wantsStream = isStreamingRequest(args.params);
2457
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredAnthropicSampleDecision({
2458
+ usageTap: args.usageTap,
2459
+ sampling: args.defaultSampling,
2460
+ beginRequest,
2461
+ input: args.params
2462
+ });
2463
+ let providerCompleted = false;
1755
2464
  try {
1756
2465
  const hintedParams = args.applyVendorHints ? applyAnthropicVendorHints(args.params, ctx.begin.data.vendorHints) : args.params;
1757
2466
  const finalParams = await compressAnthropicParamsForCall({
@@ -1764,8 +2473,10 @@ async function invokeMessagesCreate(args) {
1764
2473
  withUsage: args.withUsage,
1765
2474
  operation: "messages.create"
1766
2475
  });
2476
+ ctx.setUsage(anthropicRequestExecutionMetadata(finalParams));
1767
2477
  const request = attachCorrelationHeader(args.requestOptions, ctx.begin.correlationId);
1768
2478
  const response = await args.originalCreate(finalParams, request);
2479
+ providerCompleted = true;
1769
2480
  if (isStreamingRequest(finalParams)) {
1770
2481
  ensureAsyncIterable(response, "messages.create");
1771
2482
  return wrapAnthropicStreamForUsageTap(
@@ -1777,6 +2488,15 @@ async function invokeMessagesCreate(args) {
1777
2488
  );
1778
2489
  }
1779
2490
  inferAnthropicUsage(response, readString(finalParams.model), ctx);
2491
+ await captureMeteredAnthropicSample({
2492
+ usageTap: args.usageTap,
2493
+ sampling: args.defaultSampling,
2494
+ decision: sampleDecision,
2495
+ state,
2496
+ input: args.params,
2497
+ response,
2498
+ startedAt: sampleStartedAt
2499
+ });
1780
2500
  await finalizeCall(state, args.usageTap, args.withUsage);
1781
2501
  return response;
1782
2502
  } catch (error) {
@@ -1786,24 +2506,145 @@ async function invokeMessagesCreate(args) {
1786
2506
  message: error instanceof Error ? error.message : String(error)
1787
2507
  };
1788
2508
  }
2509
+ if (!providerCompleted) {
2510
+ await captureMeteredAnthropicSample({
2511
+ usageTap: args.usageTap,
2512
+ sampling: args.defaultSampling,
2513
+ decision: sampleDecision,
2514
+ state,
2515
+ input: args.params,
2516
+ error,
2517
+ startedAt: sampleStartedAt
2518
+ });
2519
+ }
1789
2520
  await finalizeCall(state, args.usageTap, args.withUsage);
1790
2521
  throw error;
1791
2522
  }
1792
2523
  }
2524
+ function normalizeMeteredAnthropicSampling(options) {
2525
+ if (!options) return void 0;
2526
+ if (options === true) return {};
2527
+ return {
2528
+ policy: typeof options.rate === "number" ? options : void 0
2529
+ };
2530
+ }
2531
+ function startMeteredAnthropicSampleDecision({
2532
+ usageTap,
2533
+ sampling,
2534
+ beginRequest,
2535
+ input
2536
+ }) {
2537
+ if (!sampling) return Promise.resolve(false);
2538
+ return usageTap.shouldSampleAsync(
2539
+ {
2540
+ customerId: beginRequest.customerId,
2541
+ feature: beginRequest.feature,
2542
+ input
2543
+ },
2544
+ sampling.policy
2545
+ );
2546
+ }
2547
+ async function captureMeteredAnthropicSample({
2548
+ usageTap,
2549
+ sampling,
2550
+ decision,
2551
+ state,
2552
+ input,
2553
+ response,
2554
+ error,
2555
+ startedAt
2556
+ }) {
2557
+ if (!sampling) return;
2558
+ let selected = false;
2559
+ try {
2560
+ selected = await decision;
2561
+ } catch {
2562
+ return;
2563
+ }
2564
+ if (!selected) return;
2565
+ const record = isObjectRecord(response) ? response : {};
2566
+ await usageTap.captureSample({
2567
+ sampleId: state.begin.data.callId,
2568
+ callId: state.begin.data.callId,
2569
+ customerId: state.beginRequest.customerId,
2570
+ feature: state.beginRequest.feature,
2571
+ tags: state.beginRequest.tags,
2572
+ provider: "anthropic",
2573
+ model: readString(record.model) ?? readString(input.model),
2574
+ input,
2575
+ ...response === void 0 ? {} : { output: response },
2576
+ usage: record.usage,
2577
+ latencyMs: Date.now() - startedAt,
2578
+ ...error === void 0 ? {} : {
2579
+ error: error instanceof Error ? { name: error.name, message: error.message } : { message: String(error) }
2580
+ }
2581
+ }).catch(() => void 0);
2582
+ }
1793
2583
  async function compressAnthropicParamsForCall(args) {
1794
- const compression = resolveEffectivePromptCompressionOptions(
2584
+ let compression = resolveEffectivePromptCompressionOptions(
1795
2585
  args.defaultPromptCompression,
1796
2586
  args.callPromptCompression
1797
2587
  );
2588
+ const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
2589
+ const policyDriven = !compression && Boolean(runtimePolicy);
2590
+ if (policyDriven && runtimePolicy) {
2591
+ if (!runtimePolicy.selected) {
2592
+ args.ctx.setUsage({
2593
+ runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
2594
+ });
2595
+ return args.params;
2596
+ }
2597
+ compression = anthropicPolicyCompressionOptions(runtimePolicy);
2598
+ if (runtimePolicy.mode === "SHADOW") {
2599
+ const startedAt2 = Date.now();
2600
+ void compressAnthropicParams(
2601
+ args.params,
2602
+ args.usageTap,
2603
+ compression,
2604
+ args.withUsage?.signal
2605
+ ).then((outcome2) => {
2606
+ args.ctx.setUsage({
2607
+ runtimeCompression: anthropicRuntimeMeasurement({
2608
+ policy: runtimePolicy,
2609
+ telemetry: buildPromptCompressionTelemetry(outcome2.segments),
2610
+ latencyMs: Date.now() - startedAt2,
2611
+ shadow: true,
2612
+ originalPayload: args.params,
2613
+ resultPayload: outcome2.params
2614
+ })
2615
+ });
2616
+ }).catch(() => {
2617
+ args.ctx.setUsage({
2618
+ runtimeCompression: failedRuntimeMeasurement(
2619
+ runtimePolicy,
2620
+ Date.now() - startedAt2
2621
+ )
2622
+ });
2623
+ });
2624
+ return args.params;
2625
+ }
2626
+ }
1798
2627
  if (!compression) {
1799
2628
  return args.params;
1800
2629
  }
2630
+ const startedAt = Date.now();
1801
2631
  const outcome = await compressAnthropicParams(
1802
2632
  args.params,
1803
2633
  args.usageTap,
1804
2634
  compression,
1805
2635
  args.withUsage?.signal
1806
2636
  );
2637
+ if (policyDriven && runtimePolicy) {
2638
+ const runtimeMeasurement = anthropicRuntimeMeasurement({
2639
+ policy: runtimePolicy,
2640
+ telemetry: buildPromptCompressionTelemetry(outcome.segments),
2641
+ latencyMs: Date.now() - startedAt,
2642
+ originalPayload: args.params,
2643
+ resultPayload: outcome.params
2644
+ });
2645
+ args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
2646
+ if (runtimeMeasurement.decision !== "compressed") return args.params;
2647
+ }
1807
2648
  await recordCompressionOutcome({
1808
2649
  outcome,
1809
2650
  compression,
@@ -1815,6 +2656,76 @@ async function compressAnthropicParamsForCall(args) {
1815
2656
  });
1816
2657
  return outcome.params;
1817
2658
  }
2659
+ function anthropicPolicyCompressionOptions(policy) {
2660
+ const roles = Object.fromEntries(
2661
+ ["system", "user", "tool", "assistant"].filter((role) => policy.snapshot.scope.messageRoles.includes(role)).map((role) => [role, true])
2662
+ );
2663
+ return {
2664
+ provider: "usagetap",
2665
+ roles,
2666
+ minContextTokens: policy.snapshot.scope.minimumTokens,
2667
+ failOpen: true
2668
+ };
2669
+ }
2670
+ function bypassedRuntimeMeasurement(policy) {
2671
+ return {
2672
+ policyId: policy.policyId,
2673
+ policyVersionId: policy.policyVersionId,
2674
+ policyVersion: policy.version,
2675
+ rolloutMode: policy.mode,
2676
+ decision: "bypassed",
2677
+ reasonCode: "not_in_rollout_sample",
2678
+ methodsAttempted: [],
2679
+ methodsApplied: [],
2680
+ originalTokens: 0,
2681
+ resultTokens: 0,
2682
+ savedTokens: 0,
2683
+ reductionPercentage: 0,
2684
+ compressionLatencyMs: 0,
2685
+ compressionComputeCostUsd: 0,
2686
+ financialsEstimated: true
2687
+ };
2688
+ }
2689
+ function failedRuntimeMeasurement(policy, latencyMs) {
2690
+ return {
2691
+ ...bypassedRuntimeMeasurement(policy),
2692
+ decision: "fallback",
2693
+ reasonCode: "transformation_failed",
2694
+ methodsAttempted: ["deterministic"],
2695
+ compressionLatencyMs: latencyMs
2696
+ };
2697
+ }
2698
+ function anthropicRuntimeMeasurement(input) {
2699
+ const telemetry = input.telemetry;
2700
+ const originalTokens = telemetry?.originalTokens ?? estimatePromptTokens(JSON.stringify(input.originalPayload));
2701
+ const resultTokens = telemetry?.compressedTokens ?? originalTokens;
2702
+ const savedTokens = Math.max(0, telemetry?.savedTokens ?? 0);
2703
+ const reductionPercentage = originalTokens > 0 ? savedTokens / originalTokens * 100 : 0;
2704
+ const belowMinimumSize = originalTokens < input.policy.snapshot.scope.minimumTokens;
2705
+ const latencyExceeded = input.latencyMs > input.policy.snapshot.gates.maximumLatencyMs;
2706
+ const belowSavings = savedTokens < input.policy.snapshot.gates.minimumTokensRemoved || reductionPercentage < input.policy.snapshot.gates.minimumReductionPercentage;
2707
+ const accepted = !belowMinimumSize && !latencyExceeded && !belowSavings;
2708
+ const deepText = input.policy.snapshot.methods.deepText.enabled && !input.policy.snapshot.rollout.deterministicOnly && (input.policy.mode !== "CANARY" || input.policy.snapshot.rollout.deepTextCanaryAllowed);
2709
+ const reasonCode = belowMinimumSize ? "below_minimum_size" : latencyExceeded ? "latency_budget_exceeded" : belowSavings ? "below_minimum_savings" : void 0;
2710
+ const measurement = {
2711
+ policyId: input.policy.policyId,
2712
+ policyVersionId: input.policy.policyVersionId,
2713
+ policyVersion: input.policy.version,
2714
+ rolloutMode: input.policy.mode,
2715
+ decision: accepted ? input.shadow ? "bypassed" : "compressed" : latencyExceeded ? "fallback" : "skipped",
2716
+ ...reasonCode ? { reasonCode } : {},
2717
+ methodsAttempted: ["deterministic", ...deepText ? ["deep_text"] : []],
2718
+ methodsApplied: accepted ? ["deterministic"] : [],
2719
+ originalTokens,
2720
+ resultTokens: accepted ? resultTokens : originalTokens,
2721
+ savedTokens: accepted ? savedTokens : 0,
2722
+ reductionPercentage: accepted ? reductionPercentage : 0,
2723
+ compressionLatencyMs: input.latencyMs,
2724
+ compressionComputeCostUsd: 0,
2725
+ financialsEstimated: true
2726
+ };
2727
+ return measurement;
2728
+ }
1818
2729
  async function recordCompressionOutcome(args) {
1819
2730
  const telemetry = buildPromptCompressionTelemetry(args.outcome.segments);
1820
2731
  if (!telemetry) {
@@ -1869,6 +2780,10 @@ async function compressAnthropicParams(params, usageTap, compression, signal) {
1869
2780
  const result = await usageTap.compressPromptMessages(source, {
1870
2781
  provider: "usagetap",
1871
2782
  failOpen: compression.failOpen,
2783
+ mode: compression.mode,
2784
+ latencyBudgetMs: compression.latencyBudgetMs,
2785
+ compactEmptyUserMessages: compression.compactEmptyUserMessages,
2786
+ compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
1872
2787
  aggressiveness: resolveMessageEndpointAggressiveness(compression),
1873
2788
  signal
1874
2789
  });
@@ -2253,10 +3168,16 @@ function resolveBeginRequest(defaults, override) {
2253
3168
  }
2254
3169
  const tags = mergeTags(base.tags, current.tags);
2255
3170
  const begin = { customerId };
3171
+ const runtimeCompressionContext = current.runtimeCompressionContext ?? base.runtimeCompressionContext;
3172
+ if (runtimeCompressionContext) {
3173
+ begin.runtimeCompressionContext = runtimeCompressionContext;
3174
+ }
2256
3175
  const requested = current.requested ?? base.requested;
2257
3176
  if (requested) begin.requested = requested;
2258
3177
  const feature = current.feature ?? base.feature;
2259
3178
  if (feature) begin.feature = feature;
3179
+ const runId = current.runId ?? base.runId;
3180
+ if (runId) begin.runId = runId;
2260
3181
  const idempotency = current.idempotency ?? base.idempotency;
2261
3182
  if (idempotency) begin.idempotency = idempotency;
2262
3183
  const idempotencyKey = current.idempotencyKey ?? base.idempotencyKey;
@@ -2265,6 +3186,12 @@ function resolveBeginRequest(defaults, override) {
2265
3186
  if (customerName) begin.customerName = customerName;
2266
3187
  const customerEmail = current.customerEmail ?? base.customerEmail;
2267
3188
  if (customerEmail) begin.customerEmail = customerEmail;
3189
+ const customerUserId = current.customerUserId ?? base.customerUserId;
3190
+ if (customerUserId) begin.customerUserId = customerUserId;
3191
+ const customerUserName = current.customerUserName ?? base.customerUserName;
3192
+ if (customerUserName) begin.customerUserName = customerUserName;
3193
+ const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
3194
+ if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
2268
3195
  const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
2269
3196
  if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
2270
3197
  const holdUsd = current.holdUsd ?? base.holdUsd;
@@ -2280,6 +3207,11 @@ function resolveBeginRequest(defaults, override) {
2280
3207
  }
2281
3208
  function createCallState(beginRequest, begin) {
2282
3209
  const usage = {};
3210
+ const pricingMode = begin.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
3211
+ if (pricingMode) {
3212
+ usage.pricingMode = pricingMode;
3213
+ usage.batch = pricingMode === "batch";
3214
+ }
2283
3215
  const stripeCustomerId = typeof begin.data.stripeCustomerId === "string" ? begin.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
2284
3216
  if (stripeCustomerId) {
2285
3217
  usage.stripeCustomerId = stripeCustomerId;
@@ -2343,10 +3275,12 @@ function inferAnthropicUsage(response, fallbackModel, ctx) {
2343
3275
  }
2344
3276
  function extractAnthropicUsage(payload, fallbackModel) {
2345
3277
  if (!isObjectRecord(payload)) {
2346
- return fallbackModel ? { modelUsed: fallbackModel } : void 0;
3278
+ return { providerUsed: "anthropic", ...fallbackModel ? { modelUsed: fallbackModel } : {} };
2347
3279
  }
2348
3280
  const usage = findAnthropicUsageRecord(payload);
2349
- const result = {};
3281
+ const result = {
3282
+ providerUsed: "anthropic"
3283
+ };
2350
3284
  const model = readString(payload.model) ?? readString(payload.message?.model) ?? fallbackModel;
2351
3285
  if (model) {
2352
3286
  result.modelUsed = model;
@@ -2356,6 +3290,21 @@ function extractAnthropicUsage(payload, fallbackModel) {
2356
3290
  }
2357
3291
  return Object.keys(result).length ? result : void 0;
2358
3292
  }
3293
+ function anthropicRequestExecutionMetadata(params) {
3294
+ const outputConfig = isObjectRecord(params.output_config) ? params.output_config : void 0;
3295
+ const thinking = isObjectRecord(params.thinking) ? params.thinking : void 0;
3296
+ const rawEffort = outputConfig?.effort;
3297
+ const effort = rawEffort === "none" || rawEffort === "minimal" || rawEffort === "low" || rawEffort === "medium" || rawEffort === "high" || rawEffort === "xhigh" || rawEffort === "max" ? rawEffort : void 0;
3298
+ const mode = readString(thinking?.type);
3299
+ const rawBudget = readNumber(thinking?.budget_tokens);
3300
+ const budget = rawBudget !== void 0 && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
3301
+ return {
3302
+ providerUsed: "anthropic",
3303
+ ...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
3304
+ ...mode ? { reasoningMode: mode } : {},
3305
+ ...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
3306
+ };
3307
+ }
2359
3308
  function findAnthropicUsageRecord(payload) {
2360
3309
  if (isObjectRecord(payload.usage)) {
2361
3310
  return payload.usage;
@@ -2366,10 +3315,6 @@ function findAnthropicUsageRecord(payload) {
2366
3315
  return void 0;
2367
3316
  }
2368
3317
  function applyAnthropicUsageRecord(usage, usageRecord) {
2369
- const inputTokens = readNumber(usageRecord.input_tokens ?? usageRecord.prompt_tokens);
2370
- if (inputTokens !== void 0) {
2371
- usage.inputTokens = inputTokens;
2372
- }
2373
3318
  const outputTokens = readNumber(usageRecord.output_tokens ?? usageRecord.completion_tokens);
2374
3319
  if (outputTokens !== void 0) {
2375
3320
  usage.responseTokens = outputTokens;
@@ -2380,6 +3325,26 @@ function applyAnthropicUsageRecord(usage, usageRecord) {
2380
3325
  if (cachedInputTokens !== void 0) {
2381
3326
  usage.cachedInputTokens = cachedInputTokens;
2382
3327
  }
3328
+ const cacheWriteInputTokens = readNumber(
3329
+ usageRecord.cache_creation_input_tokens ?? usageRecord.prompt_cache_miss_tokens ?? usageRecord.cache_write_tokens
3330
+ );
3331
+ if (cacheWriteInputTokens !== void 0) {
3332
+ usage.cacheWriteInputTokens = cacheWriteInputTokens;
3333
+ }
3334
+ const baseInputTokens = readNumber(usageRecord.input_tokens ?? usageRecord.prompt_tokens);
3335
+ if (baseInputTokens !== void 0) {
3336
+ usage.inputTokens = baseInputTokens + (cachedInputTokens ?? 0) + (cacheWriteInputTokens ?? 0);
3337
+ }
3338
+ }
3339
+ function withRuntimeCompressionContext(begin, provider, model) {
3340
+ return {
3341
+ ...begin,
3342
+ runtimeCompressionContext: {
3343
+ ...begin.runtimeCompressionContext ?? {},
3344
+ provider,
3345
+ model
3346
+ }
3347
+ };
2383
3348
  }
2384
3349
  function accumulateAnthropicStreamUsage(chunk, fallbackModel, state) {
2385
3350
  const usage = extractAnthropicUsage(chunk, fallbackModel);
@@ -2603,6 +3568,6 @@ function isIteratorResult(value) {
2603
3568
  return isObjectRecord(value) && "done" in value;
2604
3569
  }
2605
3570
 
2606
- export { AnthropicPromptCompressionStats, withCompression, withMetering, wrapAnthropic };
3571
+ export { AnthropicPromptCompressionStats, withCompression, withMetering, withSampling, wrapAnthropic };
2607
3572
  //# sourceMappingURL=index.mjs.map
2608
3573
  //# sourceMappingURL=index.mjs.map