@usagetap/sdk 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +219 -24
  2. package/dist/adapters/anthropic.cjs +990 -24
  3. package/dist/adapters/anthropic.cjs.map +1 -1
  4. package/dist/adapters/anthropic.d.cts +45 -3
  5. package/dist/adapters/anthropic.d.ts +45 -3
  6. package/dist/adapters/anthropic.mjs +990 -25
  7. package/dist/adapters/anthropic.mjs.map +1 -1
  8. package/dist/adapters/openai.cjs +1164 -44
  9. package/dist/adapters/openai.cjs.map +1 -1
  10. package/dist/adapters/openai.d.cts +46 -3
  11. package/dist/adapters/openai.d.ts +46 -3
  12. package/dist/adapters/openai.mjs +1164 -45
  13. package/dist/adapters/openai.mjs.map +1 -1
  14. package/dist/adapters/openrouter.cjs +3899 -22
  15. package/dist/adapters/openrouter.cjs.map +1 -1
  16. package/dist/adapters/openrouter.d.cts +6 -3
  17. package/dist/adapters/openrouter.d.ts +6 -3
  18. package/dist/adapters/openrouter.mjs +3897 -23
  19. package/dist/adapters/openrouter.mjs.map +1 -1
  20. package/dist/anthropic/index.cjs +990 -24
  21. package/dist/anthropic/index.cjs.map +1 -1
  22. package/dist/anthropic/index.d.cts +2 -2
  23. package/dist/anthropic/index.d.ts +2 -2
  24. package/dist/anthropic/index.mjs +990 -25
  25. package/dist/anthropic/index.mjs.map +1 -1
  26. package/dist/client-CExQ8e1T.d.cts +1225 -0
  27. package/dist/client-CExQ8e1T.d.ts +1225 -0
  28. package/dist/express/index.cjs +421 -29
  29. package/dist/express/index.cjs.map +1 -1
  30. package/dist/express/index.d.cts +2 -2
  31. package/dist/express/index.d.ts +2 -2
  32. package/dist/express/index.mjs +421 -29
  33. package/dist/express/index.mjs.map +1 -1
  34. package/dist/index.cjs +680 -15
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +5 -3
  37. package/dist/index.d.ts +5 -3
  38. package/dist/index.mjs +680 -15
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/openai/index.cjs +1165 -45
  41. package/dist/openai/index.cjs.map +1 -1
  42. package/dist/openai/index.d.cts +2 -2
  43. package/dist/openai/index.d.ts +2 -2
  44. package/dist/openai/index.mjs +1165 -46
  45. package/dist/openai/index.mjs.map +1 -1
  46. package/dist/openrouter/index.cjs +1182 -47
  47. package/dist/openrouter/index.cjs.map +1 -1
  48. package/dist/openrouter/index.d.cts +3 -3
  49. package/dist/openrouter/index.d.ts +3 -3
  50. package/dist/openrouter/index.mjs +1180 -46
  51. package/dist/openrouter/index.mjs.map +1 -1
  52. package/dist/react/index.cjs +19 -1
  53. package/dist/react/index.cjs.map +1 -1
  54. package/dist/react/index.d.cts +17 -4
  55. package/dist/react/index.d.ts +17 -4
  56. package/dist/react/index.mjs +19 -1
  57. package/dist/react/index.mjs.map +1 -1
  58. package/package.json +1 -1
  59. package/dist/client-BD8O2J8Z.d.cts +0 -668
  60. package/dist/client-BD8O2J8Z.d.ts +0 -668
@@ -242,6 +242,11 @@ async function compressMessagesWithUsageTap(options) {
242
242
  aggressiveness,
243
243
  "UsageTap prompt message compression"
244
244
  );
245
+ if (options.latencyBudgetMs !== void 0 && (!Number.isFinite(options.latencyBudgetMs) || options.latencyBudgetMs < 0)) {
246
+ throw new Error(
247
+ "UsageTap prompt message compression latencyBudgetMs must be a non-negative number"
248
+ );
249
+ }
245
250
  const original = stableStringifyInput(options.input);
246
251
  const headers = {
247
252
  "content-type": "application/json"
@@ -256,7 +261,15 @@ async function compressMessagesWithUsageTap(options) {
256
261
  headers,
257
262
  body: JSON.stringify({
258
263
  ...cloneInputRecord(options.input),
259
- compression_settings: { aggressiveness }
264
+ compression_settings: {
265
+ aggressiveness,
266
+ ...options.mode === void 0 ? {} : { mode: options.mode },
267
+ ...options.latencyBudgetMs === void 0 ? {} : { latency_budget_ms: options.latencyBudgetMs },
268
+ ...options.compactEmptyUserMessages === void 0 ? {} : { compact_empty_user_messages: options.compactEmptyUserMessages },
269
+ ...options.compactDuplicateUserTextParts === void 0 ? {} : {
270
+ compact_duplicate_user_text_parts: options.compactDuplicateUserTextParts
271
+ }
272
+ }
260
273
  }),
261
274
  signal: options.signal
262
275
  }
@@ -752,10 +765,358 @@ function scalarToToon(value) {
752
765
  return JSON.stringify(text);
753
766
  }
754
767
 
768
+ // src/resources.ts
769
+ var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
770
+ var DEFAULT_GATEWAY_BASE_URL = "https://gateway.usagetap.com";
771
+ function normalizedBaseUrl(value) {
772
+ return `${value.replace(/\/+$/, "")}/`;
773
+ }
774
+ function errorMessage(payload, status) {
775
+ if (payload && typeof payload === "object") {
776
+ const record = payload;
777
+ const error = record.error;
778
+ if (error && typeof error === "object") {
779
+ const message2 = error.message;
780
+ if (typeof message2 === "string" && message2) return message2;
781
+ }
782
+ const message = record.message;
783
+ if (typeof message === "string" && message) return message;
784
+ }
785
+ return `UsageTap request failed with HTTP ${status}`;
786
+ }
787
+ function errorCode(status) {
788
+ if (status === 401 || status === 403) return "USAGETAP_AUTH_ERROR";
789
+ if (status === 429) return "USAGETAP_RATE_LIMITED";
790
+ if (status >= 500) return "USAGETAP_SERVER_ERROR";
791
+ return "USAGETAP_BAD_REQUEST";
792
+ }
793
+ var ResourceTransport = class {
794
+ baseUrl;
795
+ apiKey;
796
+ fetchImpl;
797
+ defaultHeaders;
798
+ sdkVersion;
799
+ constructor(baseUrl, config) {
800
+ this.baseUrl = normalizedBaseUrl(baseUrl);
801
+ this.apiKey = config.apiKey;
802
+ this.fetchImpl = config.fetchImpl;
803
+ this.defaultHeaders = config.headers ?? {};
804
+ this.sdkVersion = config.sdkVersion;
805
+ }
806
+ async request(request) {
807
+ const body = request.body === void 0 ? void 0 : JSON.stringify(request.body);
808
+ const headers = {
809
+ ...this.defaultHeaders,
810
+ accept: request.response === "data" ? CANONICAL_MEDIA_TYPE : request.response === "ndjson" ? "application/x-ndjson" : "application/json",
811
+ authorization: `Bearer ${this.apiKey}`,
812
+ "x-usage-sdk": `js/${this.sdkVersion}`,
813
+ ...body ? { "content-type": "application/json" } : {},
814
+ ...request.options?.idempotencyKey ? { "idempotency-key": request.options.idempotencyKey } : {},
815
+ ...request.options?.headers
816
+ };
817
+ let response;
818
+ try {
819
+ response = await this.fetchImpl(
820
+ new URL(request.path.replace(/^\/+/, ""), this.baseUrl),
821
+ {
822
+ method: request.method,
823
+ headers,
824
+ body,
825
+ signal: request.options?.signal
826
+ }
827
+ );
828
+ } catch (error) {
829
+ throw new UsageTapError(
830
+ "USAGETAP_NETWORK_ERROR",
831
+ "Failed to reach UsageTap",
832
+ { retryable: true, cause: error }
833
+ );
834
+ }
835
+ const requestId = response.headers.get("x-request-id") ?? response.headers.get("x-usage-correlation-id") ?? void 0;
836
+ const text = await response.text();
837
+ let payload;
838
+ if (text && request.response !== "ndjson") {
839
+ try {
840
+ payload = JSON.parse(text);
841
+ } catch (error) {
842
+ throw new UsageTapError(
843
+ "USAGETAP_INVALID_RESPONSE",
844
+ "UsageTap returned invalid JSON",
845
+ { status: response.status, correlationId: requestId, cause: error }
846
+ );
847
+ }
848
+ }
849
+ if (!response.ok) {
850
+ throw new UsageTapError(
851
+ errorCode(response.status),
852
+ errorMessage(payload, response.status),
853
+ {
854
+ status: response.status,
855
+ retryable: response.status === 429 || response.status >= 500,
856
+ correlationId: requestId,
857
+ details: payload && typeof payload === "object" ? payload : void 0
858
+ }
859
+ );
860
+ }
861
+ if (request.response === "ndjson") {
862
+ if (!text.trim()) return [];
863
+ try {
864
+ return text.trim().split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
865
+ } catch (error) {
866
+ throw new UsageTapError(
867
+ "USAGETAP_INVALID_RESPONSE",
868
+ "UsageTap returned invalid NDJSON",
869
+ { status: response.status, correlationId: requestId, cause: error }
870
+ );
871
+ }
872
+ }
873
+ if (request.response === "data") {
874
+ if (!payload || typeof payload !== "object" || !("data" in payload)) {
875
+ throw new UsageTapError(
876
+ "USAGETAP_INVALID_RESPONSE",
877
+ "UsageTap response missing data",
878
+ { status: response.status, correlationId: requestId }
879
+ );
880
+ }
881
+ return payload.data;
882
+ }
883
+ if (payload === void 0) {
884
+ throw new UsageTapError(
885
+ "USAGETAP_INVALID_RESPONSE",
886
+ "UsageTap response was empty",
887
+ { status: response.status, correlationId: requestId }
888
+ );
889
+ }
890
+ return payload;
891
+ }
892
+ };
893
+ function resourceId(value, keys, label) {
894
+ const id = typeof value === "string" ? value : keys.map((key) => value[key]).find((candidate) => Boolean(candidate?.trim()));
895
+ if (!id?.trim()) {
896
+ throw new UsageTapError(
897
+ "USAGETAP_BAD_REQUEST",
898
+ `${label} requires a non-empty ID`
899
+ );
900
+ }
901
+ return id.trim();
902
+ }
903
+ function terminalSummary(status) {
904
+ return status === "COMPLETE" || status === "FAILED";
905
+ }
906
+ function terminalGatewayBatch(status) {
907
+ return ["completed", "failed", "expired", "cancelled"].includes(status);
908
+ }
909
+ function validateWaitOptions(options) {
910
+ const pollIntervalMs = options.pollIntervalMs ?? 1500;
911
+ const timeoutMs = options.timeoutMs ?? 3 * 6e4;
912
+ if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 0) {
913
+ throw new UsageTapError(
914
+ "USAGETAP_BAD_REQUEST",
915
+ "pollIntervalMs must be a non-negative number"
916
+ );
917
+ }
918
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
919
+ throw new UsageTapError(
920
+ "USAGETAP_BAD_REQUEST",
921
+ "timeoutMs must be a positive number"
922
+ );
923
+ }
924
+ return { pollIntervalMs, timeoutMs };
925
+ }
926
+ var SummarizationResource = class {
927
+ summaries;
928
+ batches;
929
+ profiles;
930
+ measurements;
931
+ transport;
932
+ constructor(config) {
933
+ this.transport = new ResourceTransport(config.apiBaseUrl, config);
934
+ this.summaries = {
935
+ create: (params, options) => this.transport.request({
936
+ method: "POST",
937
+ path: "/v1/compression/summaries",
938
+ body: params,
939
+ options,
940
+ response: "data"
941
+ }),
942
+ retrieve: (jobId, options) => this.transport.request({
943
+ method: "GET",
944
+ path: `/v1/compression/jobs/${encodeURIComponent(
945
+ resourceId(jobId, ["jobId"], "summaries.retrieve")
946
+ )}`,
947
+ options,
948
+ response: "data"
949
+ }),
950
+ wait: (job, options) => this.waitForSummary(job, options)
951
+ };
952
+ this.batches = {
953
+ create: (params, options) => this.transport.request({
954
+ method: "POST",
955
+ path: "/v1/compression/batches",
956
+ body: params,
957
+ options,
958
+ response: "data"
959
+ }),
960
+ retrieve: (batchId, options) => this.transport.request({
961
+ method: "GET",
962
+ path: `/v1/compression/batches/${encodeURIComponent(
963
+ resourceId(batchId, ["batchId"], "summarization.batches.retrieve")
964
+ )}`,
965
+ options,
966
+ response: "data"
967
+ }),
968
+ wait: (batch, options) => this.waitForBatch(batch, options)
969
+ };
970
+ this.profiles = {
971
+ retrieve: (profile, options) => this.transport.request({
972
+ method: "GET",
973
+ path: `/v1/compression/profiles/${encodeURIComponent(
974
+ resourceId(profile, [], "summarization.profiles.retrieve")
975
+ )}`,
976
+ options,
977
+ response: "data"
978
+ })
979
+ };
980
+ this.measurements = {
981
+ create: (params, options) => this.transport.request({
982
+ method: "POST",
983
+ path: "/v1/compression/measurements",
984
+ body: params,
985
+ options,
986
+ response: "data"
987
+ })
988
+ };
989
+ }
990
+ async waitForSummary(value, options = {}) {
991
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
992
+ const deadline = Date.now() + timeoutMs;
993
+ let job = typeof value === "string" ? await this.summaries.retrieve(value, options) : value;
994
+ while (!terminalSummary(job.status)) {
995
+ if (Date.now() >= deadline) {
996
+ throw new UsageTapError(
997
+ "USAGETAP_RETRY_EXHAUSTED",
998
+ `Summarization job ${job.jobId} did not finish before timeout`,
999
+ { retryable: true }
1000
+ );
1001
+ }
1002
+ await sleep(pollIntervalMs, options.signal);
1003
+ job = await this.summaries.retrieve(job.jobId, options);
1004
+ }
1005
+ return job;
1006
+ }
1007
+ async waitForBatch(value, options = {}) {
1008
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
1009
+ const deadline = Date.now() + timeoutMs;
1010
+ let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
1011
+ while (!terminalSummary(batch.status)) {
1012
+ if (Date.now() >= deadline) {
1013
+ throw new UsageTapError(
1014
+ "USAGETAP_RETRY_EXHAUSTED",
1015
+ `Summarization batch ${batch.batchId} did not finish before timeout`,
1016
+ { retryable: true }
1017
+ );
1018
+ }
1019
+ await sleep(pollIntervalMs, options.signal);
1020
+ batch = await this.batches.retrieve(batch.batchId, options);
1021
+ }
1022
+ return batch;
1023
+ }
1024
+ };
1025
+ var GatewayResource = class {
1026
+ chat;
1027
+ models;
1028
+ batches;
1029
+ transport;
1030
+ idempotencyGenerator;
1031
+ constructor(config) {
1032
+ this.transport = new ResourceTransport(
1033
+ config.gatewayBaseUrl ?? DEFAULT_GATEWAY_BASE_URL,
1034
+ config
1035
+ );
1036
+ this.idempotencyGenerator = config.idempotencyGenerator ?? createIdempotencyKey;
1037
+ this.chat = {
1038
+ completions: {
1039
+ create: (params, options) => this.transport.request({
1040
+ method: "POST",
1041
+ path: "/v1/chat/completions",
1042
+ body: params,
1043
+ options,
1044
+ response: "json"
1045
+ })
1046
+ }
1047
+ };
1048
+ this.models = {
1049
+ list: (options) => this.transport.request({
1050
+ method: "GET",
1051
+ path: "/v1/models",
1052
+ options,
1053
+ response: "json"
1054
+ })
1055
+ };
1056
+ this.batches = {
1057
+ create: (params, options = {}) => this.transport.request({
1058
+ method: "POST",
1059
+ path: "/v1/batches",
1060
+ body: params,
1061
+ options: {
1062
+ ...options,
1063
+ idempotencyKey: options.idempotencyKey ?? this.idempotencyGenerator()
1064
+ },
1065
+ response: "json"
1066
+ }),
1067
+ retrieve: (batchId, options) => this.transport.request({
1068
+ method: "GET",
1069
+ path: `/v1/batches/${encodeURIComponent(
1070
+ resourceId(batchId, ["id"], "gateway.batches.retrieve")
1071
+ )}`,
1072
+ options,
1073
+ response: "json"
1074
+ }),
1075
+ wait: (batch, options) => this.waitForBatch(batch, options),
1076
+ cancel: (batchId, options) => this.transport.request({
1077
+ method: "POST",
1078
+ path: `/v1/batches/${encodeURIComponent(
1079
+ resourceId(batchId, ["id"], "gateway.batches.cancel")
1080
+ )}/cancel`,
1081
+ options,
1082
+ response: "json"
1083
+ }),
1084
+ results: (batchId, options) => this.transport.request({
1085
+ method: "GET",
1086
+ path: `/v1/batches/${encodeURIComponent(
1087
+ resourceId(batchId, ["id"], "gateway.batches.results")
1088
+ )}/results`,
1089
+ options,
1090
+ response: "ndjson"
1091
+ })
1092
+ };
1093
+ }
1094
+ async waitForBatch(value, options = {}) {
1095
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
1096
+ const deadline = Date.now() + timeoutMs;
1097
+ let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
1098
+ while (!terminalGatewayBatch(batch.status)) {
1099
+ if (Date.now() >= deadline) {
1100
+ throw new UsageTapError(
1101
+ "USAGETAP_RETRY_EXHAUSTED",
1102
+ `Gateway batch ${batch.id} did not finish before timeout`,
1103
+ { retryable: true }
1104
+ );
1105
+ }
1106
+ await sleep(pollIntervalMs, options.signal);
1107
+ batch = await this.batches.retrieve(batch.id, options);
1108
+ }
1109
+ return batch;
1110
+ }
1111
+ };
1112
+
755
1113
  // src/client.ts
756
1114
  var CALL_BEGIN_PATH = "call_begin";
757
1115
  var CALL_END_PATH = "call_end";
758
1116
  var COMPRESS_PROMPT_PATH = "compress_prompt";
1117
+ var SAMPLES_PATH = "samples";
1118
+ var SAMPLING_SETTINGS_PATH = "sampling/settings";
1119
+ var SAMPLING_DECIDE_PATH = "sampling/decide";
759
1120
  var CHECK_USAGE_PATH = "customers/{customerId}/usage";
760
1121
  var CREATE_CUSTOMER_PATH = "customers";
761
1122
  var CHANGE_PLAN_PATH = "customers/{customerId}/change_plan";
@@ -766,11 +1127,16 @@ var CORRELATION_HEADER = "x-usage-correlation-id";
766
1127
  var IDEMPOTENCY_HEADER = "idempotency-key";
767
1128
  var SDK_HEADER = "x-usage-sdk";
768
1129
  var USER_AGENT = "UsageTapClient";
769
- var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
1130
+ var CANONICAL_MEDIA_TYPE2 = "application/vnd.usagetap.v1+json";
770
1131
  var DEFAULT_BASE_URL = "https://api.usagetap.com";
771
- var SDK_VERSION = "1.3.2" ;
1132
+ var DEFAULT_RUN_INACTIVITY_MS = 60 * 60 * 1e3;
1133
+ var SDK_VERSION = "1.4.0" ;
772
1134
  var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
773
1135
  var UsageTapClient = class {
1136
+ /** OpenAI-compatible chat, model, and native batch operations. */
1137
+ gateway;
1138
+ /** Published-profile context summarization operations. */
1139
+ summarization;
774
1140
  apiKey;
775
1141
  baseUrl;
776
1142
  fetchImpl;
@@ -795,6 +1161,11 @@ var UsageTapClient = class {
795
1161
  usageTapCompressionMessagesEndpoint;
796
1162
  usageTapCompressionModel;
797
1163
  usageTapCompressionAggressiveness;
1164
+ sampling;
1165
+ samplingSettingsCacheMs;
1166
+ circuitBreaker;
1167
+ circuitBreakerRuns = /* @__PURE__ */ new Map();
1168
+ samplingSettingsCache;
798
1169
  constructor(options = {}) {
799
1170
  const apiKey = options.apiKey?.trim() || readEnvironmentVariable("USAGETAP_API_KEY");
800
1171
  const baseUrl = options.baseUrl?.trim() || readEnvironmentVariable("USAGETAP_BASE_URL") || DEFAULT_BASE_URL;
@@ -817,10 +1188,21 @@ var UsageTapClient = class {
817
1188
  "A global fetch implementation was not found. Pass fetchImpl in UsageTapClientOptions."
818
1189
  );
819
1190
  }
820
- const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
821
- this.baseUrl = new URL(normalizedBaseUrl);
1191
+ const normalizedBaseUrl2 = normalizeBaseUrl(baseUrl);
1192
+ this.baseUrl = new URL(normalizedBaseUrl2);
822
1193
  this.apiKey = apiKey;
823
1194
  this.fetchImpl = wrapFetchImplementation(fetchCandidate, !options.fetchImpl);
1195
+ const resourceConfig = {
1196
+ apiKey,
1197
+ apiBaseUrl: normalizedBaseUrl2,
1198
+ gatewayBaseUrl: options.gatewayBaseUrl?.trim() || readEnvironmentVariable("USAGETAP_GATEWAY_URL"),
1199
+ fetchImpl: this.fetchImpl,
1200
+ headers: options.headers,
1201
+ sdkVersion: SDK_VERSION,
1202
+ idempotencyGenerator: options.idempotencyGenerator
1203
+ };
1204
+ this.gateway = new GatewayResource(resourceConfig);
1205
+ this.summarization = new SummarizationResource(resourceConfig);
824
1206
  this.defaultFeature = options.defaultFeature;
825
1207
  this.defaultTags = options.defaultTags?.length ? dedupeStrings(options.defaultTags) : void 0;
826
1208
  this.defaultHeaders = options.headers ? normalizeHeaderDictionary(options.headers) : {};
@@ -842,11 +1224,122 @@ var UsageTapClient = class {
842
1224
  this.usageTapCompressionMessagesEndpoint = options.usageTapCompressionMessagesEndpoint;
843
1225
  this.usageTapCompressionModel = options.usageTapCompressionModel;
844
1226
  this.usageTapCompressionAggressiveness = options.usageTapCompressionAggressiveness;
1227
+ this.sampling = options.sampling;
1228
+ this.samplingSettingsCacheMs = Number.isFinite(options.samplingSettingsCacheMs) ? Math.max(0, Number(options.samplingSettingsCacheMs)) : 5 * 60 * 1e3;
1229
+ if (options.circuitBreaker) {
1230
+ const maxCallsPerRun = options.circuitBreaker.maxCallsPerRun;
1231
+ if (!Number.isInteger(maxCallsPerRun) || maxCallsPerRun < 1) {
1232
+ throw new UsageTapError(
1233
+ "USAGETAP_BAD_REQUEST",
1234
+ "circuitBreaker.maxCallsPerRun must be a positive integer"
1235
+ );
1236
+ }
1237
+ const runInactivityMs = options.circuitBreaker.runInactivityMs ?? DEFAULT_RUN_INACTIVITY_MS;
1238
+ if (!Number.isFinite(runInactivityMs) || runInactivityMs < 1) {
1239
+ throw new UsageTapError(
1240
+ "USAGETAP_BAD_REQUEST",
1241
+ "circuitBreaker.runInactivityMs must be a positive number"
1242
+ );
1243
+ }
1244
+ this.circuitBreaker = {
1245
+ maxCallsPerRun,
1246
+ runInactivityMs
1247
+ };
1248
+ }
1249
+ }
1250
+ shouldSample(request, policy = this.sampling || void 0) {
1251
+ if (!policy) return false;
1252
+ const rate = Math.min(1, Math.max(0, Number(policy.rate) || 0));
1253
+ if (rate <= 0) return false;
1254
+ const customerId = request.customerId?.trim();
1255
+ if (customerId && policy.customers?.exclude?.includes(customerId)) return false;
1256
+ const feature = request.feature?.trim();
1257
+ if (feature && policy.features?.exclude?.includes(feature)) return false;
1258
+ const included = policy.features?.include?.filter(Boolean) ?? [];
1259
+ if (included.length > 0 && (!feature || !included.includes(feature))) return false;
1260
+ const minimum = Math.max(0, Math.round(policy.minInputTokens ?? 0));
1261
+ if (minimum > 0 && estimatePromptTokens(request.input) < minimum) return false;
1262
+ return (policy.random ?? Math.random)() < rate;
1263
+ }
1264
+ async getSamplingSettings(options = {}) {
1265
+ const now = Date.now();
1266
+ if (!options.forceRefresh && this.samplingSettingsCache && this.samplingSettingsCache.expiresAtMs > now) {
1267
+ return {
1268
+ result: { status: "ACCEPTED", code: "SAMPLING_SETTINGS_CACHED" },
1269
+ data: this.samplingSettingsCache.settings,
1270
+ correlationId: options.correlationId ?? "local-cache"
1271
+ };
1272
+ }
1273
+ const response = await this.requestGet(
1274
+ SAMPLING_SETTINGS_PATH,
1275
+ {
1276
+ signal: options.signal,
1277
+ headers: options.headers,
1278
+ retries: options.retries,
1279
+ correlationId: options.correlationId
1280
+ }
1281
+ );
1282
+ const serverCacheMs = Math.max(0, Number(response.data.cacheSeconds) || 0) * 1e3;
1283
+ const cacheMs = Math.min(this.samplingSettingsCacheMs, serverCacheMs);
1284
+ this.samplingSettingsCache = {
1285
+ settings: response.data,
1286
+ expiresAtMs: now + cacheMs
1287
+ };
1288
+ return response;
1289
+ }
1290
+ async shouldSampleAsync(request, policy) {
1291
+ if (policy) return this.shouldSample(request, policy);
1292
+ if (this.sampling === false) return false;
1293
+ if (this.sampling) return this.shouldSample(request, this.sampling);
1294
+ try {
1295
+ const settings = await this.getSamplingSettings();
1296
+ return this.shouldSample(request, settings.data);
1297
+ } catch {
1298
+ return false;
1299
+ }
1300
+ }
1301
+ async decideSample(request, options = {}) {
1302
+ const hasTokens = Number.isFinite(request.inputTokens) && Number(request.inputTokens) >= 0;
1303
+ const hasCharacters = Number.isFinite(request.inputCharacters) && Number(request.inputCharacters) >= 0;
1304
+ if (!hasTokens && !hasCharacters) {
1305
+ throw new UsageTapError(
1306
+ "USAGETAP_BAD_REQUEST",
1307
+ "decideSample requires inputTokens or inputCharacters"
1308
+ );
1309
+ }
1310
+ return this.request(
1311
+ SAMPLING_DECIDE_PATH,
1312
+ request,
1313
+ options
1314
+ );
1315
+ }
1316
+ async captureSample(request, options = {}) {
1317
+ if (!request || request.input === void 0) {
1318
+ throw new UsageTapError(
1319
+ "USAGETAP_BAD_REQUEST",
1320
+ "captureSample requires input"
1321
+ );
1322
+ }
1323
+ if (!request.provider?.trim()) {
1324
+ throw new UsageTapError(
1325
+ "USAGETAP_BAD_REQUEST",
1326
+ "captureSample requires provider"
1327
+ );
1328
+ }
1329
+ const sampleId = request.sampleId?.trim() || this.idempotencyGenerator();
1330
+ return this.request(
1331
+ SAMPLES_PATH,
1332
+ { ...request, sampleId },
1333
+ { ...options, idempotencyKey: sampleId }
1334
+ );
845
1335
  }
846
1336
  async beginCall(request, options = {}) {
847
1337
  const idempotencyKey = request.idempotencyKey ?? request.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
1338
+ this.reserveRunCall(request, idempotencyKey);
1339
+ const apiRequest = { ...request };
1340
+ delete apiRequest.runId;
848
1341
  const payload = {
849
- ...request,
1342
+ ...apiRequest,
850
1343
  feature: request.feature ?? this.defaultFeature,
851
1344
  tags: this.mergeTags(request.tags)
852
1345
  };
@@ -864,6 +1357,28 @@ var UsageTapClient = class {
864
1357
  );
865
1358
  return response;
866
1359
  }
1360
+ /**
1361
+ * Inspect a configured run circuit breaker without consuming another call.
1362
+ */
1363
+ canRunContinue(request) {
1364
+ const identity = this.resolveRunIdentity(request);
1365
+ if (!identity || !this.circuitBreaker) {
1366
+ throw new UsageTapError(
1367
+ "USAGETAP_BAD_REQUEST",
1368
+ "canRunContinue requires circuitBreaker configuration and a non-empty runId"
1369
+ );
1370
+ }
1371
+ this.expireInactiveRuns();
1372
+ const calls = this.circuitBreakerRuns.get(identity.key)?.calls ?? 0;
1373
+ return this.createCircuitBreakerDecision(identity.customerId, identity.runId, calls);
1374
+ }
1375
+ /**
1376
+ * Release local state after a workflow finishes. Returns true when state existed.
1377
+ */
1378
+ resetRun(request) {
1379
+ const identity = this.resolveRunIdentity(request);
1380
+ return identity ? this.circuitBreakerRuns.delete(identity.key) : false;
1381
+ }
867
1382
  async promptCompress(request, options = {}) {
868
1383
  if (!request?.callId) {
869
1384
  throw new UsageTapError(
@@ -961,6 +1476,10 @@ var UsageTapClient = class {
961
1476
  usageTapCompressionMessagesEndpoint: this.usageTapCompressionMessagesEndpoint,
962
1477
  aggressiveness: options.aggressiveness ?? this.aggressiveness,
963
1478
  usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
1479
+ mode: options.mode,
1480
+ latencyBudgetMs: options.latencyBudgetMs,
1481
+ compactEmptyUserMessages: options.compactEmptyUserMessages,
1482
+ compactDuplicateUserTextParts: options.compactDuplicateUserTextParts,
964
1483
  fetchImpl: this.fetchImpl,
965
1484
  signal: options.signal,
966
1485
  failOpen: options.failOpen
@@ -1002,11 +1521,17 @@ var UsageTapClient = class {
1002
1521
  callId: request.callId,
1003
1522
  feature: feature ?? this.defaultFeature,
1004
1523
  tags: tags ?? this.defaultTags,
1524
+ providerUsed: request.providerUsed,
1005
1525
  modelUsed: request.modelUsed,
1526
+ reasoningEffort: request.reasoningEffort,
1527
+ reasoningEffortSource: request.reasoningEffortSource,
1528
+ reasoningMode: request.reasoningMode,
1529
+ reasoningBudgetTokens: request.reasoningBudgetTokens,
1006
1530
  metrics: {
1007
1531
  inputTokens: request.inputTokens,
1008
1532
  responseTokens: request.responseTokens,
1009
1533
  cachedInputTokens: request.cachedInputTokens,
1534
+ cacheWriteInputTokens: request.cacheWriteInputTokens,
1010
1535
  reasoningTokens: request.reasoningTokens,
1011
1536
  searches: request.searches,
1012
1537
  audioSeconds: request.audioSeconds,
@@ -1114,6 +1639,15 @@ var UsageTapClient = class {
1114
1639
  meterSlot: request.meterSlot,
1115
1640
  amount: request.amount
1116
1641
  };
1642
+ if (request.customerUserId) {
1643
+ payload.customerUserId = request.customerUserId;
1644
+ }
1645
+ if (request.customerUserName) {
1646
+ payload.customerUserName = request.customerUserName;
1647
+ }
1648
+ if (request.customerUserEmail) {
1649
+ payload.customerUserEmail = request.customerUserEmail;
1650
+ }
1117
1651
  if (request.feature) {
1118
1652
  payload.feature = request.feature;
1119
1653
  }
@@ -1150,6 +1684,11 @@ var UsageTapClient = class {
1150
1684
  const beginPayload = idempotencyKey ? { ...beginRequest, idempotencyKey, idempotency: idempotencyKey } : { ...beginRequest };
1151
1685
  const beginResponse = await this.beginCall(beginPayload, options);
1152
1686
  let usage = {};
1687
+ const pricingMode = beginResponse.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
1688
+ if (pricingMode) {
1689
+ usage.pricingMode = pricingMode;
1690
+ usage.batch = pricingMode === "batch";
1691
+ }
1153
1692
  const initialStripeCustomerId = typeof beginResponse.data.stripeCustomerId === "string" ? beginResponse.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
1154
1693
  if (initialStripeCustomerId) {
1155
1694
  usage = { ...usage, stripeCustomerId: initialStripeCustomerId };
@@ -1217,17 +1756,86 @@ var UsageTapClient = class {
1217
1756
  toPromptCompressionTelemetry(result) {
1218
1757
  return {
1219
1758
  provider: result.provider,
1220
- originalCharacters: result.originalCharacters,
1221
- compressedCharacters: result.compressedCharacters,
1222
- savedCharacters: result.savedCharacters,
1223
1759
  originalTokens: result.originalTokens,
1224
1760
  compressedTokens: result.compressedTokens,
1225
1761
  savedTokens: result.savedTokens,
1226
1762
  tokenSavingsRatio: result.tokenSavingsRatio,
1227
- savingsRatio: result.savingsRatio,
1228
1763
  techniques: result.techniques
1229
1764
  };
1230
1765
  }
1766
+ reserveRunCall(request, idempotencyKey) {
1767
+ const identity = this.resolveRunIdentity(request);
1768
+ if (!identity || !this.circuitBreaker) return;
1769
+ this.expireInactiveRuns();
1770
+ const now = Date.now();
1771
+ const state = this.circuitBreakerRuns.get(identity.key) ?? {
1772
+ calls: 0,
1773
+ reservationKeys: /* @__PURE__ */ new Set(),
1774
+ lastSeenAtMs: now
1775
+ };
1776
+ const reservationKey = idempotencyKey ?? this.idempotencyGenerator();
1777
+ state.lastSeenAtMs = now;
1778
+ if (state.reservationKeys.has(reservationKey)) {
1779
+ this.circuitBreakerRuns.set(identity.key, state);
1780
+ return;
1781
+ }
1782
+ const decision = this.createCircuitBreakerDecision(
1783
+ identity.customerId,
1784
+ identity.runId,
1785
+ state.calls
1786
+ );
1787
+ if (!decision.allowed) {
1788
+ throw new UsageTapError(
1789
+ "USAGETAP_CIRCUIT_OPEN",
1790
+ `Run ${identity.runId} reached its ${decision.limit}-call circuit-breaker limit`,
1791
+ {
1792
+ details: {
1793
+ reason: decision.reason,
1794
+ customerId: identity.customerId,
1795
+ runId: identity.runId,
1796
+ calls: decision.calls,
1797
+ limit: decision.limit,
1798
+ remaining: decision.remaining
1799
+ }
1800
+ }
1801
+ );
1802
+ }
1803
+ state.calls += 1;
1804
+ state.reservationKeys.add(reservationKey);
1805
+ this.circuitBreakerRuns.set(identity.key, state);
1806
+ }
1807
+ resolveRunIdentity(request) {
1808
+ const customerId = request.customerId?.trim();
1809
+ const runId = request.runId?.trim();
1810
+ if (!customerId || !runId) return void 0;
1811
+ return {
1812
+ key: `${customerId}\0${runId}`,
1813
+ customerId,
1814
+ runId
1815
+ };
1816
+ }
1817
+ createCircuitBreakerDecision(customerId, runId, calls) {
1818
+ const limit = this.circuitBreaker?.maxCallsPerRun ?? 0;
1819
+ const allowed = calls < limit;
1820
+ return {
1821
+ allowed,
1822
+ ...allowed ? {} : { reason: "max_calls_per_run" },
1823
+ customerId,
1824
+ runId,
1825
+ calls,
1826
+ limit,
1827
+ remaining: Math.max(0, limit - calls)
1828
+ };
1829
+ }
1830
+ expireInactiveRuns() {
1831
+ if (!this.circuitBreaker || this.circuitBreakerRuns.size === 0) return;
1832
+ const expiredBefore = Date.now() - this.circuitBreaker.runInactivityMs;
1833
+ for (const [key, state] of this.circuitBreakerRuns) {
1834
+ if (state.lastSeenAtMs < expiredBefore) {
1835
+ this.circuitBreakerRuns.delete(key);
1836
+ }
1837
+ }
1838
+ }
1231
1839
  async request(path, payload, options) {
1232
1840
  const url = new URL(path, this.baseUrl).toString();
1233
1841
  const body = payload !== void 0 ? JSON.stringify(payload) : void 0;
@@ -1415,7 +2023,7 @@ var UsageTapClient = class {
1415
2023
  ...this.defaultHeaders,
1416
2024
  [SDK_HEADER]: `js/${SDK_VERSION}`,
1417
2025
  "content-type": "application/json",
1418
- accept: CANONICAL_MEDIA_TYPE
2026
+ accept: CANONICAL_MEDIA_TYPE2
1419
2027
  };
1420
2028
  if (!HAS_WINDOW) {
1421
2029
  headers["user-agent"] = `${USER_AGENT}/${SDK_VERSION}`;
@@ -1616,6 +2224,82 @@ var AnthropicPromptCompressionStats = class {
1616
2224
  }
1617
2225
  };
1618
2226
  var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
2227
+ function withSampling(client, options = {}) {
2228
+ if (!client?.messages || typeof client.messages.create !== "function" || !options) {
2229
+ throw new UsageTapError(
2230
+ "USAGETAP_BAD_REQUEST",
2231
+ "withSampling requires an Anthropic client and sampling options"
2232
+ );
2233
+ }
2234
+ const { apiKey, usageTapClient, ...policy } = options;
2235
+ const localPolicy = typeof policy.rate === "number" ? policy : void 0;
2236
+ const usageTap = usageTapClient ?? new UsageTapClient({ apiKey, sampling: localPolicy });
2237
+ const originalCreate = client.messages.create.bind(client.messages);
2238
+ const create = async (params, requestOptions) => {
2239
+ const { usageTap: callContext = {}, ...providerOptions } = requestOptions ?? {};
2240
+ const decision = params.stream === true ? Promise.resolve(false) : usageTap.shouldSampleAsync({
2241
+ customerId: callContext.customerId,
2242
+ feature: callContext.feature,
2243
+ input: params
2244
+ }, localPolicy);
2245
+ const startedAt = Date.now();
2246
+ try {
2247
+ const response = await originalCreate(
2248
+ params,
2249
+ Object.keys(providerOptions).length ? providerOptions : void 0
2250
+ );
2251
+ const selected = await decision;
2252
+ if (selected) {
2253
+ const record = isObjectRecord(response) ? response : {};
2254
+ await usageTap.captureSample({
2255
+ customerId: callContext.customerId,
2256
+ feature: callContext.feature,
2257
+ environment: callContext.environment,
2258
+ tags: callContext.tags,
2259
+ provider: "anthropic",
2260
+ model: readString(record.model) ?? readString(params.model),
2261
+ input: params,
2262
+ output: response,
2263
+ usage: record.usage,
2264
+ latencyMs: Date.now() - startedAt
2265
+ }).catch(() => void 0);
2266
+ }
2267
+ return response;
2268
+ } catch (error) {
2269
+ const selected = await decision;
2270
+ if (selected) {
2271
+ await usageTap.captureSample({
2272
+ customerId: callContext.customerId,
2273
+ feature: callContext.feature,
2274
+ environment: callContext.environment,
2275
+ tags: callContext.tags,
2276
+ provider: "anthropic",
2277
+ model: readString(params.model),
2278
+ input: params,
2279
+ latencyMs: Date.now() - startedAt,
2280
+ error: error instanceof Error ? { name: error.name, message: error.message } : { message: String(error) }
2281
+ }).catch(() => void 0);
2282
+ }
2283
+ throw error;
2284
+ }
2285
+ };
2286
+ const messages = new Proxy(client.messages, {
2287
+ get(target, prop, receiver) {
2288
+ if (prop === "create") return create;
2289
+ return safeReflectGet(target, prop, receiver);
2290
+ }
2291
+ });
2292
+ return new Proxy(client, {
2293
+ get(target, prop, receiver) {
2294
+ if (prop === "messages") return messages;
2295
+ if (prop === "unwrap") return () => target;
2296
+ return safeReflectGet(target, prop, receiver);
2297
+ }
2298
+ });
2299
+ }
2300
+ function safeReflectGet(target, prop, receiver) {
2301
+ return Reflect.get(target, prop, receiver);
2302
+ }
1619
2303
  function withCompression(client, options = {}) {
1620
2304
  if (!client?.messages || typeof client.messages.create !== "function") {
1621
2305
  throw new UsageTapError(
@@ -1626,11 +2310,13 @@ function withCompression(client, options = {}) {
1626
2310
  const {
1627
2311
  apiKey,
1628
2312
  usageTapClient,
2313
+ sampling,
1629
2314
  ...compressionOverrides
1630
2315
  } = options;
1631
2316
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
1632
2317
  const compression = {
1633
2318
  provider: "usagetap",
2319
+ roles: { user: { enabled: true, aggressiveness: 0.2 } },
1634
2320
  minContextTokens: DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS,
1635
2321
  ...compressionOverrides
1636
2322
  };
@@ -1644,13 +2330,18 @@ function withCompression(client, options = {}) {
1644
2330
  return Reflect.get(target, prop, receiver);
1645
2331
  }
1646
2332
  });
1647
- return new Proxy(client, {
2333
+ const compressed = new Proxy(client, {
1648
2334
  get(target, prop, receiver) {
1649
2335
  if (prop === "messages") return proxiedMessages;
1650
2336
  if (prop === "unwrap") return () => target;
1651
2337
  return Reflect.get(target, prop, receiver);
1652
2338
  }
1653
2339
  });
2340
+ return sampling ? withSampling(compressed, {
2341
+ ...sampling === true ? {} : sampling,
2342
+ apiKey,
2343
+ usageTapClient
2344
+ }) : compressed;
1654
2345
  }
1655
2346
  function withMetering(client, customer) {
1656
2347
  const config = typeof customer === "string" ? { customerId: customer } : customer;
@@ -1665,6 +2356,7 @@ function withMetering(client, customer) {
1665
2356
  usageTapClient,
1666
2357
  applyVendorHints,
1667
2358
  promptCompression,
2359
+ sampling,
1668
2360
  ...defaultContext
1669
2361
  } = config;
1670
2362
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
@@ -1672,7 +2364,8 @@ function withMetering(client, customer) {
1672
2364
  return wrapAnthropic(client, usageTap, {
1673
2365
  defaultContext,
1674
2366
  applyVendorHints,
1675
- promptCompression: normalizedCompression
2367
+ promptCompression: normalizedCompression,
2368
+ sampling
1676
2369
  });
1677
2370
  }
1678
2371
  function wrapAnthropic(client, usageTap, options = {}) {
@@ -1685,6 +2378,7 @@ function wrapAnthropic(client, usageTap, options = {}) {
1685
2378
  const defaultContext = options.defaultContext;
1686
2379
  const applyVendorHints = options.applyVendorHints !== false;
1687
2380
  const defaultPromptCompression = normalizePromptCompressionOptions(options.promptCompression);
2381
+ const defaultSampling = normalizeMeteredAnthropicSampling(options.sampling);
1688
2382
  const promptCompressionStats = new AnthropicPromptCompressionStats();
1689
2383
  const proxiedMessages = createMessagesProxy(
1690
2384
  client.messages,
@@ -1692,7 +2386,8 @@ function wrapAnthropic(client, usageTap, options = {}) {
1692
2386
  defaultContext,
1693
2387
  applyVendorHints,
1694
2388
  defaultPromptCompression,
1695
- promptCompressionStats
2389
+ promptCompressionStats,
2390
+ defaultSampling
1696
2391
  );
1697
2392
  const handler = {
1698
2393
  get(target, prop, receiver) {
@@ -1710,7 +2405,7 @@ function wrapAnthropic(client, usageTap, options = {}) {
1710
2405
  };
1711
2406
  return new Proxy(client, handler);
1712
2407
  }
1713
- function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2408
+ function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling) {
1714
2409
  if (typeof resource.create !== "function") {
1715
2410
  throw new UsageTapError("USAGETAP_BAD_REQUEST", "wrapAnthropic requires client.messages.create");
1716
2411
  }
@@ -1733,7 +2428,8 @@ function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHint
1733
2428
  defaultContext,
1734
2429
  applyVendorHints,
1735
2430
  defaultPromptCompression,
1736
- promptCompressionStats
2431
+ promptCompressionStats,
2432
+ defaultSampling
1737
2433
  });
1738
2434
  });
1739
2435
  const handler = {
@@ -1747,13 +2443,26 @@ function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHint
1747
2443
  return new Proxy(resource, handler);
1748
2444
  }
1749
2445
  async function invokeMessagesCreate(args) {
1750
- const beginRequest = resolveBeginRequest(args.defaultContext, args.usageContext);
2446
+ const beginRequest = withRuntimeCompressionContext(
2447
+ resolveBeginRequest(args.defaultContext, args.usageContext),
2448
+ "anthropic",
2449
+ String(args.params.model)
2450
+ );
1751
2451
  const begin = await args.usageTap.beginCall(
1752
2452
  beginRequest,
1753
2453
  beginCallOptions(args.withUsage)
1754
2454
  );
1755
2455
  const state = createCallState(beginRequest, begin);
1756
2456
  const ctx = createUsageContext(state);
2457
+ const sampleStartedAt = Date.now();
2458
+ const wantsStream = isStreamingRequest(args.params);
2459
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredAnthropicSampleDecision({
2460
+ usageTap: args.usageTap,
2461
+ sampling: args.defaultSampling,
2462
+ beginRequest,
2463
+ input: args.params
2464
+ });
2465
+ let providerCompleted = false;
1757
2466
  try {
1758
2467
  const hintedParams = args.applyVendorHints ? applyAnthropicVendorHints(args.params, ctx.begin.data.vendorHints) : args.params;
1759
2468
  const finalParams = await compressAnthropicParamsForCall({
@@ -1766,8 +2475,10 @@ async function invokeMessagesCreate(args) {
1766
2475
  withUsage: args.withUsage,
1767
2476
  operation: "messages.create"
1768
2477
  });
2478
+ ctx.setUsage(anthropicRequestExecutionMetadata(finalParams));
1769
2479
  const request = attachCorrelationHeader(args.requestOptions, ctx.begin.correlationId);
1770
2480
  const response = await args.originalCreate(finalParams, request);
2481
+ providerCompleted = true;
1771
2482
  if (isStreamingRequest(finalParams)) {
1772
2483
  ensureAsyncIterable(response, "messages.create");
1773
2484
  return wrapAnthropicStreamForUsageTap(
@@ -1779,6 +2490,15 @@ async function invokeMessagesCreate(args) {
1779
2490
  );
1780
2491
  }
1781
2492
  inferAnthropicUsage(response, readString(finalParams.model), ctx);
2493
+ await captureMeteredAnthropicSample({
2494
+ usageTap: args.usageTap,
2495
+ sampling: args.defaultSampling,
2496
+ decision: sampleDecision,
2497
+ state,
2498
+ input: args.params,
2499
+ response,
2500
+ startedAt: sampleStartedAt
2501
+ });
1782
2502
  await finalizeCall(state, args.usageTap, args.withUsage);
1783
2503
  return response;
1784
2504
  } catch (error) {
@@ -1788,24 +2508,145 @@ async function invokeMessagesCreate(args) {
1788
2508
  message: error instanceof Error ? error.message : String(error)
1789
2509
  };
1790
2510
  }
2511
+ if (!providerCompleted) {
2512
+ await captureMeteredAnthropicSample({
2513
+ usageTap: args.usageTap,
2514
+ sampling: args.defaultSampling,
2515
+ decision: sampleDecision,
2516
+ state,
2517
+ input: args.params,
2518
+ error,
2519
+ startedAt: sampleStartedAt
2520
+ });
2521
+ }
1791
2522
  await finalizeCall(state, args.usageTap, args.withUsage);
1792
2523
  throw error;
1793
2524
  }
1794
2525
  }
2526
+ function normalizeMeteredAnthropicSampling(options) {
2527
+ if (!options) return void 0;
2528
+ if (options === true) return {};
2529
+ return {
2530
+ policy: typeof options.rate === "number" ? options : void 0
2531
+ };
2532
+ }
2533
+ function startMeteredAnthropicSampleDecision({
2534
+ usageTap,
2535
+ sampling,
2536
+ beginRequest,
2537
+ input
2538
+ }) {
2539
+ if (!sampling) return Promise.resolve(false);
2540
+ return usageTap.shouldSampleAsync(
2541
+ {
2542
+ customerId: beginRequest.customerId,
2543
+ feature: beginRequest.feature,
2544
+ input
2545
+ },
2546
+ sampling.policy
2547
+ );
2548
+ }
2549
+ async function captureMeteredAnthropicSample({
2550
+ usageTap,
2551
+ sampling,
2552
+ decision,
2553
+ state,
2554
+ input,
2555
+ response,
2556
+ error,
2557
+ startedAt
2558
+ }) {
2559
+ if (!sampling) return;
2560
+ let selected = false;
2561
+ try {
2562
+ selected = await decision;
2563
+ } catch {
2564
+ return;
2565
+ }
2566
+ if (!selected) return;
2567
+ const record = isObjectRecord(response) ? response : {};
2568
+ await usageTap.captureSample({
2569
+ sampleId: state.begin.data.callId,
2570
+ callId: state.begin.data.callId,
2571
+ customerId: state.beginRequest.customerId,
2572
+ feature: state.beginRequest.feature,
2573
+ tags: state.beginRequest.tags,
2574
+ provider: "anthropic",
2575
+ model: readString(record.model) ?? readString(input.model),
2576
+ input,
2577
+ ...response === void 0 ? {} : { output: response },
2578
+ usage: record.usage,
2579
+ latencyMs: Date.now() - startedAt,
2580
+ ...error === void 0 ? {} : {
2581
+ error: error instanceof Error ? { name: error.name, message: error.message } : { message: String(error) }
2582
+ }
2583
+ }).catch(() => void 0);
2584
+ }
1795
2585
  async function compressAnthropicParamsForCall(args) {
1796
- const compression = resolveEffectivePromptCompressionOptions(
2586
+ let compression = resolveEffectivePromptCompressionOptions(
1797
2587
  args.defaultPromptCompression,
1798
2588
  args.callPromptCompression
1799
2589
  );
2590
+ const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
2591
+ const policyDriven = !compression && Boolean(runtimePolicy);
2592
+ if (policyDriven && runtimePolicy) {
2593
+ if (!runtimePolicy.selected) {
2594
+ args.ctx.setUsage({
2595
+ runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
2596
+ });
2597
+ return args.params;
2598
+ }
2599
+ compression = anthropicPolicyCompressionOptions(runtimePolicy);
2600
+ if (runtimePolicy.mode === "SHADOW") {
2601
+ const startedAt2 = Date.now();
2602
+ void compressAnthropicParams(
2603
+ args.params,
2604
+ args.usageTap,
2605
+ compression,
2606
+ args.withUsage?.signal
2607
+ ).then((outcome2) => {
2608
+ args.ctx.setUsage({
2609
+ runtimeCompression: anthropicRuntimeMeasurement({
2610
+ policy: runtimePolicy,
2611
+ telemetry: buildPromptCompressionTelemetry(outcome2.segments),
2612
+ latencyMs: Date.now() - startedAt2,
2613
+ shadow: true,
2614
+ originalPayload: args.params,
2615
+ resultPayload: outcome2.params
2616
+ })
2617
+ });
2618
+ }).catch(() => {
2619
+ args.ctx.setUsage({
2620
+ runtimeCompression: failedRuntimeMeasurement(
2621
+ runtimePolicy,
2622
+ Date.now() - startedAt2
2623
+ )
2624
+ });
2625
+ });
2626
+ return args.params;
2627
+ }
2628
+ }
1800
2629
  if (!compression) {
1801
2630
  return args.params;
1802
2631
  }
2632
+ const startedAt = Date.now();
1803
2633
  const outcome = await compressAnthropicParams(
1804
2634
  args.params,
1805
2635
  args.usageTap,
1806
2636
  compression,
1807
2637
  args.withUsage?.signal
1808
2638
  );
2639
+ if (policyDriven && runtimePolicy) {
2640
+ const runtimeMeasurement = anthropicRuntimeMeasurement({
2641
+ policy: runtimePolicy,
2642
+ telemetry: buildPromptCompressionTelemetry(outcome.segments),
2643
+ latencyMs: Date.now() - startedAt,
2644
+ originalPayload: args.params,
2645
+ resultPayload: outcome.params
2646
+ });
2647
+ args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
2648
+ if (runtimeMeasurement.decision !== "compressed") return args.params;
2649
+ }
1809
2650
  await recordCompressionOutcome({
1810
2651
  outcome,
1811
2652
  compression,
@@ -1817,6 +2658,76 @@ async function compressAnthropicParamsForCall(args) {
1817
2658
  });
1818
2659
  return outcome.params;
1819
2660
  }
2661
+ function anthropicPolicyCompressionOptions(policy) {
2662
+ const roles = Object.fromEntries(
2663
+ ["system", "user", "tool", "assistant"].filter((role) => policy.snapshot.scope.messageRoles.includes(role)).map((role) => [role, true])
2664
+ );
2665
+ return {
2666
+ provider: "usagetap",
2667
+ roles,
2668
+ minContextTokens: policy.snapshot.scope.minimumTokens,
2669
+ failOpen: true
2670
+ };
2671
+ }
2672
+ function bypassedRuntimeMeasurement(policy) {
2673
+ return {
2674
+ policyId: policy.policyId,
2675
+ policyVersionId: policy.policyVersionId,
2676
+ policyVersion: policy.version,
2677
+ rolloutMode: policy.mode,
2678
+ decision: "bypassed",
2679
+ reasonCode: "not_in_rollout_sample",
2680
+ methodsAttempted: [],
2681
+ methodsApplied: [],
2682
+ originalTokens: 0,
2683
+ resultTokens: 0,
2684
+ savedTokens: 0,
2685
+ reductionPercentage: 0,
2686
+ compressionLatencyMs: 0,
2687
+ compressionComputeCostUsd: 0,
2688
+ financialsEstimated: true
2689
+ };
2690
+ }
2691
+ function failedRuntimeMeasurement(policy, latencyMs) {
2692
+ return {
2693
+ ...bypassedRuntimeMeasurement(policy),
2694
+ decision: "fallback",
2695
+ reasonCode: "transformation_failed",
2696
+ methodsAttempted: ["deterministic"],
2697
+ compressionLatencyMs: latencyMs
2698
+ };
2699
+ }
2700
+ function anthropicRuntimeMeasurement(input) {
2701
+ const telemetry = input.telemetry;
2702
+ const originalTokens = telemetry?.originalTokens ?? estimatePromptTokens(JSON.stringify(input.originalPayload));
2703
+ const resultTokens = telemetry?.compressedTokens ?? originalTokens;
2704
+ const savedTokens = Math.max(0, telemetry?.savedTokens ?? 0);
2705
+ const reductionPercentage = originalTokens > 0 ? savedTokens / originalTokens * 100 : 0;
2706
+ const belowMinimumSize = originalTokens < input.policy.snapshot.scope.minimumTokens;
2707
+ const latencyExceeded = input.latencyMs > input.policy.snapshot.gates.maximumLatencyMs;
2708
+ const belowSavings = savedTokens < input.policy.snapshot.gates.minimumTokensRemoved || reductionPercentage < input.policy.snapshot.gates.minimumReductionPercentage;
2709
+ const accepted = !belowMinimumSize && !latencyExceeded && !belowSavings;
2710
+ const deepText = input.policy.snapshot.methods.deepText.enabled && !input.policy.snapshot.rollout.deterministicOnly && (input.policy.mode !== "CANARY" || input.policy.snapshot.rollout.deepTextCanaryAllowed);
2711
+ const reasonCode = belowMinimumSize ? "below_minimum_size" : latencyExceeded ? "latency_budget_exceeded" : belowSavings ? "below_minimum_savings" : void 0;
2712
+ const measurement = {
2713
+ policyId: input.policy.policyId,
2714
+ policyVersionId: input.policy.policyVersionId,
2715
+ policyVersion: input.policy.version,
2716
+ rolloutMode: input.policy.mode,
2717
+ decision: accepted ? input.shadow ? "bypassed" : "compressed" : latencyExceeded ? "fallback" : "skipped",
2718
+ ...reasonCode ? { reasonCode } : {},
2719
+ methodsAttempted: ["deterministic", ...deepText ? ["deep_text"] : []],
2720
+ methodsApplied: accepted ? ["deterministic"] : [],
2721
+ originalTokens,
2722
+ resultTokens: accepted ? resultTokens : originalTokens,
2723
+ savedTokens: accepted ? savedTokens : 0,
2724
+ reductionPercentage: accepted ? reductionPercentage : 0,
2725
+ compressionLatencyMs: input.latencyMs,
2726
+ compressionComputeCostUsd: 0,
2727
+ financialsEstimated: true
2728
+ };
2729
+ return measurement;
2730
+ }
1820
2731
  async function recordCompressionOutcome(args) {
1821
2732
  const telemetry = buildPromptCompressionTelemetry(args.outcome.segments);
1822
2733
  if (!telemetry) {
@@ -1871,6 +2782,10 @@ async function compressAnthropicParams(params, usageTap, compression, signal) {
1871
2782
  const result = await usageTap.compressPromptMessages(source, {
1872
2783
  provider: "usagetap",
1873
2784
  failOpen: compression.failOpen,
2785
+ mode: compression.mode,
2786
+ latencyBudgetMs: compression.latencyBudgetMs,
2787
+ compactEmptyUserMessages: compression.compactEmptyUserMessages,
2788
+ compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
1874
2789
  aggressiveness: resolveMessageEndpointAggressiveness(compression),
1875
2790
  signal
1876
2791
  });
@@ -2255,10 +3170,16 @@ function resolveBeginRequest(defaults, override) {
2255
3170
  }
2256
3171
  const tags = mergeTags(base.tags, current.tags);
2257
3172
  const begin = { customerId };
3173
+ const runtimeCompressionContext = current.runtimeCompressionContext ?? base.runtimeCompressionContext;
3174
+ if (runtimeCompressionContext) {
3175
+ begin.runtimeCompressionContext = runtimeCompressionContext;
3176
+ }
2258
3177
  const requested = current.requested ?? base.requested;
2259
3178
  if (requested) begin.requested = requested;
2260
3179
  const feature = current.feature ?? base.feature;
2261
3180
  if (feature) begin.feature = feature;
3181
+ const runId = current.runId ?? base.runId;
3182
+ if (runId) begin.runId = runId;
2262
3183
  const idempotency = current.idempotency ?? base.idempotency;
2263
3184
  if (idempotency) begin.idempotency = idempotency;
2264
3185
  const idempotencyKey = current.idempotencyKey ?? base.idempotencyKey;
@@ -2267,6 +3188,12 @@ function resolveBeginRequest(defaults, override) {
2267
3188
  if (customerName) begin.customerName = customerName;
2268
3189
  const customerEmail = current.customerEmail ?? base.customerEmail;
2269
3190
  if (customerEmail) begin.customerEmail = customerEmail;
3191
+ const customerUserId = current.customerUserId ?? base.customerUserId;
3192
+ if (customerUserId) begin.customerUserId = customerUserId;
3193
+ const customerUserName = current.customerUserName ?? base.customerUserName;
3194
+ if (customerUserName) begin.customerUserName = customerUserName;
3195
+ const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
3196
+ if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
2270
3197
  const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
2271
3198
  if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
2272
3199
  const holdUsd = current.holdUsd ?? base.holdUsd;
@@ -2282,6 +3209,11 @@ function resolveBeginRequest(defaults, override) {
2282
3209
  }
2283
3210
  function createCallState(beginRequest, begin) {
2284
3211
  const usage = {};
3212
+ const pricingMode = begin.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
3213
+ if (pricingMode) {
3214
+ usage.pricingMode = pricingMode;
3215
+ usage.batch = pricingMode === "batch";
3216
+ }
2285
3217
  const stripeCustomerId = typeof begin.data.stripeCustomerId === "string" ? begin.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
2286
3218
  if (stripeCustomerId) {
2287
3219
  usage.stripeCustomerId = stripeCustomerId;
@@ -2345,10 +3277,12 @@ function inferAnthropicUsage(response, fallbackModel, ctx) {
2345
3277
  }
2346
3278
  function extractAnthropicUsage(payload, fallbackModel) {
2347
3279
  if (!isObjectRecord(payload)) {
2348
- return fallbackModel ? { modelUsed: fallbackModel } : void 0;
3280
+ return { providerUsed: "anthropic", ...fallbackModel ? { modelUsed: fallbackModel } : {} };
2349
3281
  }
2350
3282
  const usage = findAnthropicUsageRecord(payload);
2351
- const result = {};
3283
+ const result = {
3284
+ providerUsed: "anthropic"
3285
+ };
2352
3286
  const model = readString(payload.model) ?? readString(payload.message?.model) ?? fallbackModel;
2353
3287
  if (model) {
2354
3288
  result.modelUsed = model;
@@ -2358,6 +3292,21 @@ function extractAnthropicUsage(payload, fallbackModel) {
2358
3292
  }
2359
3293
  return Object.keys(result).length ? result : void 0;
2360
3294
  }
3295
+ function anthropicRequestExecutionMetadata(params) {
3296
+ const outputConfig = isObjectRecord(params.output_config) ? params.output_config : void 0;
3297
+ const thinking = isObjectRecord(params.thinking) ? params.thinking : void 0;
3298
+ const rawEffort = outputConfig?.effort;
3299
+ const effort = rawEffort === "none" || rawEffort === "minimal" || rawEffort === "low" || rawEffort === "medium" || rawEffort === "high" || rawEffort === "xhigh" || rawEffort === "max" ? rawEffort : void 0;
3300
+ const mode = readString(thinking?.type);
3301
+ const rawBudget = readNumber(thinking?.budget_tokens);
3302
+ const budget = rawBudget !== void 0 && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
3303
+ return {
3304
+ providerUsed: "anthropic",
3305
+ ...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
3306
+ ...mode ? { reasoningMode: mode } : {},
3307
+ ...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
3308
+ };
3309
+ }
2361
3310
  function findAnthropicUsageRecord(payload) {
2362
3311
  if (isObjectRecord(payload.usage)) {
2363
3312
  return payload.usage;
@@ -2368,10 +3317,6 @@ function findAnthropicUsageRecord(payload) {
2368
3317
  return void 0;
2369
3318
  }
2370
3319
  function applyAnthropicUsageRecord(usage, usageRecord) {
2371
- const inputTokens = readNumber(usageRecord.input_tokens ?? usageRecord.prompt_tokens);
2372
- if (inputTokens !== void 0) {
2373
- usage.inputTokens = inputTokens;
2374
- }
2375
3320
  const outputTokens = readNumber(usageRecord.output_tokens ?? usageRecord.completion_tokens);
2376
3321
  if (outputTokens !== void 0) {
2377
3322
  usage.responseTokens = outputTokens;
@@ -2382,6 +3327,26 @@ function applyAnthropicUsageRecord(usage, usageRecord) {
2382
3327
  if (cachedInputTokens !== void 0) {
2383
3328
  usage.cachedInputTokens = cachedInputTokens;
2384
3329
  }
3330
+ const cacheWriteInputTokens = readNumber(
3331
+ usageRecord.cache_creation_input_tokens ?? usageRecord.prompt_cache_miss_tokens ?? usageRecord.cache_write_tokens
3332
+ );
3333
+ if (cacheWriteInputTokens !== void 0) {
3334
+ usage.cacheWriteInputTokens = cacheWriteInputTokens;
3335
+ }
3336
+ const baseInputTokens = readNumber(usageRecord.input_tokens ?? usageRecord.prompt_tokens);
3337
+ if (baseInputTokens !== void 0) {
3338
+ usage.inputTokens = baseInputTokens + (cachedInputTokens ?? 0) + (cacheWriteInputTokens ?? 0);
3339
+ }
3340
+ }
3341
+ function withRuntimeCompressionContext(begin, provider, model) {
3342
+ return {
3343
+ ...begin,
3344
+ runtimeCompressionContext: {
3345
+ ...begin.runtimeCompressionContext ?? {},
3346
+ provider,
3347
+ model
3348
+ }
3349
+ };
2385
3350
  }
2386
3351
  function accumulateAnthropicStreamUsage(chunk, fallbackModel, state) {
2387
3352
  const usage = extractAnthropicUsage(chunk, fallbackModel);
@@ -2608,6 +3573,7 @@ function isIteratorResult(value) {
2608
3573
  exports.AnthropicPromptCompressionStats = AnthropicPromptCompressionStats;
2609
3574
  exports.withCompression = withCompression;
2610
3575
  exports.withMetering = withMetering;
3576
+ exports.withSampling = withSampling;
2611
3577
  exports.wrapAnthropic = wrapAnthropic;
2612
3578
  //# sourceMappingURL=anthropic.cjs.map
2613
3579
  //# sourceMappingURL=anthropic.cjs.map