@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
@@ -240,6 +240,11 @@ async function compressMessagesWithUsageTap(options) {
240
240
  aggressiveness,
241
241
  "UsageTap prompt message compression"
242
242
  );
243
+ if (options.latencyBudgetMs !== void 0 && (!Number.isFinite(options.latencyBudgetMs) || options.latencyBudgetMs < 0)) {
244
+ throw new Error(
245
+ "UsageTap prompt message compression latencyBudgetMs must be a non-negative number"
246
+ );
247
+ }
243
248
  const original = stableStringifyInput(options.input);
244
249
  const headers = {
245
250
  "content-type": "application/json"
@@ -254,7 +259,15 @@ async function compressMessagesWithUsageTap(options) {
254
259
  headers,
255
260
  body: JSON.stringify({
256
261
  ...cloneInputRecord(options.input),
257
- compression_settings: { aggressiveness }
262
+ compression_settings: {
263
+ aggressiveness,
264
+ ...options.mode === void 0 ? {} : { mode: options.mode },
265
+ ...options.latencyBudgetMs === void 0 ? {} : { latency_budget_ms: options.latencyBudgetMs },
266
+ ...options.compactEmptyUserMessages === void 0 ? {} : { compact_empty_user_messages: options.compactEmptyUserMessages },
267
+ ...options.compactDuplicateUserTextParts === void 0 ? {} : {
268
+ compact_duplicate_user_text_parts: options.compactDuplicateUserTextParts
269
+ }
270
+ }
258
271
  }),
259
272
  signal: options.signal
260
273
  }
@@ -750,10 +763,369 @@ function scalarToToon(value) {
750
763
  return JSON.stringify(text);
751
764
  }
752
765
 
766
+ // src/resources.ts
767
+ var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
768
+ var DEFAULT_GATEWAY_BASE_URL = "https://gateway.usagetap.com";
769
+ function normalizedBaseUrl(value) {
770
+ return `${value.replace(/\/+$/, "")}/`;
771
+ }
772
+ function errorMessage(payload, status) {
773
+ if (payload && typeof payload === "object") {
774
+ const record = payload;
775
+ const error = record.error;
776
+ if (error && typeof error === "object") {
777
+ const message2 = error.message;
778
+ if (typeof message2 === "string" && message2) return message2;
779
+ }
780
+ const message = record.message;
781
+ if (typeof message === "string" && message) return message;
782
+ }
783
+ return `UsageTap request failed with HTTP ${status}`;
784
+ }
785
+ function errorCode(status) {
786
+ if (status === 401 || status === 403) return "USAGETAP_AUTH_ERROR";
787
+ if (status === 429) return "USAGETAP_RATE_LIMITED";
788
+ if (status >= 500) return "USAGETAP_SERVER_ERROR";
789
+ return "USAGETAP_BAD_REQUEST";
790
+ }
791
+ var ResourceTransport = class {
792
+ baseUrl;
793
+ apiKey;
794
+ fetchImpl;
795
+ defaultHeaders;
796
+ sdkVersion;
797
+ constructor(baseUrl, config) {
798
+ this.baseUrl = normalizedBaseUrl(baseUrl);
799
+ this.apiKey = config.apiKey;
800
+ this.fetchImpl = config.fetchImpl;
801
+ this.defaultHeaders = config.headers ?? {};
802
+ this.sdkVersion = config.sdkVersion;
803
+ }
804
+ async request(request) {
805
+ const body = request.body === void 0 ? void 0 : JSON.stringify(request.body);
806
+ const headers = {
807
+ ...this.defaultHeaders,
808
+ accept: request.response === "data" ? CANONICAL_MEDIA_TYPE : request.response === "ndjson" ? "application/x-ndjson" : "application/json",
809
+ authorization: `Bearer ${this.apiKey}`,
810
+ "x-usage-sdk": `js/${this.sdkVersion}`,
811
+ ...body ? { "content-type": "application/json" } : {},
812
+ ...request.options?.idempotencyKey ? { "idempotency-key": request.options.idempotencyKey } : {},
813
+ ...request.options?.headers
814
+ };
815
+ let response;
816
+ try {
817
+ response = await this.fetchImpl(
818
+ new URL(request.path.replace(/^\/+/, ""), this.baseUrl),
819
+ {
820
+ method: request.method,
821
+ headers,
822
+ body,
823
+ signal: request.options?.signal
824
+ }
825
+ );
826
+ } catch (error) {
827
+ throw new UsageTapError(
828
+ "USAGETAP_NETWORK_ERROR",
829
+ "Failed to reach UsageTap",
830
+ { retryable: true, cause: error }
831
+ );
832
+ }
833
+ const requestId = response.headers.get("x-request-id") ?? response.headers.get("x-usage-correlation-id") ?? void 0;
834
+ const text = await response.text();
835
+ let payload;
836
+ if (text && request.response !== "ndjson") {
837
+ try {
838
+ payload = JSON.parse(text);
839
+ } catch (error) {
840
+ throw new UsageTapError(
841
+ "USAGETAP_INVALID_RESPONSE",
842
+ "UsageTap returned invalid JSON",
843
+ { status: response.status, correlationId: requestId, cause: error }
844
+ );
845
+ }
846
+ }
847
+ if (!response.ok) {
848
+ throw new UsageTapError(
849
+ errorCode(response.status),
850
+ errorMessage(payload, response.status),
851
+ {
852
+ status: response.status,
853
+ retryable: response.status === 429 || response.status >= 500,
854
+ correlationId: requestId,
855
+ details: payload && typeof payload === "object" ? payload : void 0
856
+ }
857
+ );
858
+ }
859
+ if (request.response === "ndjson") {
860
+ if (!text.trim()) return [];
861
+ try {
862
+ return text.trim().split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
863
+ } catch (error) {
864
+ throw new UsageTapError(
865
+ "USAGETAP_INVALID_RESPONSE",
866
+ "UsageTap returned invalid NDJSON",
867
+ { status: response.status, correlationId: requestId, cause: error }
868
+ );
869
+ }
870
+ }
871
+ if (request.response === "data") {
872
+ if (!payload || typeof payload !== "object" || !("data" in payload)) {
873
+ throw new UsageTapError(
874
+ "USAGETAP_INVALID_RESPONSE",
875
+ "UsageTap response missing data",
876
+ { status: response.status, correlationId: requestId }
877
+ );
878
+ }
879
+ return payload.data;
880
+ }
881
+ if (payload === void 0) {
882
+ throw new UsageTapError(
883
+ "USAGETAP_INVALID_RESPONSE",
884
+ "UsageTap response was empty",
885
+ { status: response.status, correlationId: requestId }
886
+ );
887
+ }
888
+ return payload;
889
+ }
890
+ };
891
+ function resourceId(value, keys, label) {
892
+ const id = typeof value === "string" ? value : keys.map((key) => value[key]).find((candidate) => Boolean(candidate?.trim()));
893
+ if (!id?.trim()) {
894
+ throw new UsageTapError(
895
+ "USAGETAP_BAD_REQUEST",
896
+ `${label} requires a non-empty ID`
897
+ );
898
+ }
899
+ return id.trim();
900
+ }
901
+ function terminalSummary(status) {
902
+ return status === "COMPLETE" || status === "FAILED";
903
+ }
904
+ function terminalGatewayBatch(status) {
905
+ return ["completed", "failed", "expired", "cancelled"].includes(status);
906
+ }
907
+ function validateWaitOptions(options) {
908
+ const pollIntervalMs = options.pollIntervalMs ?? 1500;
909
+ const timeoutMs = options.timeoutMs ?? 3 * 6e4;
910
+ if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 0) {
911
+ throw new UsageTapError(
912
+ "USAGETAP_BAD_REQUEST",
913
+ "pollIntervalMs must be a non-negative number"
914
+ );
915
+ }
916
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
917
+ throw new UsageTapError(
918
+ "USAGETAP_BAD_REQUEST",
919
+ "timeoutMs must be a positive number"
920
+ );
921
+ }
922
+ return { pollIntervalMs, timeoutMs };
923
+ }
924
+ var SummarizationResource = class {
925
+ summaries;
926
+ batches;
927
+ profiles;
928
+ measurements;
929
+ transport;
930
+ constructor(config) {
931
+ this.transport = new ResourceTransport(config.apiBaseUrl, config);
932
+ this.summaries = {
933
+ create: (params, options) => this.transport.request({
934
+ method: "POST",
935
+ path: "/v1/compression/summaries",
936
+ body: params,
937
+ options,
938
+ response: "data"
939
+ }),
940
+ retrieve: (jobId, options) => this.transport.request({
941
+ method: "GET",
942
+ path: `/v1/compression/jobs/${encodeURIComponent(
943
+ resourceId(jobId, ["jobId"], "summaries.retrieve")
944
+ )}`,
945
+ options,
946
+ response: "data"
947
+ }),
948
+ wait: (job, options) => this.waitForSummary(job, options)
949
+ };
950
+ this.batches = {
951
+ create: (params, options) => this.transport.request({
952
+ method: "POST",
953
+ path: "/v1/compression/batches",
954
+ body: params,
955
+ options,
956
+ response: "data"
957
+ }),
958
+ retrieve: (batchId, options) => this.transport.request({
959
+ method: "GET",
960
+ path: `/v1/compression/batches/${encodeURIComponent(
961
+ resourceId(batchId, ["batchId"], "summarization.batches.retrieve")
962
+ )}`,
963
+ options,
964
+ response: "data"
965
+ }),
966
+ wait: (batch, options) => this.waitForBatch(batch, options)
967
+ };
968
+ this.profiles = {
969
+ retrieve: (profile, options) => this.transport.request({
970
+ method: "GET",
971
+ path: `/v1/compression/profiles/${encodeURIComponent(
972
+ resourceId(profile, [], "summarization.profiles.retrieve")
973
+ )}`,
974
+ options,
975
+ response: "data"
976
+ })
977
+ };
978
+ this.measurements = {
979
+ create: (params, options) => this.transport.request({
980
+ method: "POST",
981
+ path: "/v1/compression/measurements",
982
+ body: params,
983
+ options,
984
+ response: "data"
985
+ })
986
+ };
987
+ }
988
+ async waitForSummary(value, options = {}) {
989
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
990
+ const deadline = Date.now() + timeoutMs;
991
+ let job = typeof value === "string" ? await this.summaries.retrieve(value, options) : value;
992
+ while (!terminalSummary(job.status)) {
993
+ if (Date.now() >= deadline) {
994
+ throw new UsageTapError(
995
+ "USAGETAP_RETRY_EXHAUSTED",
996
+ `Summarization job ${job.jobId} did not finish before timeout`,
997
+ { retryable: true }
998
+ );
999
+ }
1000
+ await sleep(pollIntervalMs, options.signal);
1001
+ job = await this.summaries.retrieve(job.jobId, options);
1002
+ }
1003
+ return job;
1004
+ }
1005
+ async waitForBatch(value, options = {}) {
1006
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
1007
+ const deadline = Date.now() + timeoutMs;
1008
+ let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
1009
+ while (!terminalSummary(batch.status)) {
1010
+ if (Date.now() >= deadline) {
1011
+ throw new UsageTapError(
1012
+ "USAGETAP_RETRY_EXHAUSTED",
1013
+ `Summarization batch ${batch.batchId} did not finish before timeout`,
1014
+ { retryable: true }
1015
+ );
1016
+ }
1017
+ await sleep(pollIntervalMs, options.signal);
1018
+ batch = await this.batches.retrieve(batch.batchId, options);
1019
+ }
1020
+ return batch;
1021
+ }
1022
+ };
1023
+ var GatewayResource = class {
1024
+ chat;
1025
+ /** Buffered OpenAI Responses-compatible requests. */
1026
+ responses;
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.responses = {
1049
+ create: (params, options) => this.transport.request({
1050
+ method: "POST",
1051
+ path: "/v1/responses",
1052
+ body: params,
1053
+ options,
1054
+ response: "json"
1055
+ })
1056
+ };
1057
+ this.models = {
1058
+ list: (options) => this.transport.request({
1059
+ method: "GET",
1060
+ path: "/v1/models",
1061
+ options,
1062
+ response: "json"
1063
+ })
1064
+ };
1065
+ this.batches = {
1066
+ create: (params, options = {}) => this.transport.request({
1067
+ method: "POST",
1068
+ path: "/v1/batches",
1069
+ body: params,
1070
+ options: {
1071
+ ...options,
1072
+ idempotencyKey: options.idempotencyKey ?? this.idempotencyGenerator()
1073
+ },
1074
+ response: "json"
1075
+ }),
1076
+ retrieve: (batchId, options) => this.transport.request({
1077
+ method: "GET",
1078
+ path: `/v1/batches/${encodeURIComponent(
1079
+ resourceId(batchId, ["id"], "gateway.batches.retrieve")
1080
+ )}`,
1081
+ options,
1082
+ response: "json"
1083
+ }),
1084
+ wait: (batch, options) => this.waitForBatch(batch, options),
1085
+ cancel: (batchId, options) => this.transport.request({
1086
+ method: "POST",
1087
+ path: `/v1/batches/${encodeURIComponent(
1088
+ resourceId(batchId, ["id"], "gateway.batches.cancel")
1089
+ )}/cancel`,
1090
+ options,
1091
+ response: "json"
1092
+ }),
1093
+ results: (batchId, options) => this.transport.request({
1094
+ method: "GET",
1095
+ path: `/v1/batches/${encodeURIComponent(
1096
+ resourceId(batchId, ["id"], "gateway.batches.results")
1097
+ )}/results`,
1098
+ options,
1099
+ response: "ndjson"
1100
+ })
1101
+ };
1102
+ }
1103
+ async waitForBatch(value, options = {}) {
1104
+ const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
1105
+ const deadline = Date.now() + timeoutMs;
1106
+ let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
1107
+ while (!terminalGatewayBatch(batch.status)) {
1108
+ if (Date.now() >= deadline) {
1109
+ throw new UsageTapError(
1110
+ "USAGETAP_RETRY_EXHAUSTED",
1111
+ `Gateway batch ${batch.id} did not finish before timeout`,
1112
+ { retryable: true }
1113
+ );
1114
+ }
1115
+ await sleep(pollIntervalMs, options.signal);
1116
+ batch = await this.batches.retrieve(batch.id, options);
1117
+ }
1118
+ return batch;
1119
+ }
1120
+ };
1121
+
753
1122
  // src/client.ts
754
1123
  var CALL_BEGIN_PATH = "call_begin";
755
1124
  var CALL_END_PATH = "call_end";
756
1125
  var COMPRESS_PROMPT_PATH = "compress_prompt";
1126
+ var SAMPLES_PATH = "samples";
1127
+ var SAMPLING_SETTINGS_PATH = "sampling/settings";
1128
+ var SAMPLING_DECIDE_PATH = "sampling/decide";
757
1129
  var CHECK_USAGE_PATH = "customers/{customerId}/usage";
758
1130
  var CREATE_CUSTOMER_PATH = "customers";
759
1131
  var CHANGE_PLAN_PATH = "customers/{customerId}/change_plan";
@@ -764,11 +1136,16 @@ var CORRELATION_HEADER = "x-usage-correlation-id";
764
1136
  var IDEMPOTENCY_HEADER = "idempotency-key";
765
1137
  var SDK_HEADER = "x-usage-sdk";
766
1138
  var USER_AGENT = "UsageTapClient";
767
- var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
1139
+ var CANONICAL_MEDIA_TYPE2 = "application/vnd.usagetap.v1+json";
768
1140
  var DEFAULT_BASE_URL = "https://api.usagetap.com";
769
- var SDK_VERSION = "1.3.2" ;
1141
+ var DEFAULT_RUN_INACTIVITY_MS = 60 * 60 * 1e3;
1142
+ var SDK_VERSION = "1.7.0" ;
770
1143
  var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
771
1144
  var UsageTapClient = class {
1145
+ /** OpenAI-compatible chat, model, and native batch operations. */
1146
+ gateway;
1147
+ /** Published-profile context summarization operations. */
1148
+ summarization;
772
1149
  apiKey;
773
1150
  baseUrl;
774
1151
  fetchImpl;
@@ -793,6 +1170,11 @@ var UsageTapClient = class {
793
1170
  usageTapCompressionMessagesEndpoint;
794
1171
  usageTapCompressionModel;
795
1172
  usageTapCompressionAggressiveness;
1173
+ sampling;
1174
+ samplingSettingsCacheMs;
1175
+ circuitBreaker;
1176
+ circuitBreakerRuns = /* @__PURE__ */ new Map();
1177
+ samplingSettingsCache;
796
1178
  constructor(options = {}) {
797
1179
  const apiKey = options.apiKey?.trim() || readEnvironmentVariable("USAGETAP_API_KEY");
798
1180
  const baseUrl = options.baseUrl?.trim() || readEnvironmentVariable("USAGETAP_BASE_URL") || DEFAULT_BASE_URL;
@@ -815,10 +1197,21 @@ var UsageTapClient = class {
815
1197
  "A global fetch implementation was not found. Pass fetchImpl in UsageTapClientOptions."
816
1198
  );
817
1199
  }
818
- const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
819
- this.baseUrl = new URL(normalizedBaseUrl);
1200
+ const normalizedBaseUrl2 = normalizeBaseUrl(baseUrl);
1201
+ this.baseUrl = new URL(normalizedBaseUrl2);
820
1202
  this.apiKey = apiKey;
821
1203
  this.fetchImpl = wrapFetchImplementation(fetchCandidate, !options.fetchImpl);
1204
+ const resourceConfig = {
1205
+ apiKey,
1206
+ apiBaseUrl: normalizedBaseUrl2,
1207
+ gatewayBaseUrl: options.gatewayBaseUrl?.trim() || readEnvironmentVariable("USAGETAP_GATEWAY_URL"),
1208
+ fetchImpl: this.fetchImpl,
1209
+ headers: options.headers,
1210
+ sdkVersion: SDK_VERSION,
1211
+ idempotencyGenerator: options.idempotencyGenerator
1212
+ };
1213
+ this.gateway = new GatewayResource(resourceConfig);
1214
+ this.summarization = new SummarizationResource(resourceConfig);
822
1215
  this.defaultFeature = options.defaultFeature;
823
1216
  this.defaultTags = options.defaultTags?.length ? dedupeStrings(options.defaultTags) : void 0;
824
1217
  this.defaultHeaders = options.headers ? normalizeHeaderDictionary(options.headers) : {};
@@ -840,11 +1233,122 @@ var UsageTapClient = class {
840
1233
  this.usageTapCompressionMessagesEndpoint = options.usageTapCompressionMessagesEndpoint;
841
1234
  this.usageTapCompressionModel = options.usageTapCompressionModel;
842
1235
  this.usageTapCompressionAggressiveness = options.usageTapCompressionAggressiveness;
1236
+ this.sampling = options.sampling;
1237
+ this.samplingSettingsCacheMs = Number.isFinite(options.samplingSettingsCacheMs) ? Math.max(0, Number(options.samplingSettingsCacheMs)) : 5 * 60 * 1e3;
1238
+ if (options.circuitBreaker) {
1239
+ const maxCallsPerRun = options.circuitBreaker.maxCallsPerRun;
1240
+ if (!Number.isInteger(maxCallsPerRun) || maxCallsPerRun < 1) {
1241
+ throw new UsageTapError(
1242
+ "USAGETAP_BAD_REQUEST",
1243
+ "circuitBreaker.maxCallsPerRun must be a positive integer"
1244
+ );
1245
+ }
1246
+ const runInactivityMs = options.circuitBreaker.runInactivityMs ?? DEFAULT_RUN_INACTIVITY_MS;
1247
+ if (!Number.isFinite(runInactivityMs) || runInactivityMs < 1) {
1248
+ throw new UsageTapError(
1249
+ "USAGETAP_BAD_REQUEST",
1250
+ "circuitBreaker.runInactivityMs must be a positive number"
1251
+ );
1252
+ }
1253
+ this.circuitBreaker = {
1254
+ maxCallsPerRun,
1255
+ runInactivityMs
1256
+ };
1257
+ }
1258
+ }
1259
+ shouldSample(request, policy = this.sampling || void 0) {
1260
+ if (!policy) return false;
1261
+ const rate = Math.min(1, Math.max(0, Number(policy.rate) || 0));
1262
+ if (rate <= 0) return false;
1263
+ const customerId = request.customerId?.trim();
1264
+ if (customerId && policy.customers?.exclude?.includes(customerId)) return false;
1265
+ const feature = request.feature?.trim();
1266
+ if (feature && policy.features?.exclude?.includes(feature)) return false;
1267
+ const included = policy.features?.include?.filter(Boolean) ?? [];
1268
+ if (included.length > 0 && (!feature || !included.includes(feature))) return false;
1269
+ const minimum = Math.max(0, Math.round(policy.minInputTokens ?? 0));
1270
+ if (minimum > 0 && estimatePromptTokens(request.input) < minimum) return false;
1271
+ return (policy.random ?? Math.random)() < rate;
1272
+ }
1273
+ async getSamplingSettings(options = {}) {
1274
+ const now = Date.now();
1275
+ if (!options.forceRefresh && this.samplingSettingsCache && this.samplingSettingsCache.expiresAtMs > now) {
1276
+ return {
1277
+ result: { status: "ACCEPTED", code: "SAMPLING_SETTINGS_CACHED" },
1278
+ data: this.samplingSettingsCache.settings,
1279
+ correlationId: options.correlationId ?? "local-cache"
1280
+ };
1281
+ }
1282
+ const response = await this.requestGet(
1283
+ SAMPLING_SETTINGS_PATH,
1284
+ {
1285
+ signal: options.signal,
1286
+ headers: options.headers,
1287
+ retries: options.retries,
1288
+ correlationId: options.correlationId
1289
+ }
1290
+ );
1291
+ const serverCacheMs = Math.max(0, Number(response.data.cacheSeconds) || 0) * 1e3;
1292
+ const cacheMs = Math.min(this.samplingSettingsCacheMs, serverCacheMs);
1293
+ this.samplingSettingsCache = {
1294
+ settings: response.data,
1295
+ expiresAtMs: now + cacheMs
1296
+ };
1297
+ return response;
1298
+ }
1299
+ async shouldSampleAsync(request, policy) {
1300
+ if (policy) return this.shouldSample(request, policy);
1301
+ if (this.sampling === false) return false;
1302
+ if (this.sampling) return this.shouldSample(request, this.sampling);
1303
+ try {
1304
+ const settings = await this.getSamplingSettings();
1305
+ return this.shouldSample(request, settings.data);
1306
+ } catch {
1307
+ return false;
1308
+ }
1309
+ }
1310
+ async decideSample(request, options = {}) {
1311
+ const hasTokens = Number.isFinite(request.inputTokens) && Number(request.inputTokens) >= 0;
1312
+ const hasCharacters = Number.isFinite(request.inputCharacters) && Number(request.inputCharacters) >= 0;
1313
+ if (!hasTokens && !hasCharacters) {
1314
+ throw new UsageTapError(
1315
+ "USAGETAP_BAD_REQUEST",
1316
+ "decideSample requires inputTokens or inputCharacters"
1317
+ );
1318
+ }
1319
+ return this.request(
1320
+ SAMPLING_DECIDE_PATH,
1321
+ request,
1322
+ options
1323
+ );
1324
+ }
1325
+ async captureSample(request, options = {}) {
1326
+ if (!request || request.input === void 0) {
1327
+ throw new UsageTapError(
1328
+ "USAGETAP_BAD_REQUEST",
1329
+ "captureSample requires input"
1330
+ );
1331
+ }
1332
+ if (!request.provider?.trim()) {
1333
+ throw new UsageTapError(
1334
+ "USAGETAP_BAD_REQUEST",
1335
+ "captureSample requires provider"
1336
+ );
1337
+ }
1338
+ const sampleId = request.sampleId?.trim() || this.idempotencyGenerator();
1339
+ return this.request(
1340
+ SAMPLES_PATH,
1341
+ { ...request, sampleId },
1342
+ { ...options, idempotencyKey: sampleId }
1343
+ );
843
1344
  }
844
1345
  async beginCall(request, options = {}) {
845
1346
  const idempotencyKey = request.idempotencyKey ?? request.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
1347
+ this.reserveRunCall(request, idempotencyKey);
1348
+ const apiRequest = { ...request };
1349
+ delete apiRequest.runId;
846
1350
  const payload = {
847
- ...request,
1351
+ ...apiRequest,
848
1352
  feature: request.feature ?? this.defaultFeature,
849
1353
  tags: this.mergeTags(request.tags)
850
1354
  };
@@ -862,6 +1366,28 @@ var UsageTapClient = class {
862
1366
  );
863
1367
  return response;
864
1368
  }
1369
+ /**
1370
+ * Inspect a configured run circuit breaker without consuming another call.
1371
+ */
1372
+ canRunContinue(request) {
1373
+ const identity = this.resolveRunIdentity(request);
1374
+ if (!identity || !this.circuitBreaker) {
1375
+ throw new UsageTapError(
1376
+ "USAGETAP_BAD_REQUEST",
1377
+ "canRunContinue requires circuitBreaker configuration and a non-empty runId"
1378
+ );
1379
+ }
1380
+ this.expireInactiveRuns();
1381
+ const calls = this.circuitBreakerRuns.get(identity.key)?.calls ?? 0;
1382
+ return this.createCircuitBreakerDecision(identity.customerId, identity.runId, calls);
1383
+ }
1384
+ /**
1385
+ * Release local state after a workflow finishes. Returns true when state existed.
1386
+ */
1387
+ resetRun(request) {
1388
+ const identity = this.resolveRunIdentity(request);
1389
+ return identity ? this.circuitBreakerRuns.delete(identity.key) : false;
1390
+ }
865
1391
  async promptCompress(request, options = {}) {
866
1392
  if (!request?.callId) {
867
1393
  throw new UsageTapError(
@@ -959,6 +1485,10 @@ var UsageTapClient = class {
959
1485
  usageTapCompressionMessagesEndpoint: this.usageTapCompressionMessagesEndpoint,
960
1486
  aggressiveness: options.aggressiveness ?? this.aggressiveness,
961
1487
  usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
1488
+ mode: options.mode,
1489
+ latencyBudgetMs: options.latencyBudgetMs,
1490
+ compactEmptyUserMessages: options.compactEmptyUserMessages,
1491
+ compactDuplicateUserTextParts: options.compactDuplicateUserTextParts,
962
1492
  fetchImpl: this.fetchImpl,
963
1493
  signal: options.signal,
964
1494
  failOpen: options.failOpen
@@ -1000,14 +1530,29 @@ var UsageTapClient = class {
1000
1530
  callId: request.callId,
1001
1531
  feature: feature ?? this.defaultFeature,
1002
1532
  tags: tags ?? this.defaultTags,
1533
+ providerUsed: request.providerUsed,
1003
1534
  modelUsed: request.modelUsed,
1535
+ reasoningEffort: request.reasoningEffort,
1536
+ reasoningEffortSource: request.reasoningEffortSource,
1537
+ reasoningMode: request.reasoningMode,
1538
+ reasoningBudgetTokens: request.reasoningBudgetTokens,
1004
1539
  metrics: {
1005
1540
  inputTokens: request.inputTokens,
1006
1541
  responseTokens: request.responseTokens,
1007
1542
  cachedInputTokens: request.cachedInputTokens,
1543
+ cacheWriteInputTokens: request.cacheWriteInputTokens,
1544
+ cacheWrite5mInputTokens: request.cacheWrite5mInputTokens,
1545
+ cacheWrite1hInputTokens: request.cacheWrite1hInputTokens,
1008
1546
  reasoningTokens: request.reasoningTokens,
1009
1547
  searches: request.searches,
1010
1548
  audioSeconds: request.audioSeconds,
1549
+ imageInputCount: request.imageInputCount,
1550
+ imageInputTokens: request.imageInputTokens,
1551
+ imageOutputCount: request.imageOutputCount,
1552
+ imageOutputTokens: request.imageOutputTokens,
1553
+ audioInputTokens: request.audioInputTokens,
1554
+ cachedAudioInputTokens: request.cachedAudioInputTokens,
1555
+ audioOutputTokens: request.audioOutputTokens,
1011
1556
  costUsd: response.data.costUSD
1012
1557
  },
1013
1558
  correlationId: response.correlationId
@@ -1094,10 +1639,10 @@ var UsageTapClient = class {
1094
1639
  "incrementCustomMeter requires meterSlot"
1095
1640
  );
1096
1641
  }
1097
- if (!["CUSTOM1", "CUSTOM2"].includes(request.meterSlot)) {
1642
+ if (!["CUSTOM1", "CUSTOM2", "AGENTIC_API"].includes(request.meterSlot)) {
1098
1643
  throw new UsageTapError(
1099
1644
  "USAGETAP_BAD_REQUEST",
1100
- "meterSlot must be CUSTOM1 or CUSTOM2"
1645
+ "meterSlot must be CUSTOM1, CUSTOM2 or AGENTIC_API"
1101
1646
  );
1102
1647
  }
1103
1648
  if (typeof request.amount !== "number" || !Number.isFinite(request.amount) || request.amount <= 0) {
@@ -1112,6 +1657,15 @@ var UsageTapClient = class {
1112
1657
  meterSlot: request.meterSlot,
1113
1658
  amount: request.amount
1114
1659
  };
1660
+ if (request.customerUserId) {
1661
+ payload.customerUserId = request.customerUserId;
1662
+ }
1663
+ if (request.customerUserName) {
1664
+ payload.customerUserName = request.customerUserName;
1665
+ }
1666
+ if (request.customerUserEmail) {
1667
+ payload.customerUserEmail = request.customerUserEmail;
1668
+ }
1115
1669
  if (request.feature) {
1116
1670
  payload.feature = request.feature;
1117
1671
  }
@@ -1148,6 +1702,11 @@ var UsageTapClient = class {
1148
1702
  const beginPayload = idempotencyKey ? { ...beginRequest, idempotencyKey, idempotency: idempotencyKey } : { ...beginRequest };
1149
1703
  const beginResponse = await this.beginCall(beginPayload, options);
1150
1704
  let usage = {};
1705
+ const pricingMode = beginResponse.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
1706
+ if (pricingMode) {
1707
+ usage.pricingMode = pricingMode;
1708
+ usage.batch = pricingMode === "batch";
1709
+ }
1151
1710
  const initialStripeCustomerId = typeof beginResponse.data.stripeCustomerId === "string" ? beginResponse.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
1152
1711
  if (initialStripeCustomerId) {
1153
1712
  usage = { ...usage, stripeCustomerId: initialStripeCustomerId };
@@ -1156,6 +1715,28 @@ var UsageTapClient = class {
1156
1715
  let handlerResult;
1157
1716
  let handlerError;
1158
1717
  let endCallError;
1718
+ let finalizationDeferred = false;
1719
+ let finalizationPromise;
1720
+ const finalize = () => {
1721
+ if (!finalizationPromise) {
1722
+ finalizationPromise = this.endCall(
1723
+ {
1724
+ callId: beginResponse.data.callId,
1725
+ // Pass context for metric tracking
1726
+ customerId: beginRequest.customerId,
1727
+ feature: beginRequest.feature ?? this.defaultFeature,
1728
+ tags: beginRequest.tags ?? this.defaultTags,
1729
+ ...usage,
1730
+ error: errorPayload
1731
+ },
1732
+ {
1733
+ ...options,
1734
+ correlationId: beginResponse.correlationId
1735
+ }
1736
+ ).then(() => void 0);
1737
+ }
1738
+ return finalizationPromise;
1739
+ };
1159
1740
  const context = {
1160
1741
  begin: beginResponse,
1161
1742
  setUsage: (u) => {
@@ -1163,6 +1744,10 @@ var UsageTapClient = class {
1163
1744
  },
1164
1745
  setError: (err) => {
1165
1746
  errorPayload = err;
1747
+ },
1748
+ deferFinalization: () => {
1749
+ finalizationDeferred = true;
1750
+ return finalize;
1166
1751
  }
1167
1752
  };
1168
1753
  try {
@@ -1176,24 +1761,12 @@ var UsageTapClient = class {
1176
1761
  };
1177
1762
  }
1178
1763
  } finally {
1179
- try {
1180
- await this.endCall(
1181
- {
1182
- callId: beginResponse.data.callId,
1183
- // Pass context for metric tracking
1184
- customerId: beginRequest.customerId,
1185
- feature: beginRequest.feature ?? this.defaultFeature,
1186
- tags: beginRequest.tags ?? this.defaultTags,
1187
- ...usage,
1188
- error: errorPayload
1189
- },
1190
- {
1191
- ...options,
1192
- correlationId: beginResponse.correlationId
1193
- }
1194
- );
1195
- } catch (error) {
1196
- endCallError = error;
1764
+ if (handlerError || !finalizationDeferred) {
1765
+ try {
1766
+ await finalize();
1767
+ } catch (error) {
1768
+ endCallError = error;
1769
+ }
1197
1770
  }
1198
1771
  }
1199
1772
  if (handlerError) {
@@ -1215,17 +1788,86 @@ var UsageTapClient = class {
1215
1788
  toPromptCompressionTelemetry(result) {
1216
1789
  return {
1217
1790
  provider: result.provider,
1218
- originalCharacters: result.originalCharacters,
1219
- compressedCharacters: result.compressedCharacters,
1220
- savedCharacters: result.savedCharacters,
1221
1791
  originalTokens: result.originalTokens,
1222
1792
  compressedTokens: result.compressedTokens,
1223
1793
  savedTokens: result.savedTokens,
1224
1794
  tokenSavingsRatio: result.tokenSavingsRatio,
1225
- savingsRatio: result.savingsRatio,
1226
1795
  techniques: result.techniques
1227
1796
  };
1228
1797
  }
1798
+ reserveRunCall(request, idempotencyKey) {
1799
+ const identity = this.resolveRunIdentity(request);
1800
+ if (!identity || !this.circuitBreaker) return;
1801
+ this.expireInactiveRuns();
1802
+ const now = Date.now();
1803
+ const state = this.circuitBreakerRuns.get(identity.key) ?? {
1804
+ calls: 0,
1805
+ reservationKeys: /* @__PURE__ */ new Set(),
1806
+ lastSeenAtMs: now
1807
+ };
1808
+ const reservationKey = idempotencyKey ?? this.idempotencyGenerator();
1809
+ state.lastSeenAtMs = now;
1810
+ if (state.reservationKeys.has(reservationKey)) {
1811
+ this.circuitBreakerRuns.set(identity.key, state);
1812
+ return;
1813
+ }
1814
+ const decision = this.createCircuitBreakerDecision(
1815
+ identity.customerId,
1816
+ identity.runId,
1817
+ state.calls
1818
+ );
1819
+ if (!decision.allowed) {
1820
+ throw new UsageTapError(
1821
+ "USAGETAP_CIRCUIT_OPEN",
1822
+ `Run ${identity.runId} reached its ${decision.limit}-call circuit-breaker limit`,
1823
+ {
1824
+ details: {
1825
+ reason: decision.reason,
1826
+ customerId: identity.customerId,
1827
+ runId: identity.runId,
1828
+ calls: decision.calls,
1829
+ limit: decision.limit,
1830
+ remaining: decision.remaining
1831
+ }
1832
+ }
1833
+ );
1834
+ }
1835
+ state.calls += 1;
1836
+ state.reservationKeys.add(reservationKey);
1837
+ this.circuitBreakerRuns.set(identity.key, state);
1838
+ }
1839
+ resolveRunIdentity(request) {
1840
+ const customerId = request.customerId?.trim();
1841
+ const runId = request.runId?.trim();
1842
+ if (!customerId || !runId) return void 0;
1843
+ return {
1844
+ key: `${customerId}\0${runId}`,
1845
+ customerId,
1846
+ runId
1847
+ };
1848
+ }
1849
+ createCircuitBreakerDecision(customerId, runId, calls) {
1850
+ const limit = this.circuitBreaker?.maxCallsPerRun ?? 0;
1851
+ const allowed = calls < limit;
1852
+ return {
1853
+ allowed,
1854
+ ...allowed ? {} : { reason: "max_calls_per_run" },
1855
+ customerId,
1856
+ runId,
1857
+ calls,
1858
+ limit,
1859
+ remaining: Math.max(0, limit - calls)
1860
+ };
1861
+ }
1862
+ expireInactiveRuns() {
1863
+ if (!this.circuitBreaker || this.circuitBreakerRuns.size === 0) return;
1864
+ const expiredBefore = Date.now() - this.circuitBreaker.runInactivityMs;
1865
+ for (const [key, state] of this.circuitBreakerRuns) {
1866
+ if (state.lastSeenAtMs < expiredBefore) {
1867
+ this.circuitBreakerRuns.delete(key);
1868
+ }
1869
+ }
1870
+ }
1229
1871
  async request(path, payload, options) {
1230
1872
  const url = new URL(path, this.baseUrl).toString();
1231
1873
  const body = payload !== void 0 ? JSON.stringify(payload) : void 0;
@@ -1413,7 +2055,7 @@ var UsageTapClient = class {
1413
2055
  ...this.defaultHeaders,
1414
2056
  [SDK_HEADER]: `js/${SDK_VERSION}`,
1415
2057
  "content-type": "application/json",
1416
- accept: CANONICAL_MEDIA_TYPE
2058
+ accept: CANONICAL_MEDIA_TYPE2
1417
2059
  };
1418
2060
  if (!HAS_WINDOW) {
1419
2061
  headers["user-agent"] = `${USER_AGENT}/${SDK_VERSION}`;
@@ -1466,7 +2108,8 @@ var UsageTapClient = class {
1466
2108
  }
1467
2109
  toHttpError(status, payload, correlationId) {
1468
2110
  const code = mapStatusToErrorCode(status);
1469
- const retryable = isRetryableStatus(status);
2111
+ const apiCode = payload?.error?.code ?? payload?.result?.code ?? "UNKNOWN";
2112
+ const retryable = isRetryableStatus(status) || isRetryableApiCode(apiCode);
1470
2113
  const message = payload?.error?.message ?? payload?.result?.message ?? `UsageTap responded with HTTP ${status}`;
1471
2114
  return new UsageTapError(code, message, {
1472
2115
  status,
@@ -1499,7 +2142,7 @@ function isRetryableStatus(status) {
1499
2142
  }
1500
2143
  function isRetryableApiCode(code) {
1501
2144
  const normalized = code.toUpperCase();
1502
- return normalized.includes("TRANSIENT") || normalized.includes("RETRY") || normalized.includes("TIMEOUT") || normalized.includes("THROTTLE") || normalized.includes("RATE_LIMIT");
2145
+ return normalized === "PAYG_CONFLICT" || normalized.includes("TRANSIENT") || normalized.includes("RETRY") || normalized.includes("TIMEOUT") || normalized.includes("THROTTLE") || normalized.includes("RATE_LIMIT");
1503
2146
  }
1504
2147
  function mapApiCodeToError(code) {
1505
2148
  const normalized = code.toUpperCase();
@@ -1613,7 +2256,123 @@ var AnthropicPromptCompressionStats = class {
1613
2256
  return this.totalOriginalCharacters > 0 ? this.totalCharactersSaved / this.totalOriginalCharacters : 0;
1614
2257
  }
1615
2258
  };
2259
+ var DeferredAnthropicApiPromise = class extends Promise {
2260
+ responseEnvelope;
2261
+ constructor(execute) {
2262
+ super((resolve) => resolve(void 0));
2263
+ this.responseEnvelope = Promise.resolve().then(execute);
2264
+ }
2265
+ then(onfulfilled, onrejected) {
2266
+ return this.responseEnvelope.then((result) => result.data).then(onfulfilled, onrejected);
2267
+ }
2268
+ catch(onrejected) {
2269
+ return this.then(void 0, onrejected);
2270
+ }
2271
+ finally(onfinally) {
2272
+ return this.responseEnvelope.then((result) => result.data).finally(onfinally ?? void 0);
2273
+ }
2274
+ withResponse() {
2275
+ return this.responseEnvelope;
2276
+ }
2277
+ async asResponse() {
2278
+ const result = await this.responseEnvelope;
2279
+ if (!("response" in result)) {
2280
+ throw new TypeError("The wrapped Anthropic result does not expose a raw response");
2281
+ }
2282
+ return result.response;
2283
+ }
2284
+ };
2285
+ async function resolveAnthropicResponseEnvelope(value) {
2286
+ if (isObjectRecord(value) && typeof value.withResponse === "function") {
2287
+ const withResponse = value.withResponse;
2288
+ const result = await withResponse.call(value);
2289
+ if (!isObjectRecord(result) || !("data" in result)) {
2290
+ throw new TypeError("Anthropic create().withResponse() returned an invalid result");
2291
+ }
2292
+ return result;
2293
+ }
2294
+ return { data: await value };
2295
+ }
1616
2296
  var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
2297
+ function withSampling(client, options = {}) {
2298
+ if (!client?.messages || typeof client.messages.create !== "function" || !options) {
2299
+ throw new UsageTapError(
2300
+ "USAGETAP_BAD_REQUEST",
2301
+ "withSampling requires an Anthropic client and sampling options"
2302
+ );
2303
+ }
2304
+ const { apiKey, usageTapClient, ...policy } = options;
2305
+ const localPolicy = typeof policy.rate === "number" ? policy : void 0;
2306
+ const usageTap = usageTapClient ?? new UsageTapClient({ apiKey, sampling: localPolicy });
2307
+ const originalCreate = client.messages.create.bind(client.messages);
2308
+ const create = (params, requestOptions) => {
2309
+ return new DeferredAnthropicApiPromise(async () => {
2310
+ const { usageTap: callContext = {}, ...providerOptions } = requestOptions ?? {};
2311
+ const decision = params.stream === true ? Promise.resolve(false) : usageTap.shouldSampleAsync({
2312
+ customerId: callContext.customerId,
2313
+ feature: callContext.feature,
2314
+ input: params
2315
+ }, localPolicy);
2316
+ const startedAt = Date.now();
2317
+ try {
2318
+ const result = await resolveAnthropicResponseEnvelope(originalCreate(
2319
+ params,
2320
+ Object.keys(providerOptions).length ? providerOptions : void 0
2321
+ ));
2322
+ const response = result.data;
2323
+ const selected = await decision;
2324
+ if (selected) {
2325
+ const record = isObjectRecord(response) ? response : {};
2326
+ await usageTap.captureSample({
2327
+ customerId: callContext.customerId,
2328
+ feature: callContext.feature,
2329
+ environment: callContext.environment,
2330
+ tags: callContext.tags,
2331
+ provider: "anthropic",
2332
+ model: readString(record.model) ?? readString(params.model),
2333
+ input: params,
2334
+ output: response,
2335
+ usage: record.usage,
2336
+ latencyMs: Date.now() - startedAt
2337
+ }).catch(() => void 0);
2338
+ }
2339
+ return result;
2340
+ } catch (error) {
2341
+ const selected = await decision;
2342
+ if (selected) {
2343
+ await usageTap.captureSample({
2344
+ customerId: callContext.customerId,
2345
+ feature: callContext.feature,
2346
+ environment: callContext.environment,
2347
+ tags: callContext.tags,
2348
+ provider: "anthropic",
2349
+ model: readString(params.model),
2350
+ input: params,
2351
+ latencyMs: Date.now() - startedAt,
2352
+ error: error instanceof Error ? { name: error.name, message: error.message } : { message: String(error) }
2353
+ }).catch(() => void 0);
2354
+ }
2355
+ throw error;
2356
+ }
2357
+ });
2358
+ };
2359
+ const messages = new Proxy(client.messages, {
2360
+ get(target, prop, receiver) {
2361
+ if (prop === "create") return create;
2362
+ return safeReflectGet(target, prop, receiver);
2363
+ }
2364
+ });
2365
+ return new Proxy(client, {
2366
+ get(target, prop, receiver) {
2367
+ if (prop === "messages") return messages;
2368
+ if (prop === "unwrap") return () => target;
2369
+ return safeReflectGet(target, prop, receiver);
2370
+ }
2371
+ });
2372
+ }
2373
+ function safeReflectGet(target, prop, receiver) {
2374
+ return Reflect.get(target, prop, receiver);
2375
+ }
1617
2376
  function withCompression(client, options = {}) {
1618
2377
  if (!client?.messages || typeof client.messages.create !== "function") {
1619
2378
  throw new UsageTapError(
@@ -1624,17 +2383,24 @@ function withCompression(client, options = {}) {
1624
2383
  const {
1625
2384
  apiKey,
1626
2385
  usageTapClient,
2386
+ sampling,
1627
2387
  ...compressionOverrides
1628
2388
  } = options;
1629
2389
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
1630
2390
  const compression = {
1631
2391
  provider: "usagetap",
2392
+ roles: { user: { enabled: true, aggressiveness: 0.2 } },
1632
2393
  minContextTokens: DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS,
1633
2394
  ...compressionOverrides
1634
2395
  };
1635
2396
  const originalCreate = client.messages.create.bind(client.messages);
1636
2397
  const wrappedCreate = (params, requestOptions) => {
1637
- return compressAnthropicParams(params, usageTap, compression).then((outcome) => originalCreate(outcome.params, requestOptions));
2398
+ return new DeferredAnthropicApiPromise(async () => {
2399
+ const outcome = await compressAnthropicParams(params, usageTap, compression);
2400
+ return resolveAnthropicResponseEnvelope(
2401
+ originalCreate(outcome.params, requestOptions)
2402
+ );
2403
+ });
1638
2404
  };
1639
2405
  const proxiedMessages = new Proxy(client.messages, {
1640
2406
  get(target, prop, receiver) {
@@ -1642,13 +2408,18 @@ function withCompression(client, options = {}) {
1642
2408
  return Reflect.get(target, prop, receiver);
1643
2409
  }
1644
2410
  });
1645
- return new Proxy(client, {
2411
+ const compressed = new Proxy(client, {
1646
2412
  get(target, prop, receiver) {
1647
2413
  if (prop === "messages") return proxiedMessages;
1648
2414
  if (prop === "unwrap") return () => target;
1649
2415
  return Reflect.get(target, prop, receiver);
1650
2416
  }
1651
2417
  });
2418
+ return sampling ? withSampling(compressed, {
2419
+ ...sampling === true ? {} : sampling,
2420
+ apiKey,
2421
+ usageTapClient
2422
+ }) : compressed;
1652
2423
  }
1653
2424
  function withMetering(client, customer) {
1654
2425
  const config = typeof customer === "string" ? { customerId: customer } : customer;
@@ -1663,6 +2434,7 @@ function withMetering(client, customer) {
1663
2434
  usageTapClient,
1664
2435
  applyVendorHints,
1665
2436
  promptCompression,
2437
+ sampling,
1666
2438
  ...defaultContext
1667
2439
  } = config;
1668
2440
  const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
@@ -1670,7 +2442,8 @@ function withMetering(client, customer) {
1670
2442
  return wrapAnthropic(client, usageTap, {
1671
2443
  defaultContext,
1672
2444
  applyVendorHints,
1673
- promptCompression: normalizedCompression
2445
+ promptCompression: normalizedCompression,
2446
+ sampling
1674
2447
  });
1675
2448
  }
1676
2449
  function wrapAnthropic(client, usageTap, options = {}) {
@@ -1683,6 +2456,7 @@ function wrapAnthropic(client, usageTap, options = {}) {
1683
2456
  const defaultContext = options.defaultContext;
1684
2457
  const applyVendorHints = options.applyVendorHints !== false;
1685
2458
  const defaultPromptCompression = normalizePromptCompressionOptions(options.promptCompression);
2459
+ const defaultSampling = normalizeMeteredAnthropicSampling(options.sampling);
1686
2460
  const promptCompressionStats = new AnthropicPromptCompressionStats();
1687
2461
  const proxiedMessages = createMessagesProxy(
1688
2462
  client.messages,
@@ -1690,7 +2464,8 @@ function wrapAnthropic(client, usageTap, options = {}) {
1690
2464
  defaultContext,
1691
2465
  applyVendorHints,
1692
2466
  defaultPromptCompression,
1693
- promptCompressionStats
2467
+ promptCompressionStats,
2468
+ defaultSampling
1694
2469
  );
1695
2470
  const handler = {
1696
2471
  get(target, prop, receiver) {
@@ -1708,7 +2483,7 @@ function wrapAnthropic(client, usageTap, options = {}) {
1708
2483
  };
1709
2484
  return new Proxy(client, handler);
1710
2485
  }
1711
- function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
2486
+ function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling) {
1712
2487
  if (typeof resource.create !== "function") {
1713
2488
  throw new UsageTapError("USAGETAP_BAD_REQUEST", "wrapAnthropic requires client.messages.create");
1714
2489
  }
@@ -1720,19 +2495,22 @@ function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHint
1720
2495
  withUsage,
1721
2496
  promptCompression
1722
2497
  } = splitUsageOptions(options);
1723
- return invokeMessagesCreate({
1724
- params,
1725
- requestOptions,
1726
- usageContext,
1727
- withUsage,
1728
- promptCompression,
1729
- originalCreate,
1730
- usageTap,
1731
- defaultContext,
1732
- applyVendorHints,
1733
- defaultPromptCompression,
1734
- promptCompressionStats
1735
- });
2498
+ return new DeferredAnthropicApiPromise(
2499
+ () => invokeMessagesCreate({
2500
+ params,
2501
+ requestOptions,
2502
+ usageContext,
2503
+ withUsage,
2504
+ promptCompression,
2505
+ originalCreate,
2506
+ usageTap,
2507
+ defaultContext,
2508
+ applyVendorHints,
2509
+ defaultPromptCompression,
2510
+ promptCompressionStats,
2511
+ defaultSampling
2512
+ })
2513
+ );
1736
2514
  });
1737
2515
  const handler = {
1738
2516
  get(target, prop, receiver) {
@@ -1745,13 +2523,25 @@ function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHint
1745
2523
  return new Proxy(resource, handler);
1746
2524
  }
1747
2525
  async function invokeMessagesCreate(args) {
1748
- const beginRequest = resolveBeginRequest(args.defaultContext, args.usageContext);
2526
+ const beginRequest = resolveBeginRequest(
2527
+ args.defaultContext,
2528
+ args.usageContext
2529
+ );
1749
2530
  const begin = await args.usageTap.beginCall(
1750
2531
  beginRequest,
1751
2532
  beginCallOptions(args.withUsage)
1752
2533
  );
1753
2534
  const state = createCallState(beginRequest, begin);
1754
2535
  const ctx = createUsageContext(state);
2536
+ const sampleStartedAt = Date.now();
2537
+ const wantsStream = isStreamingRequest(args.params);
2538
+ const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredAnthropicSampleDecision({
2539
+ usageTap: args.usageTap,
2540
+ sampling: args.defaultSampling,
2541
+ beginRequest,
2542
+ input: args.params
2543
+ });
2544
+ let providerCompleted = false;
1755
2545
  try {
1756
2546
  const hintedParams = args.applyVendorHints ? applyAnthropicVendorHints(args.params, ctx.begin.data.vendorHints) : args.params;
1757
2547
  const finalParams = await compressAnthropicParamsForCall({
@@ -1764,21 +2554,38 @@ async function invokeMessagesCreate(args) {
1764
2554
  withUsage: args.withUsage,
1765
2555
  operation: "messages.create"
1766
2556
  });
2557
+ ctx.setUsage(anthropicRequestExecutionMetadata(finalParams));
1767
2558
  const request = attachCorrelationHeader(args.requestOptions, ctx.begin.correlationId);
1768
- const response = await args.originalCreate(finalParams, request);
2559
+ const providerResult = await resolveAnthropicResponseEnvelope(
2560
+ args.originalCreate(finalParams, request)
2561
+ );
2562
+ const response = providerResult.data;
2563
+ providerCompleted = true;
1769
2564
  if (isStreamingRequest(finalParams)) {
1770
2565
  ensureAsyncIterable(response, "messages.create");
1771
- return wrapAnthropicStreamForUsageTap(
1772
- response,
1773
- state,
1774
- args.usageTap,
1775
- args.withUsage,
1776
- readString(finalParams.model)
1777
- );
2566
+ return {
2567
+ ...providerResult,
2568
+ data: wrapAnthropicStreamForUsageTap(
2569
+ response,
2570
+ state,
2571
+ args.usageTap,
2572
+ args.withUsage,
2573
+ readString(finalParams.model)
2574
+ )
2575
+ };
1778
2576
  }
1779
2577
  inferAnthropicUsage(response, readString(finalParams.model), ctx);
2578
+ await captureMeteredAnthropicSample({
2579
+ usageTap: args.usageTap,
2580
+ sampling: args.defaultSampling,
2581
+ decision: sampleDecision,
2582
+ state,
2583
+ input: args.params,
2584
+ response,
2585
+ startedAt: sampleStartedAt
2586
+ });
1780
2587
  await finalizeCall(state, args.usageTap, args.withUsage);
1781
- return response;
2588
+ return providerResult;
1782
2589
  } catch (error) {
1783
2590
  if (!state.error) {
1784
2591
  state.error = {
@@ -1786,10 +2593,80 @@ async function invokeMessagesCreate(args) {
1786
2593
  message: error instanceof Error ? error.message : String(error)
1787
2594
  };
1788
2595
  }
2596
+ if (!providerCompleted) {
2597
+ await captureMeteredAnthropicSample({
2598
+ usageTap: args.usageTap,
2599
+ sampling: args.defaultSampling,
2600
+ decision: sampleDecision,
2601
+ state,
2602
+ input: args.params,
2603
+ error,
2604
+ startedAt: sampleStartedAt
2605
+ });
2606
+ }
1789
2607
  await finalizeCall(state, args.usageTap, args.withUsage);
1790
2608
  throw error;
1791
2609
  }
1792
2610
  }
2611
+ function normalizeMeteredAnthropicSampling(options) {
2612
+ if (!options) return void 0;
2613
+ if (options === true) return {};
2614
+ return {
2615
+ policy: typeof options.rate === "number" ? options : void 0
2616
+ };
2617
+ }
2618
+ function startMeteredAnthropicSampleDecision({
2619
+ usageTap,
2620
+ sampling,
2621
+ beginRequest,
2622
+ input
2623
+ }) {
2624
+ if (!sampling) return Promise.resolve(false);
2625
+ return usageTap.shouldSampleAsync(
2626
+ {
2627
+ customerId: beginRequest.customerId,
2628
+ feature: beginRequest.feature,
2629
+ input
2630
+ },
2631
+ sampling.policy
2632
+ );
2633
+ }
2634
+ async function captureMeteredAnthropicSample({
2635
+ usageTap,
2636
+ sampling,
2637
+ decision,
2638
+ state,
2639
+ input,
2640
+ response,
2641
+ error,
2642
+ startedAt
2643
+ }) {
2644
+ if (!sampling) return;
2645
+ let selected = false;
2646
+ try {
2647
+ selected = await decision;
2648
+ } catch {
2649
+ return;
2650
+ }
2651
+ if (!selected) return;
2652
+ const record = isObjectRecord(response) ? response : {};
2653
+ await usageTap.captureSample({
2654
+ sampleId: state.begin.data.callId,
2655
+ callId: state.begin.data.callId,
2656
+ customerId: state.beginRequest.customerId,
2657
+ feature: state.beginRequest.feature,
2658
+ tags: state.beginRequest.tags,
2659
+ provider: "anthropic",
2660
+ model: readString(record.model) ?? readString(input.model),
2661
+ input,
2662
+ ...response === void 0 ? {} : { output: response },
2663
+ usage: record.usage,
2664
+ latencyMs: Date.now() - startedAt,
2665
+ ...error === void 0 ? {} : {
2666
+ error: error instanceof Error ? { name: error.name, message: error.message } : { message: String(error) }
2667
+ }
2668
+ }).catch(() => void 0);
2669
+ }
1793
2670
  async function compressAnthropicParamsForCall(args) {
1794
2671
  const compression = resolveEffectivePromptCompressionOptions(
1795
2672
  args.defaultPromptCompression,
@@ -1869,6 +2746,10 @@ async function compressAnthropicParams(params, usageTap, compression, signal) {
1869
2746
  const result = await usageTap.compressPromptMessages(source, {
1870
2747
  provider: "usagetap",
1871
2748
  failOpen: compression.failOpen,
2749
+ mode: compression.mode,
2750
+ latencyBudgetMs: compression.latencyBudgetMs,
2751
+ compactEmptyUserMessages: compression.compactEmptyUserMessages,
2752
+ compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
1872
2753
  aggressiveness: resolveMessageEndpointAggressiveness(compression),
1873
2754
  signal
1874
2755
  });
@@ -2257,6 +3138,8 @@ function resolveBeginRequest(defaults, override) {
2257
3138
  if (requested) begin.requested = requested;
2258
3139
  const feature = current.feature ?? base.feature;
2259
3140
  if (feature) begin.feature = feature;
3141
+ const runId = current.runId ?? base.runId;
3142
+ if (runId) begin.runId = runId;
2260
3143
  const idempotency = current.idempotency ?? base.idempotency;
2261
3144
  if (idempotency) begin.idempotency = idempotency;
2262
3145
  const idempotencyKey = current.idempotencyKey ?? base.idempotencyKey;
@@ -2265,10 +3148,14 @@ function resolveBeginRequest(defaults, override) {
2265
3148
  if (customerName) begin.customerName = customerName;
2266
3149
  const customerEmail = current.customerEmail ?? base.customerEmail;
2267
3150
  if (customerEmail) begin.customerEmail = customerEmail;
3151
+ const customerUserId = current.customerUserId ?? base.customerUserId;
3152
+ if (customerUserId) begin.customerUserId = customerUserId;
3153
+ const customerUserName = current.customerUserName ?? base.customerUserName;
3154
+ if (customerUserName) begin.customerUserName = customerUserName;
3155
+ const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
3156
+ if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
2268
3157
  const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
2269
3158
  if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
2270
- const holdUsd = current.holdUsd ?? base.holdUsd;
2271
- if (typeof holdUsd === "number") begin.holdUsd = holdUsd;
2272
3159
  const batch = current.batch ?? base.batch;
2273
3160
  if (typeof batch === "boolean") begin.batch = batch;
2274
3161
  const pricingMode = current.pricingMode ?? base.pricingMode;
@@ -2280,6 +3167,11 @@ function resolveBeginRequest(defaults, override) {
2280
3167
  }
2281
3168
  function createCallState(beginRequest, begin) {
2282
3169
  const usage = {};
3170
+ const pricingMode = begin.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
3171
+ if (pricingMode) {
3172
+ usage.pricingMode = pricingMode;
3173
+ usage.batch = pricingMode === "batch";
3174
+ }
2283
3175
  const stripeCustomerId = typeof begin.data.stripeCustomerId === "string" ? begin.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
2284
3176
  if (stripeCustomerId) {
2285
3177
  usage.stripeCustomerId = stripeCustomerId;
@@ -2343,10 +3235,12 @@ function inferAnthropicUsage(response, fallbackModel, ctx) {
2343
3235
  }
2344
3236
  function extractAnthropicUsage(payload, fallbackModel) {
2345
3237
  if (!isObjectRecord(payload)) {
2346
- return fallbackModel ? { modelUsed: fallbackModel } : void 0;
3238
+ return { providerUsed: "anthropic", ...fallbackModel ? { modelUsed: fallbackModel } : {} };
2347
3239
  }
2348
3240
  const usage = findAnthropicUsageRecord(payload);
2349
- const result = {};
3241
+ const result = {
3242
+ providerUsed: "anthropic"
3243
+ };
2350
3244
  const model = readString(payload.model) ?? readString(payload.message?.model) ?? fallbackModel;
2351
3245
  if (model) {
2352
3246
  result.modelUsed = model;
@@ -2356,6 +3250,21 @@ function extractAnthropicUsage(payload, fallbackModel) {
2356
3250
  }
2357
3251
  return Object.keys(result).length ? result : void 0;
2358
3252
  }
3253
+ function anthropicRequestExecutionMetadata(params) {
3254
+ const outputConfig = isObjectRecord(params.output_config) ? params.output_config : void 0;
3255
+ const thinking = isObjectRecord(params.thinking) ? params.thinking : void 0;
3256
+ const rawEffort = outputConfig?.effort;
3257
+ const effort = rawEffort === "none" || rawEffort === "minimal" || rawEffort === "low" || rawEffort === "medium" || rawEffort === "high" || rawEffort === "xhigh" || rawEffort === "max" ? rawEffort : void 0;
3258
+ const mode = readString(thinking?.type);
3259
+ const rawBudget = readNumber(thinking?.budget_tokens);
3260
+ const budget = rawBudget !== void 0 && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
3261
+ return {
3262
+ providerUsed: "anthropic",
3263
+ ...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
3264
+ ...mode ? { reasoningMode: mode } : {},
3265
+ ...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
3266
+ };
3267
+ }
2359
3268
  function findAnthropicUsageRecord(payload) {
2360
3269
  if (isObjectRecord(payload.usage)) {
2361
3270
  return payload.usage;
@@ -2366,10 +3275,6 @@ function findAnthropicUsageRecord(payload) {
2366
3275
  return void 0;
2367
3276
  }
2368
3277
  function applyAnthropicUsageRecord(usage, usageRecord) {
2369
- const inputTokens = readNumber(usageRecord.input_tokens ?? usageRecord.prompt_tokens);
2370
- if (inputTokens !== void 0) {
2371
- usage.inputTokens = inputTokens;
2372
- }
2373
3278
  const outputTokens = readNumber(usageRecord.output_tokens ?? usageRecord.completion_tokens);
2374
3279
  if (outputTokens !== void 0) {
2375
3280
  usage.responseTokens = outputTokens;
@@ -2380,6 +3285,26 @@ function applyAnthropicUsageRecord(usage, usageRecord) {
2380
3285
  if (cachedInputTokens !== void 0) {
2381
3286
  usage.cachedInputTokens = cachedInputTokens;
2382
3287
  }
3288
+ const cacheWriteInputTokens = readNumber(
3289
+ usageRecord.cache_creation_input_tokens ?? usageRecord.prompt_cache_miss_tokens ?? usageRecord.cache_write_tokens
3290
+ );
3291
+ if (cacheWriteInputTokens !== void 0) {
3292
+ usage.cacheWriteInputTokens = cacheWriteInputTokens;
3293
+ }
3294
+ if (isObjectRecord(usageRecord.cache_creation)) {
3295
+ const cacheWrite5m = readNumber(
3296
+ usageRecord.cache_creation.ephemeral_5m_input_tokens
3297
+ );
3298
+ const cacheWrite1h = readNumber(
3299
+ usageRecord.cache_creation.ephemeral_1h_input_tokens
3300
+ );
3301
+ if (cacheWrite5m !== void 0) usage.cacheWrite5mInputTokens = cacheWrite5m;
3302
+ if (cacheWrite1h !== void 0) usage.cacheWrite1hInputTokens = cacheWrite1h;
3303
+ }
3304
+ const baseInputTokens = readNumber(usageRecord.input_tokens ?? usageRecord.prompt_tokens);
3305
+ if (baseInputTokens !== void 0) {
3306
+ usage.inputTokens = baseInputTokens + (cachedInputTokens ?? 0) + (cacheWriteInputTokens ?? 0);
3307
+ }
2383
3308
  }
2384
3309
  function accumulateAnthropicStreamUsage(chunk, fallbackModel, state) {
2385
3310
  const usage = extractAnthropicUsage(chunk, fallbackModel);
@@ -2603,6 +3528,6 @@ function isIteratorResult(value) {
2603
3528
  return isObjectRecord(value) && "done" in value;
2604
3529
  }
2605
3530
 
2606
- export { AnthropicPromptCompressionStats, withCompression, withMetering, wrapAnthropic };
3531
+ export { AnthropicPromptCompressionStats, withCompression, withMetering, withSampling, wrapAnthropic };
2607
3532
  //# sourceMappingURL=index.mjs.map
2608
3533
  //# sourceMappingURL=index.mjs.map