@usagetap/sdk 1.3.2 → 1.7.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 +372 -39
  2. package/dist/adapters/anthropic.cjs +995 -69
  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 +995 -70
  7. package/dist/adapters/anthropic.mjs.map +1 -1
  8. package/dist/adapters/openai.cjs +1208 -106
  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 +1208 -107
  13. package/dist/adapters/openai.mjs.map +1 -1
  14. package/dist/adapters/openrouter.cjs +3912 -53
  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 +3910 -54
  19. package/dist/adapters/openrouter.mjs.map +1 -1
  20. package/dist/anthropic/index.cjs +995 -69
  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 +995 -70
  25. package/dist/anthropic/index.mjs.map +1 -1
  26. package/dist/client-C0UiaqVB.d.cts +1305 -0
  27. package/dist/client-C0UiaqVB.d.ts +1305 -0
  28. package/dist/express/index.cjs +399 -64
  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 +399 -64
  33. package/dist/express/index.mjs.map +1 -1
  34. package/dist/index.cjs +1044 -163
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +16 -5
  37. package/dist/index.d.ts +16 -5
  38. package/dist/index.mjs +1044 -163
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/openai/index.cjs +1209 -107
  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 +1209 -108
  45. package/dist/openai/index.mjs.map +1 -1
  46. package/dist/openrouter/index.cjs +1226 -109
  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 +1224 -108
  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 +2 -2
  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,369 @@ 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
+ /** Buffered OpenAI Responses-compatible requests. */
1028
+ responses;
1029
+ models;
1030
+ batches;
1031
+ transport;
1032
+ idempotencyGenerator;
1033
+ constructor(config) {
1034
+ this.transport = new ResourceTransport(
1035
+ config.gatewayBaseUrl ?? DEFAULT_GATEWAY_BASE_URL,
1036
+ config
1037
+ );
1038
+ this.idempotencyGenerator = config.idempotencyGenerator ?? createIdempotencyKey;
1039
+ this.chat = {
1040
+ completions: {
1041
+ create: (params, options) => this.transport.request({
1042
+ method: "POST",
1043
+ path: "/v1/chat/completions",
1044
+ body: params,
1045
+ options,
1046
+ response: "json"
1047
+ })
1048
+ }
1049
+ };
1050
+ this.responses = {
1051
+ create: (params, options) => this.transport.request({
1052
+ method: "POST",
1053
+ path: "/v1/responses",
1054
+ body: params,
1055
+ options,
1056
+ response: "json"
1057
+ })
1058
+ };
1059
+ this.models = {
1060
+ list: (options) => this.transport.request({
1061
+ method: "GET",
1062
+ path: "/v1/models",
1063
+ options,
1064
+ response: "json"
1065
+ })
1066
+ };
1067
+ this.batches = {
1068
+ create: (params, options = {}) => this.transport.request({
1069
+ method: "POST",
1070
+ path: "/v1/batches",
1071
+ body: params,
1072
+ options: {
1073
+ ...options,
1074
+ idempotencyKey: options.idempotencyKey ?? this.idempotencyGenerator()
1075
+ },
1076
+ response: "json"
1077
+ }),
1078
+ retrieve: (batchId, options) => this.transport.request({
1079
+ method: "GET",
1080
+ path: `/v1/batches/${encodeURIComponent(
1081
+ resourceId(batchId, ["id"], "gateway.batches.retrieve")
1082
+ )}`,
1083
+ options,
1084
+ response: "json"
1085
+ }),
1086
+ wait: (batch, options) => this.waitForBatch(batch, options),
1087
+ cancel: (batchId, options) => this.transport.request({
1088
+ method: "POST",
1089
+ path: `/v1/batches/${encodeURIComponent(
1090
+ resourceId(batchId, ["id"], "gateway.batches.cancel")
1091
+ )}/cancel`,
1092
+ options,
1093
+ response: "json"
1094
+ }),
1095
+ results: (batchId, options) => this.transport.request({
1096
+ method: "GET",
1097
+ path: `/v1/batches/${encodeURIComponent(
1098
+ resourceId(batchId, ["id"], "gateway.batches.results")
1099
+ )}/results`,
1100
+ options,
1101
+ response: "ndjson"
1102
+ })
1103
+ };
1104
+ }
1105
+ async waitForBatch(value, options = {}) {
1106
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
1107
+ const deadline = Date.now() + timeoutMs;
1108
+ let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
1109
+ while (!terminalGatewayBatch(batch.status)) {
1110
+ if (Date.now() >= deadline) {
1111
+ throw new UsageTapError(
1112
+ "USAGETAP_RETRY_EXHAUSTED",
1113
+ `Gateway batch ${batch.id} did not finish before timeout`,
1114
+ { retryable: true }
1115
+ );
1116
+ }
1117
+ await sleep(pollIntervalMs, options.signal);
1118
+ batch = await this.batches.retrieve(batch.id, options);
1119
+ }
1120
+ return batch;
1121
+ }
1122
+ };
1123
+
755
1124
  // src/client.ts
756
1125
  var CALL_BEGIN_PATH = "call_begin";
757
1126
  var CALL_END_PATH = "call_end";
758
1127
  var COMPRESS_PROMPT_PATH = "compress_prompt";
1128
+ var SAMPLES_PATH = "samples";
1129
+ var SAMPLING_SETTINGS_PATH = "sampling/settings";
1130
+ var SAMPLING_DECIDE_PATH = "sampling/decide";
759
1131
  var CHECK_USAGE_PATH = "customers/{customerId}/usage";
760
1132
  var CREATE_CUSTOMER_PATH = "customers";
761
1133
  var CHANGE_PLAN_PATH = "customers/{customerId}/change_plan";
@@ -766,11 +1138,16 @@ var CORRELATION_HEADER = "x-usage-correlation-id";
766
1138
  var IDEMPOTENCY_HEADER = "idempotency-key";
767
1139
  var SDK_HEADER = "x-usage-sdk";
768
1140
  var USER_AGENT = "UsageTapClient";
769
- var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
1141
+ var CANONICAL_MEDIA_TYPE2 = "application/vnd.usagetap.v1+json";
770
1142
  var DEFAULT_BASE_URL = "https://api.usagetap.com";
771
- var SDK_VERSION = "1.3.2" ;
1143
+ var DEFAULT_RUN_INACTIVITY_MS = 60 * 60 * 1e3;
1144
+ var SDK_VERSION = "1.7.0" ;
772
1145
  var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
773
1146
  var UsageTapClient = class {
1147
+ /** OpenAI-compatible chat, model, and native batch operations. */
1148
+ gateway;
1149
+ /** Published-profile context summarization operations. */
1150
+ summarization;
774
1151
  apiKey;
775
1152
  baseUrl;
776
1153
  fetchImpl;
@@ -795,6 +1172,11 @@ var UsageTapClient = class {
795
1172
  usageTapCompressionMessagesEndpoint;
796
1173
  usageTapCompressionModel;
797
1174
  usageTapCompressionAggressiveness;
1175
+ sampling;
1176
+ samplingSettingsCacheMs;
1177
+ circuitBreaker;
1178
+ circuitBreakerRuns = /* @__PURE__ */ new Map();
1179
+ samplingSettingsCache;
798
1180
  constructor(options = {}) {
799
1181
  const apiKey = options.apiKey?.trim() || readEnvironmentVariable("USAGETAP_API_KEY");
800
1182
  const baseUrl = options.baseUrl?.trim() || readEnvironmentVariable("USAGETAP_BASE_URL") || DEFAULT_BASE_URL;
@@ -817,10 +1199,21 @@ var UsageTapClient = class {
817
1199
  "A global fetch implementation was not found. Pass fetchImpl in UsageTapClientOptions."
818
1200
  );
819
1201
  }
820
- const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
821
- this.baseUrl = new URL(normalizedBaseUrl);
1202
+ const normalizedBaseUrl2 = normalizeBaseUrl(baseUrl);
1203
+ this.baseUrl = new URL(normalizedBaseUrl2);
822
1204
  this.apiKey = apiKey;
823
1205
  this.fetchImpl = wrapFetchImplementation(fetchCandidate, !options.fetchImpl);
1206
+ const resourceConfig = {
1207
+ apiKey,
1208
+ apiBaseUrl: normalizedBaseUrl2,
1209
+ gatewayBaseUrl: options.gatewayBaseUrl?.trim() || readEnvironmentVariable("USAGETAP_GATEWAY_URL"),
1210
+ fetchImpl: this.fetchImpl,
1211
+ headers: options.headers,
1212
+ sdkVersion: SDK_VERSION,
1213
+ idempotencyGenerator: options.idempotencyGenerator
1214
+ };
1215
+ this.gateway = new GatewayResource(resourceConfig);
1216
+ this.summarization = new SummarizationResource(resourceConfig);
824
1217
  this.defaultFeature = options.defaultFeature;
825
1218
  this.defaultTags = options.defaultTags?.length ? dedupeStrings(options.defaultTags) : void 0;
826
1219
  this.defaultHeaders = options.headers ? normalizeHeaderDictionary(options.headers) : {};
@@ -842,11 +1235,122 @@ var UsageTapClient = class {
842
1235
  this.usageTapCompressionMessagesEndpoint = options.usageTapCompressionMessagesEndpoint;
843
1236
  this.usageTapCompressionModel = options.usageTapCompressionModel;
844
1237
  this.usageTapCompressionAggressiveness = options.usageTapCompressionAggressiveness;
1238
+ this.sampling = options.sampling;
1239
+ this.samplingSettingsCacheMs = Number.isFinite(options.samplingSettingsCacheMs) ? Math.max(0, Number(options.samplingSettingsCacheMs)) : 5 * 60 * 1e3;
1240
+ if (options.circuitBreaker) {
1241
+ const maxCallsPerRun = options.circuitBreaker.maxCallsPerRun;
1242
+ if (!Number.isInteger(maxCallsPerRun) || maxCallsPerRun < 1) {
1243
+ throw new UsageTapError(
1244
+ "USAGETAP_BAD_REQUEST",
1245
+ "circuitBreaker.maxCallsPerRun must be a positive integer"
1246
+ );
1247
+ }
1248
+ const runInactivityMs = options.circuitBreaker.runInactivityMs ?? DEFAULT_RUN_INACTIVITY_MS;
1249
+ if (!Number.isFinite(runInactivityMs) || runInactivityMs < 1) {
1250
+ throw new UsageTapError(
1251
+ "USAGETAP_BAD_REQUEST",
1252
+ "circuitBreaker.runInactivityMs must be a positive number"
1253
+ );
1254
+ }
1255
+ this.circuitBreaker = {
1256
+ maxCallsPerRun,
1257
+ runInactivityMs
1258
+ };
1259
+ }
1260
+ }
1261
+ shouldSample(request, policy = this.sampling || void 0) {
1262
+ if (!policy) return false;
1263
+ const rate = Math.min(1, Math.max(0, Number(policy.rate) || 0));
1264
+ if (rate <= 0) return false;
1265
+ const customerId = request.customerId?.trim();
1266
+ if (customerId && policy.customers?.exclude?.includes(customerId)) return false;
1267
+ const feature = request.feature?.trim();
1268
+ if (feature && policy.features?.exclude?.includes(feature)) return false;
1269
+ const included = policy.features?.include?.filter(Boolean) ?? [];
1270
+ if (included.length > 0 && (!feature || !included.includes(feature))) return false;
1271
+ const minimum = Math.max(0, Math.round(policy.minInputTokens ?? 0));
1272
+ if (minimum > 0 && estimatePromptTokens(request.input) < minimum) return false;
1273
+ return (policy.random ?? Math.random)() < rate;
1274
+ }
1275
+ async getSamplingSettings(options = {}) {
1276
+ const now = Date.now();
1277
+ if (!options.forceRefresh && this.samplingSettingsCache && this.samplingSettingsCache.expiresAtMs > now) {
1278
+ return {
1279
+ result: { status: "ACCEPTED", code: "SAMPLING_SETTINGS_CACHED" },
1280
+ data: this.samplingSettingsCache.settings,
1281
+ correlationId: options.correlationId ?? "local-cache"
1282
+ };
1283
+ }
1284
+ const response = await this.requestGet(
1285
+ SAMPLING_SETTINGS_PATH,
1286
+ {
1287
+ signal: options.signal,
1288
+ headers: options.headers,
1289
+ retries: options.retries,
1290
+ correlationId: options.correlationId
1291
+ }
1292
+ );
1293
+ const serverCacheMs = Math.max(0, Number(response.data.cacheSeconds) || 0) * 1e3;
1294
+ const cacheMs = Math.min(this.samplingSettingsCacheMs, serverCacheMs);
1295
+ this.samplingSettingsCache = {
1296
+ settings: response.data,
1297
+ expiresAtMs: now + cacheMs
1298
+ };
1299
+ return response;
1300
+ }
1301
+ async shouldSampleAsync(request, policy) {
1302
+ if (policy) return this.shouldSample(request, policy);
1303
+ if (this.sampling === false) return false;
1304
+ if (this.sampling) return this.shouldSample(request, this.sampling);
1305
+ try {
1306
+ const settings = await this.getSamplingSettings();
1307
+ return this.shouldSample(request, settings.data);
1308
+ } catch {
1309
+ return false;
1310
+ }
1311
+ }
1312
+ async decideSample(request, options = {}) {
1313
+ const hasTokens = Number.isFinite(request.inputTokens) && Number(request.inputTokens) >= 0;
1314
+ const hasCharacters = Number.isFinite(request.inputCharacters) && Number(request.inputCharacters) >= 0;
1315
+ if (!hasTokens && !hasCharacters) {
1316
+ throw new UsageTapError(
1317
+ "USAGETAP_BAD_REQUEST",
1318
+ "decideSample requires inputTokens or inputCharacters"
1319
+ );
1320
+ }
1321
+ return this.request(
1322
+ SAMPLING_DECIDE_PATH,
1323
+ request,
1324
+ options
1325
+ );
1326
+ }
1327
+ async captureSample(request, options = {}) {
1328
+ if (!request || request.input === void 0) {
1329
+ throw new UsageTapError(
1330
+ "USAGETAP_BAD_REQUEST",
1331
+ "captureSample requires input"
1332
+ );
1333
+ }
1334
+ if (!request.provider?.trim()) {
1335
+ throw new UsageTapError(
1336
+ "USAGETAP_BAD_REQUEST",
1337
+ "captureSample requires provider"
1338
+ );
1339
+ }
1340
+ const sampleId = request.sampleId?.trim() || this.idempotencyGenerator();
1341
+ return this.request(
1342
+ SAMPLES_PATH,
1343
+ { ...request, sampleId },
1344
+ { ...options, idempotencyKey: sampleId }
1345
+ );
845
1346
  }
846
1347
  async beginCall(request, options = {}) {
847
1348
  const idempotencyKey = request.idempotencyKey ?? request.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
1349
+ this.reserveRunCall(request, idempotencyKey);
1350
+ const apiRequest = { ...request };
1351
+ delete apiRequest.runId;
848
1352
  const payload = {
849
- ...request,
1353
+ ...apiRequest,
850
1354
  feature: request.feature ?? this.defaultFeature,
851
1355
  tags: this.mergeTags(request.tags)
852
1356
  };
@@ -864,6 +1368,28 @@ var UsageTapClient = class {
864
1368
  );
865
1369
  return response;
866
1370
  }
1371
+ /**
1372
+ * Inspect a configured run circuit breaker without consuming another call.
1373
+ */
1374
+ canRunContinue(request) {
1375
+ const identity = this.resolveRunIdentity(request);
1376
+ if (!identity || !this.circuitBreaker) {
1377
+ throw new UsageTapError(
1378
+ "USAGETAP_BAD_REQUEST",
1379
+ "canRunContinue requires circuitBreaker configuration and a non-empty runId"
1380
+ );
1381
+ }
1382
+ this.expireInactiveRuns();
1383
+ const calls = this.circuitBreakerRuns.get(identity.key)?.calls ?? 0;
1384
+ return this.createCircuitBreakerDecision(identity.customerId, identity.runId, calls);
1385
+ }
1386
+ /**
1387
+ * Release local state after a workflow finishes. Returns true when state existed.
1388
+ */
1389
+ resetRun(request) {
1390
+ const identity = this.resolveRunIdentity(request);
1391
+ return identity ? this.circuitBreakerRuns.delete(identity.key) : false;
1392
+ }
867
1393
  async promptCompress(request, options = {}) {
868
1394
  if (!request?.callId) {
869
1395
  throw new UsageTapError(
@@ -961,6 +1487,10 @@ var UsageTapClient = class {
961
1487
  usageTapCompressionMessagesEndpoint: this.usageTapCompressionMessagesEndpoint,
962
1488
  aggressiveness: options.aggressiveness ?? this.aggressiveness,
963
1489
  usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
1490
+ mode: options.mode,
1491
+ latencyBudgetMs: options.latencyBudgetMs,
1492
+ compactEmptyUserMessages: options.compactEmptyUserMessages,
1493
+ compactDuplicateUserTextParts: options.compactDuplicateUserTextParts,
964
1494
  fetchImpl: this.fetchImpl,
965
1495
  signal: options.signal,
966
1496
  failOpen: options.failOpen
@@ -1002,14 +1532,29 @@ var UsageTapClient = class {
1002
1532
  callId: request.callId,
1003
1533
  feature: feature ?? this.defaultFeature,
1004
1534
  tags: tags ?? this.defaultTags,
1535
+ providerUsed: request.providerUsed,
1005
1536
  modelUsed: request.modelUsed,
1537
+ reasoningEffort: request.reasoningEffort,
1538
+ reasoningEffortSource: request.reasoningEffortSource,
1539
+ reasoningMode: request.reasoningMode,
1540
+ reasoningBudgetTokens: request.reasoningBudgetTokens,
1006
1541
  metrics: {
1007
1542
  inputTokens: request.inputTokens,
1008
1543
  responseTokens: request.responseTokens,
1009
1544
  cachedInputTokens: request.cachedInputTokens,
1545
+ cacheWriteInputTokens: request.cacheWriteInputTokens,
1546
+ cacheWrite5mInputTokens: request.cacheWrite5mInputTokens,
1547
+ cacheWrite1hInputTokens: request.cacheWrite1hInputTokens,
1010
1548
  reasoningTokens: request.reasoningTokens,
1011
1549
  searches: request.searches,
1012
1550
  audioSeconds: request.audioSeconds,
1551
+ imageInputCount: request.imageInputCount,
1552
+ imageInputTokens: request.imageInputTokens,
1553
+ imageOutputCount: request.imageOutputCount,
1554
+ imageOutputTokens: request.imageOutputTokens,
1555
+ audioInputTokens: request.audioInputTokens,
1556
+ cachedAudioInputTokens: request.cachedAudioInputTokens,
1557
+ audioOutputTokens: request.audioOutputTokens,
1013
1558
  costUsd: response.data.costUSD
1014
1559
  },
1015
1560
  correlationId: response.correlationId
@@ -1096,10 +1641,10 @@ var UsageTapClient = class {
1096
1641
  "incrementCustomMeter requires meterSlot"
1097
1642
  );
1098
1643
  }
1099
- if (!["CUSTOM1", "CUSTOM2"].includes(request.meterSlot)) {
1644
+ if (!["CUSTOM1", "CUSTOM2", "AGENTIC_API"].includes(request.meterSlot)) {
1100
1645
  throw new UsageTapError(
1101
1646
  "USAGETAP_BAD_REQUEST",
1102
- "meterSlot must be CUSTOM1 or CUSTOM2"
1647
+ "meterSlot must be CUSTOM1, CUSTOM2 or AGENTIC_API"
1103
1648
  );
1104
1649
  }
1105
1650
  if (typeof request.amount !== "number" || !Number.isFinite(request.amount) || request.amount <= 0) {
@@ -1114,6 +1659,15 @@ var UsageTapClient = class {
1114
1659
  meterSlot: request.meterSlot,
1115
1660
  amount: request.amount
1116
1661
  };
1662
+ if (request.customerUserId) {
1663
+ payload.customerUserId = request.customerUserId;
1664
+ }
1665
+ if (request.customerUserName) {
1666
+ payload.customerUserName = request.customerUserName;
1667
+ }
1668
+ if (request.customerUserEmail) {
1669
+ payload.customerUserEmail = request.customerUserEmail;
1670
+ }
1117
1671
  if (request.feature) {
1118
1672
  payload.feature = request.feature;
1119
1673
  }
@@ -1150,6 +1704,11 @@ var UsageTapClient = class {
1150
1704
  const beginPayload = idempotencyKey ? { ...beginRequest, idempotencyKey, idempotency: idempotencyKey } : { ...beginRequest };
1151
1705
  const beginResponse = await this.beginCall(beginPayload, options);
1152
1706
  let usage = {};
1707
+ const pricingMode = beginResponse.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
1708
+ if (pricingMode) {
1709
+ usage.pricingMode = pricingMode;
1710
+ usage.batch = pricingMode === "batch";
1711
+ }
1153
1712
  const initialStripeCustomerId = typeof beginResponse.data.stripeCustomerId === "string" ? beginResponse.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
1154
1713
  if (initialStripeCustomerId) {
1155
1714
  usage = { ...usage, stripeCustomerId: initialStripeCustomerId };
@@ -1158,6 +1717,28 @@ var UsageTapClient = class {
1158
1717
  let handlerResult;
1159
1718
  let handlerError;
1160
1719
  let endCallError;
1720
+ let finalizationDeferred = false;
1721
+ let finalizationPromise;
1722
+ const finalize = () => {
1723
+ if (!finalizationPromise) {
1724
+ finalizationPromise = this.endCall(
1725
+ {
1726
+ callId: beginResponse.data.callId,
1727
+ // Pass context for metric tracking
1728
+ customerId: beginRequest.customerId,
1729
+ feature: beginRequest.feature ?? this.defaultFeature,
1730
+ tags: beginRequest.tags ?? this.defaultTags,
1731
+ ...usage,
1732
+ error: errorPayload
1733
+ },
1734
+ {
1735
+ ...options,
1736
+ correlationId: beginResponse.correlationId
1737
+ }
1738
+ ).then(() => void 0);
1739
+ }
1740
+ return finalizationPromise;
1741
+ };
1161
1742
  const context = {
1162
1743
  begin: beginResponse,
1163
1744
  setUsage: (u) => {
@@ -1165,6 +1746,10 @@ var UsageTapClient = class {
1165
1746
  },
1166
1747
  setError: (err) => {
1167
1748
  errorPayload = err;
1749
+ },
1750
+ deferFinalization: () => {
1751
+ finalizationDeferred = true;
1752
+ return finalize;
1168
1753
  }
1169
1754
  };
1170
1755
  try {
@@ -1178,24 +1763,12 @@ var UsageTapClient = class {
1178
1763
  };
1179
1764
  }
1180
1765
  } finally {
1181
- try {
1182
- await this.endCall(
1183
- {
1184
- callId: beginResponse.data.callId,
1185
- // Pass context for metric tracking
1186
- customerId: beginRequest.customerId,
1187
- feature: beginRequest.feature ?? this.defaultFeature,
1188
- tags: beginRequest.tags ?? this.defaultTags,
1189
- ...usage,
1190
- error: errorPayload
1191
- },
1192
- {
1193
- ...options,
1194
- correlationId: beginResponse.correlationId
1195
- }
1196
- );
1197
- } catch (error) {
1198
- endCallError = error;
1766
+ if (handlerError || !finalizationDeferred) {
1767
+ try {
1768
+ await finalize();
1769
+ } catch (error) {
1770
+ endCallError = error;
1771
+ }
1199
1772
  }
1200
1773
  }
1201
1774
  if (handlerError) {
@@ -1217,17 +1790,86 @@ var UsageTapClient = class {
1217
1790
  toPromptCompressionTelemetry(result) {
1218
1791
  return {
1219
1792
  provider: result.provider,
1220
- originalCharacters: result.originalCharacters,
1221
- compressedCharacters: result.compressedCharacters,
1222
- savedCharacters: result.savedCharacters,
1223
1793
  originalTokens: result.originalTokens,
1224
1794
  compressedTokens: result.compressedTokens,
1225
1795
  savedTokens: result.savedTokens,
1226
1796
  tokenSavingsRatio: result.tokenSavingsRatio,
1227
- savingsRatio: result.savingsRatio,
1228
1797
  techniques: result.techniques
1229
1798
  };
1230
1799
  }
1800
+ reserveRunCall(request, idempotencyKey) {
1801
+ const identity = this.resolveRunIdentity(request);
1802
+ if (!identity || !this.circuitBreaker) return;
1803
+ this.expireInactiveRuns();
1804
+ const now = Date.now();
1805
+ const state = this.circuitBreakerRuns.get(identity.key) ?? {
1806
+ calls: 0,
1807
+ reservationKeys: /* @__PURE__ */ new Set(),
1808
+ lastSeenAtMs: now
1809
+ };
1810
+ const reservationKey = idempotencyKey ?? this.idempotencyGenerator();
1811
+ state.lastSeenAtMs = now;
1812
+ if (state.reservationKeys.has(reservationKey)) {
1813
+ this.circuitBreakerRuns.set(identity.key, state);
1814
+ return;
1815
+ }
1816
+ const decision = this.createCircuitBreakerDecision(
1817
+ identity.customerId,
1818
+ identity.runId,
1819
+ state.calls
1820
+ );
1821
+ if (!decision.allowed) {
1822
+ throw new UsageTapError(
1823
+ "USAGETAP_CIRCUIT_OPEN",
1824
+ `Run ${identity.runId} reached its ${decision.limit}-call circuit-breaker limit`,
1825
+ {
1826
+ details: {
1827
+ reason: decision.reason,
1828
+ customerId: identity.customerId,
1829
+ runId: identity.runId,
1830
+ calls: decision.calls,
1831
+ limit: decision.limit,
1832
+ remaining: decision.remaining
1833
+ }
1834
+ }
1835
+ );
1836
+ }
1837
+ state.calls += 1;
1838
+ state.reservationKeys.add(reservationKey);
1839
+ this.circuitBreakerRuns.set(identity.key, state);
1840
+ }
1841
+ resolveRunIdentity(request) {
1842
+ const customerId = request.customerId?.trim();
1843
+ const runId = request.runId?.trim();
1844
+ if (!customerId || !runId) return void 0;
1845
+ return {
1846
+ key: `${customerId}\0${runId}`,
1847
+ customerId,
1848
+ runId
1849
+ };
1850
+ }
1851
+ createCircuitBreakerDecision(customerId, runId, calls) {
1852
+ const limit = this.circuitBreaker?.maxCallsPerRun ?? 0;
1853
+ const allowed = calls < limit;
1854
+ return {
1855
+ allowed,
1856
+ ...allowed ? {} : { reason: "max_calls_per_run" },
1857
+ customerId,
1858
+ runId,
1859
+ calls,
1860
+ limit,
1861
+ remaining: Math.max(0, limit - calls)
1862
+ };
1863
+ }
1864
+ expireInactiveRuns() {
1865
+ if (!this.circuitBreaker || this.circuitBreakerRuns.size === 0) return;
1866
+ const expiredBefore = Date.now() - this.circuitBreaker.runInactivityMs;
1867
+ for (const [key, state] of this.circuitBreakerRuns) {
1868
+ if (state.lastSeenAtMs < expiredBefore) {
1869
+ this.circuitBreakerRuns.delete(key);
1870
+ }
1871
+ }
1872
+ }
1231
1873
  async request(path, payload, options) {
1232
1874
  const url = new URL(path, this.baseUrl).toString();
1233
1875
  const body = payload !== void 0 ? JSON.stringify(payload) : void 0;
@@ -1415,7 +2057,7 @@ var UsageTapClient = class {
1415
2057
  ...this.defaultHeaders,
1416
2058
  [SDK_HEADER]: `js/${SDK_VERSION}`,
1417
2059
  "content-type": "application/json",
1418
- accept: CANONICAL_MEDIA_TYPE
2060
+ accept: CANONICAL_MEDIA_TYPE2
1419
2061
  };
1420
2062
  if (!HAS_WINDOW) {
1421
2063
  headers["user-agent"] = `${USER_AGENT}/${SDK_VERSION}`;
@@ -1468,7 +2110,8 @@ var UsageTapClient = class {
1468
2110
  }
1469
2111
  toHttpError(status, payload, correlationId) {
1470
2112
  const code = mapStatusToErrorCode(status);
1471
- const retryable = isRetryableStatus(status);
2113
+ const apiCode = payload?.error?.code ?? payload?.result?.code ?? "UNKNOWN";
2114
+ const retryable = isRetryableStatus(status) || isRetryableApiCode(apiCode);
1472
2115
  const message = payload?.error?.message ?? payload?.result?.message ?? `UsageTap responded with HTTP ${status}`;
1473
2116
  return new UsageTapError(code, message, {
1474
2117
  status,
@@ -1501,7 +2144,7 @@ function isRetryableStatus(status) {
1501
2144
  }
1502
2145
  function isRetryableApiCode(code) {
1503
2146
  const normalized = code.toUpperCase();
1504
- return normalized.includes("TRANSIENT") || normalized.includes("RETRY") || normalized.includes("TIMEOUT") || normalized.includes("THROTTLE") || normalized.includes("RATE_LIMIT");
2147
+ return normalized === "PAYG_CONFLICT" || normalized.includes("TRANSIENT") || normalized.includes("RETRY") || normalized.includes("TIMEOUT") || normalized.includes("THROTTLE") || normalized.includes("RATE_LIMIT");
1505
2148
  }
1506
2149
  function mapApiCodeToError(code) {
1507
2150
  const normalized = code.toUpperCase();
@@ -1615,7 +2258,123 @@ var AnthropicPromptCompressionStats = class {
1615
2258
  return this.totalOriginalCharacters > 0 ? this.totalCharactersSaved / this.totalOriginalCharacters : 0;
1616
2259
  }
1617
2260
  };
2261
+ var DeferredAnthropicApiPromise = class extends Promise {
2262
+ responseEnvelope;
2263
+ constructor(execute) {
2264
+ super((resolve) => resolve(void 0));
2265
+ this.responseEnvelope = Promise.resolve().then(execute);
2266
+ }
2267
+ then(onfulfilled, onrejected) {
2268
+ return this.responseEnvelope.then((result) => result.data).then(onfulfilled, onrejected);
2269
+ }
2270
+ catch(onrejected) {
2271
+ return this.then(void 0, onrejected);
2272
+ }
2273
+ finally(onfinally) {
2274
+ return this.responseEnvelope.then((result) => result.data).finally(onfinally ?? void 0);
2275
+ }
2276
+ withResponse() {
2277
+ return this.responseEnvelope;
2278
+ }
2279
+ async asResponse() {
2280
+ const result = await this.responseEnvelope;
2281
+ if (!("response" in result)) {
2282
+ throw new TypeError("The wrapped Anthropic result does not expose a raw response");
2283
+ }
2284
+ return result.response;
2285
+ }
2286
+ };
2287
+ async function resolveAnthropicResponseEnvelope(value) {
2288
+ if (isObjectRecord(value) && typeof value.withResponse === "function") {
2289
+ const withResponse = value.withResponse;
2290
+ const result = await withResponse.call(value);
2291
+ if (!isObjectRecord(result) || !("data" in result)) {
2292
+ throw new TypeError("Anthropic create().withResponse() returned an invalid result");
2293
+ }
2294
+ return result;
2295
+ }
2296
+ return { data: await value };
2297
+ }
1618
2298
  var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
2299
+ function withSampling(client, options = {}) {
2300
+ if (!client?.messages || typeof client.messages.create !== "function" || !options) {
2301
+ throw new UsageTapError(
2302
+ "USAGETAP_BAD_REQUEST",
2303
+ "withSampling requires an Anthropic client and sampling options"
2304
+ );
2305
+ }
2306
+ const { apiKey, usageTapClient, ...policy } = options;
2307
+ const localPolicy = typeof policy.rate === "number" ? policy : void 0;
2308
+ const usageTap = usageTapClient ?? new UsageTapClient({ apiKey, sampling: localPolicy });
2309
+ const originalCreate = client.messages.create.bind(client.messages);
2310
+ const create = (params, requestOptions) => {
2311
+ return new DeferredAnthropicApiPromise(async () => {
2312
+ const { usageTap: callContext = {}, ...providerOptions } = requestOptions ?? {};
2313
+ const decision = params.stream === true ? Promise.resolve(false) : usageTap.shouldSampleAsync({
2314
+ customerId: callContext.customerId,
2315
+ feature: callContext.feature,
2316
+ input: params
2317
+ }, localPolicy);
2318
+ const startedAt = Date.now();
2319
+ try {
2320
+ const result = await resolveAnthropicResponseEnvelope(originalCreate(
2321
+ params,
2322
+ Object.keys(providerOptions).length ? providerOptions : void 0
2323
+ ));
2324
+ const response = result.data;
2325
+ const selected = await decision;
2326
+ if (selected) {
2327
+ const record = isObjectRecord(response) ? response : {};
2328
+ await usageTap.captureSample({
2329
+ customerId: callContext.customerId,
2330
+ feature: callContext.feature,
2331
+ environment: callContext.environment,
2332
+ tags: callContext.tags,
2333
+ provider: "anthropic",
2334
+ model: readString(record.model) ?? readString(params.model),
2335
+ input: params,
2336
+ output: response,
2337
+ usage: record.usage,
2338
+ latencyMs: Date.now() - startedAt
2339
+ }).catch(() => void 0);
2340
+ }
2341
+ return result;
2342
+ } catch (error) {
2343
+ const selected = await decision;
2344
+ if (selected) {
2345
+ await usageTap.captureSample({
2346
+ customerId: callContext.customerId,
2347
+ feature: callContext.feature,
2348
+ environment: callContext.environment,
2349
+ tags: callContext.tags,
2350
+ provider: "anthropic",
2351
+ model: readString(params.model),
2352
+ input: params,
2353
+ latencyMs: Date.now() - startedAt,
2354
+ error: error instanceof Error ? { name: error.name, message: error.message } : { message: String(error) }
2355
+ }).catch(() => void 0);
2356
+ }
2357
+ throw error;
2358
+ }
2359
+ });
2360
+ };
2361
+ const messages = new Proxy(client.messages, {
2362
+ get(target, prop, receiver) {
2363
+ if (prop === "create") return create;
2364
+ return safeReflectGet(target, prop, receiver);
2365
+ }
2366
+ });
2367
+ return new Proxy(client, {
2368
+ get(target, prop, receiver) {
2369
+ if (prop === "messages") return messages;
2370
+ if (prop === "unwrap") return () => target;
2371
+ return safeReflectGet(target, prop, receiver);
2372
+ }
2373
+ });
2374
+ }
2375
+ function safeReflectGet(target, prop, receiver) {
2376
+ return Reflect.get(target, prop, receiver);
2377
+ }
1619
2378
  function withCompression(client, options = {}) {
1620
2379
  if (!client?.messages || typeof client.messages.create !== "function") {
1621
2380
  throw new UsageTapError(
@@ -1626,17 +2385,24 @@ function withCompression(client, options = {}) {
1626
2385
  const {
1627
2386
  apiKey,
1628
2387
  usageTapClient,
2388
+ sampling,
1629
2389
  ...compressionOverrides
1630
2390
  } = options;
1631
2391
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
1632
2392
  const compression = {
1633
2393
  provider: "usagetap",
2394
+ roles: { user: { enabled: true, aggressiveness: 0.2 } },
1634
2395
  minContextTokens: DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS,
1635
2396
  ...compressionOverrides
1636
2397
  };
1637
2398
  const originalCreate = client.messages.create.bind(client.messages);
1638
2399
  const wrappedCreate = (params, requestOptions) => {
1639
- return compressAnthropicParams(params, usageTap, compression).then((outcome) => originalCreate(outcome.params, requestOptions));
2400
+ return new DeferredAnthropicApiPromise(async () => {
2401
+ const outcome = await compressAnthropicParams(params, usageTap, compression);
2402
+ return resolveAnthropicResponseEnvelope(
2403
+ originalCreate(outcome.params, requestOptions)
2404
+ );
2405
+ });
1640
2406
  };
1641
2407
  const proxiedMessages = new Proxy(client.messages, {
1642
2408
  get(target, prop, receiver) {
@@ -1644,13 +2410,18 @@ function withCompression(client, options = {}) {
1644
2410
  return Reflect.get(target, prop, receiver);
1645
2411
  }
1646
2412
  });
1647
- return new Proxy(client, {
2413
+ const compressed = new Proxy(client, {
1648
2414
  get(target, prop, receiver) {
1649
2415
  if (prop === "messages") return proxiedMessages;
1650
2416
  if (prop === "unwrap") return () => target;
1651
2417
  return Reflect.get(target, prop, receiver);
1652
2418
  }
1653
2419
  });
2420
+ return sampling ? withSampling(compressed, {
2421
+ ...sampling === true ? {} : sampling,
2422
+ apiKey,
2423
+ usageTapClient
2424
+ }) : compressed;
1654
2425
  }
1655
2426
  function withMetering(client, customer) {
1656
2427
  const config = typeof customer === "string" ? { customerId: customer } : customer;
@@ -1665,6 +2436,7 @@ function withMetering(client, customer) {
1665
2436
  usageTapClient,
1666
2437
  applyVendorHints,
1667
2438
  promptCompression,
2439
+ sampling,
1668
2440
  ...defaultContext
1669
2441
  } = config;
1670
2442
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
@@ -1672,7 +2444,8 @@ function withMetering(client, customer) {
1672
2444
  return wrapAnthropic(client, usageTap, {
1673
2445
  defaultContext,
1674
2446
  applyVendorHints,
1675
- promptCompression: normalizedCompression
2447
+ promptCompression: normalizedCompression,
2448
+ sampling
1676
2449
  });
1677
2450
  }
1678
2451
  function wrapAnthropic(client, usageTap, options = {}) {
@@ -1685,6 +2458,7 @@ function wrapAnthropic(client, usageTap, options = {}) {
1685
2458
  const defaultContext = options.defaultContext;
1686
2459
  const applyVendorHints = options.applyVendorHints !== false;
1687
2460
  const defaultPromptCompression = normalizePromptCompressionOptions(options.promptCompression);
2461
+ const defaultSampling = normalizeMeteredAnthropicSampling(options.sampling);
1688
2462
  const promptCompressionStats = new AnthropicPromptCompressionStats();
1689
2463
  const proxiedMessages = createMessagesProxy(
1690
2464
  client.messages,
@@ -1692,7 +2466,8 @@ function wrapAnthropic(client, usageTap, options = {}) {
1692
2466
  defaultContext,
1693
2467
  applyVendorHints,
1694
2468
  defaultPromptCompression,
1695
- promptCompressionStats
2469
+ promptCompressionStats,
2470
+ defaultSampling
1696
2471
  );
1697
2472
  const handler = {
1698
2473
  get(target, prop, receiver) {
@@ -1710,7 +2485,7 @@ function wrapAnthropic(client, usageTap, options = {}) {
1710
2485
  };
1711
2486
  return new Proxy(client, handler);
1712
2487
  }
1713
- function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2488
+ function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling) {
1714
2489
  if (typeof resource.create !== "function") {
1715
2490
  throw new UsageTapError("USAGETAP_BAD_REQUEST", "wrapAnthropic requires client.messages.create");
1716
2491
  }
@@ -1722,19 +2497,22 @@ function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHint
1722
2497
  withUsage,
1723
2498
  promptCompression
1724
2499
  } = splitUsageOptions(options);
1725
- return invokeMessagesCreate({
1726
- params,
1727
- requestOptions,
1728
- usageContext,
1729
- withUsage,
1730
- promptCompression,
1731
- originalCreate,
1732
- usageTap,
1733
- defaultContext,
1734
- applyVendorHints,
1735
- defaultPromptCompression,
1736
- promptCompressionStats
1737
- });
2500
+ return new DeferredAnthropicApiPromise(
2501
+ () => invokeMessagesCreate({
2502
+ params,
2503
+ requestOptions,
2504
+ usageContext,
2505
+ withUsage,
2506
+ promptCompression,
2507
+ originalCreate,
2508
+ usageTap,
2509
+ defaultContext,
2510
+ applyVendorHints,
2511
+ defaultPromptCompression,
2512
+ promptCompressionStats,
2513
+ defaultSampling
2514
+ })
2515
+ );
1738
2516
  });
1739
2517
  const handler = {
1740
2518
  get(target, prop, receiver) {
@@ -1747,13 +2525,25 @@ function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHint
1747
2525
  return new Proxy(resource, handler);
1748
2526
  }
1749
2527
  async function invokeMessagesCreate(args) {
1750
- const beginRequest = resolveBeginRequest(args.defaultContext, args.usageContext);
2528
+ const beginRequest = resolveBeginRequest(
2529
+ args.defaultContext,
2530
+ args.usageContext
2531
+ );
1751
2532
  const begin = await args.usageTap.beginCall(
1752
2533
  beginRequest,
1753
2534
  beginCallOptions(args.withUsage)
1754
2535
  );
1755
2536
  const state = createCallState(beginRequest, begin);
1756
2537
  const ctx = createUsageContext(state);
2538
+ const sampleStartedAt = Date.now();
2539
+ const wantsStream = isStreamingRequest(args.params);
2540
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredAnthropicSampleDecision({
2541
+ usageTap: args.usageTap,
2542
+ sampling: args.defaultSampling,
2543
+ beginRequest,
2544
+ input: args.params
2545
+ });
2546
+ let providerCompleted = false;
1757
2547
  try {
1758
2548
  const hintedParams = args.applyVendorHints ? applyAnthropicVendorHints(args.params, ctx.begin.data.vendorHints) : args.params;
1759
2549
  const finalParams = await compressAnthropicParamsForCall({
@@ -1766,21 +2556,38 @@ async function invokeMessagesCreate(args) {
1766
2556
  withUsage: args.withUsage,
1767
2557
  operation: "messages.create"
1768
2558
  });
2559
+ ctx.setUsage(anthropicRequestExecutionMetadata(finalParams));
1769
2560
  const request = attachCorrelationHeader(args.requestOptions, ctx.begin.correlationId);
1770
- const response = await args.originalCreate(finalParams, request);
2561
+ const providerResult = await resolveAnthropicResponseEnvelope(
2562
+ args.originalCreate(finalParams, request)
2563
+ );
2564
+ const response = providerResult.data;
2565
+ providerCompleted = true;
1771
2566
  if (isStreamingRequest(finalParams)) {
1772
2567
  ensureAsyncIterable(response, "messages.create");
1773
- return wrapAnthropicStreamForUsageTap(
1774
- response,
1775
- state,
1776
- args.usageTap,
1777
- args.withUsage,
1778
- readString(finalParams.model)
1779
- );
2568
+ return {
2569
+ ...providerResult,
2570
+ data: wrapAnthropicStreamForUsageTap(
2571
+ response,
2572
+ state,
2573
+ args.usageTap,
2574
+ args.withUsage,
2575
+ readString(finalParams.model)
2576
+ )
2577
+ };
1780
2578
  }
1781
2579
  inferAnthropicUsage(response, readString(finalParams.model), ctx);
2580
+ await captureMeteredAnthropicSample({
2581
+ usageTap: args.usageTap,
2582
+ sampling: args.defaultSampling,
2583
+ decision: sampleDecision,
2584
+ state,
2585
+ input: args.params,
2586
+ response,
2587
+ startedAt: sampleStartedAt
2588
+ });
1782
2589
  await finalizeCall(state, args.usageTap, args.withUsage);
1783
- return response;
2590
+ return providerResult;
1784
2591
  } catch (error) {
1785
2592
  if (!state.error) {
1786
2593
  state.error = {
@@ -1788,10 +2595,80 @@ async function invokeMessagesCreate(args) {
1788
2595
  message: error instanceof Error ? error.message : String(error)
1789
2596
  };
1790
2597
  }
2598
+ if (!providerCompleted) {
2599
+ await captureMeteredAnthropicSample({
2600
+ usageTap: args.usageTap,
2601
+ sampling: args.defaultSampling,
2602
+ decision: sampleDecision,
2603
+ state,
2604
+ input: args.params,
2605
+ error,
2606
+ startedAt: sampleStartedAt
2607
+ });
2608
+ }
1791
2609
  await finalizeCall(state, args.usageTap, args.withUsage);
1792
2610
  throw error;
1793
2611
  }
1794
2612
  }
2613
+ function normalizeMeteredAnthropicSampling(options) {
2614
+ if (!options) return void 0;
2615
+ if (options === true) return {};
2616
+ return {
2617
+ policy: typeof options.rate === "number" ? options : void 0
2618
+ };
2619
+ }
2620
+ function startMeteredAnthropicSampleDecision({
2621
+ usageTap,
2622
+ sampling,
2623
+ beginRequest,
2624
+ input
2625
+ }) {
2626
+ if (!sampling) return Promise.resolve(false);
2627
+ return usageTap.shouldSampleAsync(
2628
+ {
2629
+ customerId: beginRequest.customerId,
2630
+ feature: beginRequest.feature,
2631
+ input
2632
+ },
2633
+ sampling.policy
2634
+ );
2635
+ }
2636
+ async function captureMeteredAnthropicSample({
2637
+ usageTap,
2638
+ sampling,
2639
+ decision,
2640
+ state,
2641
+ input,
2642
+ response,
2643
+ error,
2644
+ startedAt
2645
+ }) {
2646
+ if (!sampling) return;
2647
+ let selected = false;
2648
+ try {
2649
+ selected = await decision;
2650
+ } catch {
2651
+ return;
2652
+ }
2653
+ if (!selected) return;
2654
+ const record = isObjectRecord(response) ? response : {};
2655
+ await usageTap.captureSample({
2656
+ sampleId: state.begin.data.callId,
2657
+ callId: state.begin.data.callId,
2658
+ customerId: state.beginRequest.customerId,
2659
+ feature: state.beginRequest.feature,
2660
+ tags: state.beginRequest.tags,
2661
+ provider: "anthropic",
2662
+ model: readString(record.model) ?? readString(input.model),
2663
+ input,
2664
+ ...response === void 0 ? {} : { output: response },
2665
+ usage: record.usage,
2666
+ latencyMs: Date.now() - startedAt,
2667
+ ...error === void 0 ? {} : {
2668
+ error: error instanceof Error ? { name: error.name, message: error.message } : { message: String(error) }
2669
+ }
2670
+ }).catch(() => void 0);
2671
+ }
1795
2672
  async function compressAnthropicParamsForCall(args) {
1796
2673
  const compression = resolveEffectivePromptCompressionOptions(
1797
2674
  args.defaultPromptCompression,
@@ -1871,6 +2748,10 @@ async function compressAnthropicParams(params, usageTap, compression, signal) {
1871
2748
  const result = await usageTap.compressPromptMessages(source, {
1872
2749
  provider: "usagetap",
1873
2750
  failOpen: compression.failOpen,
2751
+ mode: compression.mode,
2752
+ latencyBudgetMs: compression.latencyBudgetMs,
2753
+ compactEmptyUserMessages: compression.compactEmptyUserMessages,
2754
+ compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
1874
2755
  aggressiveness: resolveMessageEndpointAggressiveness(compression),
1875
2756
  signal
1876
2757
  });
@@ -2259,6 +3140,8 @@ function resolveBeginRequest(defaults, override) {
2259
3140
  if (requested) begin.requested = requested;
2260
3141
  const feature = current.feature ?? base.feature;
2261
3142
  if (feature) begin.feature = feature;
3143
+ const runId = current.runId ?? base.runId;
3144
+ if (runId) begin.runId = runId;
2262
3145
  const idempotency = current.idempotency ?? base.idempotency;
2263
3146
  if (idempotency) begin.idempotency = idempotency;
2264
3147
  const idempotencyKey = current.idempotencyKey ?? base.idempotencyKey;
@@ -2267,10 +3150,14 @@ function resolveBeginRequest(defaults, override) {
2267
3150
  if (customerName) begin.customerName = customerName;
2268
3151
  const customerEmail = current.customerEmail ?? base.customerEmail;
2269
3152
  if (customerEmail) begin.customerEmail = customerEmail;
3153
+ const customerUserId = current.customerUserId ?? base.customerUserId;
3154
+ if (customerUserId) begin.customerUserId = customerUserId;
3155
+ const customerUserName = current.customerUserName ?? base.customerUserName;
3156
+ if (customerUserName) begin.customerUserName = customerUserName;
3157
+ const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
3158
+ if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
2270
3159
  const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
2271
3160
  if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
2272
- const holdUsd = current.holdUsd ?? base.holdUsd;
2273
- if (typeof holdUsd === "number") begin.holdUsd = holdUsd;
2274
3161
  const batch = current.batch ?? base.batch;
2275
3162
  if (typeof batch === "boolean") begin.batch = batch;
2276
3163
  const pricingMode = current.pricingMode ?? base.pricingMode;
@@ -2282,6 +3169,11 @@ function resolveBeginRequest(defaults, override) {
2282
3169
  }
2283
3170
  function createCallState(beginRequest, begin) {
2284
3171
  const usage = {};
3172
+ const pricingMode = begin.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
3173
+ if (pricingMode) {
3174
+ usage.pricingMode = pricingMode;
3175
+ usage.batch = pricingMode === "batch";
3176
+ }
2285
3177
  const stripeCustomerId = typeof begin.data.stripeCustomerId === "string" ? begin.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
2286
3178
  if (stripeCustomerId) {
2287
3179
  usage.stripeCustomerId = stripeCustomerId;
@@ -2345,10 +3237,12 @@ function inferAnthropicUsage(response, fallbackModel, ctx) {
2345
3237
  }
2346
3238
  function extractAnthropicUsage(payload, fallbackModel) {
2347
3239
  if (!isObjectRecord(payload)) {
2348
- return fallbackModel ? { modelUsed: fallbackModel } : void 0;
3240
+ return { providerUsed: "anthropic", ...fallbackModel ? { modelUsed: fallbackModel } : {} };
2349
3241
  }
2350
3242
  const usage = findAnthropicUsageRecord(payload);
2351
- const result = {};
3243
+ const result = {
3244
+ providerUsed: "anthropic"
3245
+ };
2352
3246
  const model = readString(payload.model) ?? readString(payload.message?.model) ?? fallbackModel;
2353
3247
  if (model) {
2354
3248
  result.modelUsed = model;
@@ -2358,6 +3252,21 @@ function extractAnthropicUsage(payload, fallbackModel) {
2358
3252
  }
2359
3253
  return Object.keys(result).length ? result : void 0;
2360
3254
  }
3255
+ function anthropicRequestExecutionMetadata(params) {
3256
+ const outputConfig = isObjectRecord(params.output_config) ? params.output_config : void 0;
3257
+ const thinking = isObjectRecord(params.thinking) ? params.thinking : void 0;
3258
+ const rawEffort = outputConfig?.effort;
3259
+ const effort = rawEffort === "none" || rawEffort === "minimal" || rawEffort === "low" || rawEffort === "medium" || rawEffort === "high" || rawEffort === "xhigh" || rawEffort === "max" ? rawEffort : void 0;
3260
+ const mode = readString(thinking?.type);
3261
+ const rawBudget = readNumber(thinking?.budget_tokens);
3262
+ const budget = rawBudget !== void 0 && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
3263
+ return {
3264
+ providerUsed: "anthropic",
3265
+ ...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
3266
+ ...mode ? { reasoningMode: mode } : {},
3267
+ ...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
3268
+ };
3269
+ }
2361
3270
  function findAnthropicUsageRecord(payload) {
2362
3271
  if (isObjectRecord(payload.usage)) {
2363
3272
  return payload.usage;
@@ -2368,10 +3277,6 @@ function findAnthropicUsageRecord(payload) {
2368
3277
  return void 0;
2369
3278
  }
2370
3279
  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
3280
  const outputTokens = readNumber(usageRecord.output_tokens ?? usageRecord.completion_tokens);
2376
3281
  if (outputTokens !== void 0) {
2377
3282
  usage.responseTokens = outputTokens;
@@ -2382,6 +3287,26 @@ function applyAnthropicUsageRecord(usage, usageRecord) {
2382
3287
  if (cachedInputTokens !== void 0) {
2383
3288
  usage.cachedInputTokens = cachedInputTokens;
2384
3289
  }
3290
+ const cacheWriteInputTokens = readNumber(
3291
+ usageRecord.cache_creation_input_tokens ?? usageRecord.prompt_cache_miss_tokens ?? usageRecord.cache_write_tokens
3292
+ );
3293
+ if (cacheWriteInputTokens !== void 0) {
3294
+ usage.cacheWriteInputTokens = cacheWriteInputTokens;
3295
+ }
3296
+ if (isObjectRecord(usageRecord.cache_creation)) {
3297
+ const cacheWrite5m = readNumber(
3298
+ usageRecord.cache_creation.ephemeral_5m_input_tokens
3299
+ );
3300
+ const cacheWrite1h = readNumber(
3301
+ usageRecord.cache_creation.ephemeral_1h_input_tokens
3302
+ );
3303
+ if (cacheWrite5m !== void 0) usage.cacheWrite5mInputTokens = cacheWrite5m;
3304
+ if (cacheWrite1h !== void 0) usage.cacheWrite1hInputTokens = cacheWrite1h;
3305
+ }
3306
+ const baseInputTokens = readNumber(usageRecord.input_tokens ?? usageRecord.prompt_tokens);
3307
+ if (baseInputTokens !== void 0) {
3308
+ usage.inputTokens = baseInputTokens + (cachedInputTokens ?? 0) + (cacheWriteInputTokens ?? 0);
3309
+ }
2385
3310
  }
2386
3311
  function accumulateAnthropicStreamUsage(chunk, fallbackModel, state) {
2387
3312
  const usage = extractAnthropicUsage(chunk, fallbackModel);
@@ -2608,6 +3533,7 @@ function isIteratorResult(value) {
2608
3533
  exports.AnthropicPromptCompressionStats = AnthropicPromptCompressionStats;
2609
3534
  exports.withCompression = withCompression;
2610
3535
  exports.withMetering = withMetering;
3536
+ exports.withSampling = withSampling;
2611
3537
  exports.wrapAnthropic = wrapAnthropic;
2612
3538
  //# sourceMappingURL=index.cjs.map
2613
3539
  //# sourceMappingURL=index.cjs.map