@usagetap/sdk 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -24
- package/dist/adapters/anthropic.cjs +990 -24
- package/dist/adapters/anthropic.cjs.map +1 -1
- package/dist/adapters/anthropic.d.cts +45 -3
- package/dist/adapters/anthropic.d.ts +45 -3
- package/dist/adapters/anthropic.mjs +990 -25
- package/dist/adapters/anthropic.mjs.map +1 -1
- package/dist/adapters/openai.cjs +1164 -44
- package/dist/adapters/openai.cjs.map +1 -1
- package/dist/adapters/openai.d.cts +46 -3
- package/dist/adapters/openai.d.ts +46 -3
- package/dist/adapters/openai.mjs +1164 -45
- package/dist/adapters/openai.mjs.map +1 -1
- package/dist/adapters/openrouter.cjs +3899 -22
- package/dist/adapters/openrouter.cjs.map +1 -1
- package/dist/adapters/openrouter.d.cts +6 -3
- package/dist/adapters/openrouter.d.ts +6 -3
- package/dist/adapters/openrouter.mjs +3897 -23
- package/dist/adapters/openrouter.mjs.map +1 -1
- package/dist/anthropic/index.cjs +990 -24
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.d.cts +2 -2
- package/dist/anthropic/index.d.ts +2 -2
- package/dist/anthropic/index.mjs +990 -25
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/client-CExQ8e1T.d.cts +1225 -0
- package/dist/client-CExQ8e1T.d.ts +1225 -0
- package/dist/express/index.cjs +421 -29
- package/dist/express/index.cjs.map +1 -1
- package/dist/express/index.d.cts +2 -2
- package/dist/express/index.d.ts +2 -2
- package/dist/express/index.mjs +421 -29
- package/dist/express/index.mjs.map +1 -1
- package/dist/index.cjs +680 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.mjs +680 -15
- package/dist/index.mjs.map +1 -1
- package/dist/openai/index.cjs +1165 -45
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.d.cts +2 -2
- package/dist/openai/index.d.ts +2 -2
- package/dist/openai/index.mjs +1165 -46
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openrouter/index.cjs +1182 -47
- package/dist/openrouter/index.cjs.map +1 -1
- package/dist/openrouter/index.d.cts +3 -3
- package/dist/openrouter/index.d.ts +3 -3
- package/dist/openrouter/index.mjs +1180 -46
- package/dist/openrouter/index.mjs.map +1 -1
- package/dist/react/index.cjs +19 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +17 -4
- package/dist/react/index.d.ts +17 -4
- package/dist/react/index.mjs +19 -1
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/client-BD8O2J8Z.d.cts +0 -668
- package/dist/client-BD8O2J8Z.d.ts +0 -668
package/dist/index.mjs
CHANGED
|
@@ -243,6 +243,11 @@ async function compressMessagesWithUsageTap(options) {
|
|
|
243
243
|
aggressiveness,
|
|
244
244
|
"UsageTap prompt message compression"
|
|
245
245
|
);
|
|
246
|
+
if (options.latencyBudgetMs !== void 0 && (!Number.isFinite(options.latencyBudgetMs) || options.latencyBudgetMs < 0)) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
"UsageTap prompt message compression latencyBudgetMs must be a non-negative number"
|
|
249
|
+
);
|
|
250
|
+
}
|
|
246
251
|
const original = stableStringifyInput(options.input);
|
|
247
252
|
const headers = {
|
|
248
253
|
"content-type": "application/json"
|
|
@@ -257,7 +262,15 @@ async function compressMessagesWithUsageTap(options) {
|
|
|
257
262
|
headers,
|
|
258
263
|
body: JSON.stringify({
|
|
259
264
|
...cloneInputRecord(options.input),
|
|
260
|
-
compression_settings: {
|
|
265
|
+
compression_settings: {
|
|
266
|
+
aggressiveness,
|
|
267
|
+
...options.mode === void 0 ? {} : { mode: options.mode },
|
|
268
|
+
...options.latencyBudgetMs === void 0 ? {} : { latency_budget_ms: options.latencyBudgetMs },
|
|
269
|
+
...options.compactEmptyUserMessages === void 0 ? {} : { compact_empty_user_messages: options.compactEmptyUserMessages },
|
|
270
|
+
...options.compactDuplicateUserTextParts === void 0 ? {} : {
|
|
271
|
+
compact_duplicate_user_text_parts: options.compactDuplicateUserTextParts
|
|
272
|
+
}
|
|
273
|
+
}
|
|
261
274
|
}),
|
|
262
275
|
signal: options.signal
|
|
263
276
|
}
|
|
@@ -753,10 +766,358 @@ function scalarToToon(value) {
|
|
|
753
766
|
return JSON.stringify(text);
|
|
754
767
|
}
|
|
755
768
|
|
|
769
|
+
// src/resources.ts
|
|
770
|
+
var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
|
|
771
|
+
var DEFAULT_GATEWAY_BASE_URL = "https://gateway.usagetap.com";
|
|
772
|
+
function normalizedBaseUrl(value) {
|
|
773
|
+
return `${value.replace(/\/+$/, "")}/`;
|
|
774
|
+
}
|
|
775
|
+
function errorMessage(payload, status) {
|
|
776
|
+
if (payload && typeof payload === "object") {
|
|
777
|
+
const record = payload;
|
|
778
|
+
const error = record.error;
|
|
779
|
+
if (error && typeof error === "object") {
|
|
780
|
+
const message2 = error.message;
|
|
781
|
+
if (typeof message2 === "string" && message2) return message2;
|
|
782
|
+
}
|
|
783
|
+
const message = record.message;
|
|
784
|
+
if (typeof message === "string" && message) return message;
|
|
785
|
+
}
|
|
786
|
+
return `UsageTap request failed with HTTP ${status}`;
|
|
787
|
+
}
|
|
788
|
+
function errorCode(status) {
|
|
789
|
+
if (status === 401 || status === 403) return "USAGETAP_AUTH_ERROR";
|
|
790
|
+
if (status === 429) return "USAGETAP_RATE_LIMITED";
|
|
791
|
+
if (status >= 500) return "USAGETAP_SERVER_ERROR";
|
|
792
|
+
return "USAGETAP_BAD_REQUEST";
|
|
793
|
+
}
|
|
794
|
+
var ResourceTransport = class {
|
|
795
|
+
baseUrl;
|
|
796
|
+
apiKey;
|
|
797
|
+
fetchImpl;
|
|
798
|
+
defaultHeaders;
|
|
799
|
+
sdkVersion;
|
|
800
|
+
constructor(baseUrl, config) {
|
|
801
|
+
this.baseUrl = normalizedBaseUrl(baseUrl);
|
|
802
|
+
this.apiKey = config.apiKey;
|
|
803
|
+
this.fetchImpl = config.fetchImpl;
|
|
804
|
+
this.defaultHeaders = config.headers ?? {};
|
|
805
|
+
this.sdkVersion = config.sdkVersion;
|
|
806
|
+
}
|
|
807
|
+
async request(request) {
|
|
808
|
+
const body = request.body === void 0 ? void 0 : JSON.stringify(request.body);
|
|
809
|
+
const headers = {
|
|
810
|
+
...this.defaultHeaders,
|
|
811
|
+
accept: request.response === "data" ? CANONICAL_MEDIA_TYPE : request.response === "ndjson" ? "application/x-ndjson" : "application/json",
|
|
812
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
813
|
+
"x-usage-sdk": `js/${this.sdkVersion}`,
|
|
814
|
+
...body ? { "content-type": "application/json" } : {},
|
|
815
|
+
...request.options?.idempotencyKey ? { "idempotency-key": request.options.idempotencyKey } : {},
|
|
816
|
+
...request.options?.headers
|
|
817
|
+
};
|
|
818
|
+
let response;
|
|
819
|
+
try {
|
|
820
|
+
response = await this.fetchImpl(
|
|
821
|
+
new URL(request.path.replace(/^\/+/, ""), this.baseUrl),
|
|
822
|
+
{
|
|
823
|
+
method: request.method,
|
|
824
|
+
headers,
|
|
825
|
+
body,
|
|
826
|
+
signal: request.options?.signal
|
|
827
|
+
}
|
|
828
|
+
);
|
|
829
|
+
} catch (error) {
|
|
830
|
+
throw new UsageTapError(
|
|
831
|
+
"USAGETAP_NETWORK_ERROR",
|
|
832
|
+
"Failed to reach UsageTap",
|
|
833
|
+
{ retryable: true, cause: error }
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
const requestId = response.headers.get("x-request-id") ?? response.headers.get("x-usage-correlation-id") ?? void 0;
|
|
837
|
+
const text = await response.text();
|
|
838
|
+
let payload;
|
|
839
|
+
if (text && request.response !== "ndjson") {
|
|
840
|
+
try {
|
|
841
|
+
payload = JSON.parse(text);
|
|
842
|
+
} catch (error) {
|
|
843
|
+
throw new UsageTapError(
|
|
844
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
845
|
+
"UsageTap returned invalid JSON",
|
|
846
|
+
{ status: response.status, correlationId: requestId, cause: error }
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
if (!response.ok) {
|
|
851
|
+
throw new UsageTapError(
|
|
852
|
+
errorCode(response.status),
|
|
853
|
+
errorMessage(payload, response.status),
|
|
854
|
+
{
|
|
855
|
+
status: response.status,
|
|
856
|
+
retryable: response.status === 429 || response.status >= 500,
|
|
857
|
+
correlationId: requestId,
|
|
858
|
+
details: payload && typeof payload === "object" ? payload : void 0
|
|
859
|
+
}
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
if (request.response === "ndjson") {
|
|
863
|
+
if (!text.trim()) return [];
|
|
864
|
+
try {
|
|
865
|
+
return text.trim().split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
866
|
+
} catch (error) {
|
|
867
|
+
throw new UsageTapError(
|
|
868
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
869
|
+
"UsageTap returned invalid NDJSON",
|
|
870
|
+
{ status: response.status, correlationId: requestId, cause: error }
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if (request.response === "data") {
|
|
875
|
+
if (!payload || typeof payload !== "object" || !("data" in payload)) {
|
|
876
|
+
throw new UsageTapError(
|
|
877
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
878
|
+
"UsageTap response missing data",
|
|
879
|
+
{ status: response.status, correlationId: requestId }
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
return payload.data;
|
|
883
|
+
}
|
|
884
|
+
if (payload === void 0) {
|
|
885
|
+
throw new UsageTapError(
|
|
886
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
887
|
+
"UsageTap response was empty",
|
|
888
|
+
{ status: response.status, correlationId: requestId }
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
return payload;
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
function resourceId(value, keys, label) {
|
|
895
|
+
const id = typeof value === "string" ? value : keys.map((key) => value[key]).find((candidate) => Boolean(candidate?.trim()));
|
|
896
|
+
if (!id?.trim()) {
|
|
897
|
+
throw new UsageTapError(
|
|
898
|
+
"USAGETAP_BAD_REQUEST",
|
|
899
|
+
`${label} requires a non-empty ID`
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
return id.trim();
|
|
903
|
+
}
|
|
904
|
+
function terminalSummary(status) {
|
|
905
|
+
return status === "COMPLETE" || status === "FAILED";
|
|
906
|
+
}
|
|
907
|
+
function terminalGatewayBatch(status) {
|
|
908
|
+
return ["completed", "failed", "expired", "cancelled"].includes(status);
|
|
909
|
+
}
|
|
910
|
+
function validateWaitOptions(options) {
|
|
911
|
+
const pollIntervalMs = options.pollIntervalMs ?? 1500;
|
|
912
|
+
const timeoutMs = options.timeoutMs ?? 3 * 6e4;
|
|
913
|
+
if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 0) {
|
|
914
|
+
throw new UsageTapError(
|
|
915
|
+
"USAGETAP_BAD_REQUEST",
|
|
916
|
+
"pollIntervalMs must be a non-negative number"
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
|
|
920
|
+
throw new UsageTapError(
|
|
921
|
+
"USAGETAP_BAD_REQUEST",
|
|
922
|
+
"timeoutMs must be a positive number"
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
return { pollIntervalMs, timeoutMs };
|
|
926
|
+
}
|
|
927
|
+
var SummarizationResource = class {
|
|
928
|
+
summaries;
|
|
929
|
+
batches;
|
|
930
|
+
profiles;
|
|
931
|
+
measurements;
|
|
932
|
+
transport;
|
|
933
|
+
constructor(config) {
|
|
934
|
+
this.transport = new ResourceTransport(config.apiBaseUrl, config);
|
|
935
|
+
this.summaries = {
|
|
936
|
+
create: (params, options) => this.transport.request({
|
|
937
|
+
method: "POST",
|
|
938
|
+
path: "/v1/compression/summaries",
|
|
939
|
+
body: params,
|
|
940
|
+
options,
|
|
941
|
+
response: "data"
|
|
942
|
+
}),
|
|
943
|
+
retrieve: (jobId, options) => this.transport.request({
|
|
944
|
+
method: "GET",
|
|
945
|
+
path: `/v1/compression/jobs/${encodeURIComponent(
|
|
946
|
+
resourceId(jobId, ["jobId"], "summaries.retrieve")
|
|
947
|
+
)}`,
|
|
948
|
+
options,
|
|
949
|
+
response: "data"
|
|
950
|
+
}),
|
|
951
|
+
wait: (job, options) => this.waitForSummary(job, options)
|
|
952
|
+
};
|
|
953
|
+
this.batches = {
|
|
954
|
+
create: (params, options) => this.transport.request({
|
|
955
|
+
method: "POST",
|
|
956
|
+
path: "/v1/compression/batches",
|
|
957
|
+
body: params,
|
|
958
|
+
options,
|
|
959
|
+
response: "data"
|
|
960
|
+
}),
|
|
961
|
+
retrieve: (batchId, options) => this.transport.request({
|
|
962
|
+
method: "GET",
|
|
963
|
+
path: `/v1/compression/batches/${encodeURIComponent(
|
|
964
|
+
resourceId(batchId, ["batchId"], "summarization.batches.retrieve")
|
|
965
|
+
)}`,
|
|
966
|
+
options,
|
|
967
|
+
response: "data"
|
|
968
|
+
}),
|
|
969
|
+
wait: (batch, options) => this.waitForBatch(batch, options)
|
|
970
|
+
};
|
|
971
|
+
this.profiles = {
|
|
972
|
+
retrieve: (profile, options) => this.transport.request({
|
|
973
|
+
method: "GET",
|
|
974
|
+
path: `/v1/compression/profiles/${encodeURIComponent(
|
|
975
|
+
resourceId(profile, [], "summarization.profiles.retrieve")
|
|
976
|
+
)}`,
|
|
977
|
+
options,
|
|
978
|
+
response: "data"
|
|
979
|
+
})
|
|
980
|
+
};
|
|
981
|
+
this.measurements = {
|
|
982
|
+
create: (params, options) => this.transport.request({
|
|
983
|
+
method: "POST",
|
|
984
|
+
path: "/v1/compression/measurements",
|
|
985
|
+
body: params,
|
|
986
|
+
options,
|
|
987
|
+
response: "data"
|
|
988
|
+
})
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
async waitForSummary(value, options = {}) {
|
|
992
|
+
const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
|
|
993
|
+
const deadline = Date.now() + timeoutMs;
|
|
994
|
+
let job = typeof value === "string" ? await this.summaries.retrieve(value, options) : value;
|
|
995
|
+
while (!terminalSummary(job.status)) {
|
|
996
|
+
if (Date.now() >= deadline) {
|
|
997
|
+
throw new UsageTapError(
|
|
998
|
+
"USAGETAP_RETRY_EXHAUSTED",
|
|
999
|
+
`Summarization job ${job.jobId} did not finish before timeout`,
|
|
1000
|
+
{ retryable: true }
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
await sleep(pollIntervalMs, options.signal);
|
|
1004
|
+
job = await this.summaries.retrieve(job.jobId, options);
|
|
1005
|
+
}
|
|
1006
|
+
return job;
|
|
1007
|
+
}
|
|
1008
|
+
async waitForBatch(value, options = {}) {
|
|
1009
|
+
const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
|
|
1010
|
+
const deadline = Date.now() + timeoutMs;
|
|
1011
|
+
let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
|
|
1012
|
+
while (!terminalSummary(batch.status)) {
|
|
1013
|
+
if (Date.now() >= deadline) {
|
|
1014
|
+
throw new UsageTapError(
|
|
1015
|
+
"USAGETAP_RETRY_EXHAUSTED",
|
|
1016
|
+
`Summarization batch ${batch.batchId} did not finish before timeout`,
|
|
1017
|
+
{ retryable: true }
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
await sleep(pollIntervalMs, options.signal);
|
|
1021
|
+
batch = await this.batches.retrieve(batch.batchId, options);
|
|
1022
|
+
}
|
|
1023
|
+
return batch;
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
var GatewayResource = class {
|
|
1027
|
+
chat;
|
|
1028
|
+
models;
|
|
1029
|
+
batches;
|
|
1030
|
+
transport;
|
|
1031
|
+
idempotencyGenerator;
|
|
1032
|
+
constructor(config) {
|
|
1033
|
+
this.transport = new ResourceTransport(
|
|
1034
|
+
config.gatewayBaseUrl ?? DEFAULT_GATEWAY_BASE_URL,
|
|
1035
|
+
config
|
|
1036
|
+
);
|
|
1037
|
+
this.idempotencyGenerator = config.idempotencyGenerator ?? createIdempotencyKey;
|
|
1038
|
+
this.chat = {
|
|
1039
|
+
completions: {
|
|
1040
|
+
create: (params, options) => this.transport.request({
|
|
1041
|
+
method: "POST",
|
|
1042
|
+
path: "/v1/chat/completions",
|
|
1043
|
+
body: params,
|
|
1044
|
+
options,
|
|
1045
|
+
response: "json"
|
|
1046
|
+
})
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
this.models = {
|
|
1050
|
+
list: (options) => this.transport.request({
|
|
1051
|
+
method: "GET",
|
|
1052
|
+
path: "/v1/models",
|
|
1053
|
+
options,
|
|
1054
|
+
response: "json"
|
|
1055
|
+
})
|
|
1056
|
+
};
|
|
1057
|
+
this.batches = {
|
|
1058
|
+
create: (params, options = {}) => this.transport.request({
|
|
1059
|
+
method: "POST",
|
|
1060
|
+
path: "/v1/batches",
|
|
1061
|
+
body: params,
|
|
1062
|
+
options: {
|
|
1063
|
+
...options,
|
|
1064
|
+
idempotencyKey: options.idempotencyKey ?? this.idempotencyGenerator()
|
|
1065
|
+
},
|
|
1066
|
+
response: "json"
|
|
1067
|
+
}),
|
|
1068
|
+
retrieve: (batchId, options) => this.transport.request({
|
|
1069
|
+
method: "GET",
|
|
1070
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1071
|
+
resourceId(batchId, ["id"], "gateway.batches.retrieve")
|
|
1072
|
+
)}`,
|
|
1073
|
+
options,
|
|
1074
|
+
response: "json"
|
|
1075
|
+
}),
|
|
1076
|
+
wait: (batch, options) => this.waitForBatch(batch, options),
|
|
1077
|
+
cancel: (batchId, options) => this.transport.request({
|
|
1078
|
+
method: "POST",
|
|
1079
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1080
|
+
resourceId(batchId, ["id"], "gateway.batches.cancel")
|
|
1081
|
+
)}/cancel`,
|
|
1082
|
+
options,
|
|
1083
|
+
response: "json"
|
|
1084
|
+
}),
|
|
1085
|
+
results: (batchId, options) => this.transport.request({
|
|
1086
|
+
method: "GET",
|
|
1087
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1088
|
+
resourceId(batchId, ["id"], "gateway.batches.results")
|
|
1089
|
+
)}/results`,
|
|
1090
|
+
options,
|
|
1091
|
+
response: "ndjson"
|
|
1092
|
+
})
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
async waitForBatch(value, options = {}) {
|
|
1096
|
+
const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
|
|
1097
|
+
const deadline = Date.now() + timeoutMs;
|
|
1098
|
+
let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
|
|
1099
|
+
while (!terminalGatewayBatch(batch.status)) {
|
|
1100
|
+
if (Date.now() >= deadline) {
|
|
1101
|
+
throw new UsageTapError(
|
|
1102
|
+
"USAGETAP_RETRY_EXHAUSTED",
|
|
1103
|
+
`Gateway batch ${batch.id} did not finish before timeout`,
|
|
1104
|
+
{ retryable: true }
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
await sleep(pollIntervalMs, options.signal);
|
|
1108
|
+
batch = await this.batches.retrieve(batch.id, options);
|
|
1109
|
+
}
|
|
1110
|
+
return batch;
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
|
|
756
1114
|
// src/client.ts
|
|
757
1115
|
var CALL_BEGIN_PATH = "call_begin";
|
|
758
1116
|
var CALL_END_PATH = "call_end";
|
|
759
1117
|
var COMPRESS_PROMPT_PATH = "compress_prompt";
|
|
1118
|
+
var SAMPLES_PATH = "samples";
|
|
1119
|
+
var SAMPLING_SETTINGS_PATH = "sampling/settings";
|
|
1120
|
+
var SAMPLING_DECIDE_PATH = "sampling/decide";
|
|
760
1121
|
var CHECK_USAGE_PATH = "customers/{customerId}/usage";
|
|
761
1122
|
var CREATE_CUSTOMER_PATH = "customers";
|
|
762
1123
|
var CHANGE_PLAN_PATH = "customers/{customerId}/change_plan";
|
|
@@ -767,11 +1128,16 @@ var CORRELATION_HEADER = "x-usage-correlation-id";
|
|
|
767
1128
|
var IDEMPOTENCY_HEADER = "idempotency-key";
|
|
768
1129
|
var SDK_HEADER = "x-usage-sdk";
|
|
769
1130
|
var USER_AGENT = "UsageTapClient";
|
|
770
|
-
var
|
|
1131
|
+
var CANONICAL_MEDIA_TYPE2 = "application/vnd.usagetap.v1+json";
|
|
771
1132
|
var DEFAULT_BASE_URL = "https://api.usagetap.com";
|
|
772
|
-
var
|
|
1133
|
+
var DEFAULT_RUN_INACTIVITY_MS = 60 * 60 * 1e3;
|
|
1134
|
+
var SDK_VERSION = "1.4.0" ;
|
|
773
1135
|
var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
774
1136
|
var UsageTapClient = class {
|
|
1137
|
+
/** OpenAI-compatible chat, model, and native batch operations. */
|
|
1138
|
+
gateway;
|
|
1139
|
+
/** Published-profile context summarization operations. */
|
|
1140
|
+
summarization;
|
|
775
1141
|
apiKey;
|
|
776
1142
|
baseUrl;
|
|
777
1143
|
fetchImpl;
|
|
@@ -796,6 +1162,11 @@ var UsageTapClient = class {
|
|
|
796
1162
|
usageTapCompressionMessagesEndpoint;
|
|
797
1163
|
usageTapCompressionModel;
|
|
798
1164
|
usageTapCompressionAggressiveness;
|
|
1165
|
+
sampling;
|
|
1166
|
+
samplingSettingsCacheMs;
|
|
1167
|
+
circuitBreaker;
|
|
1168
|
+
circuitBreakerRuns = /* @__PURE__ */ new Map();
|
|
1169
|
+
samplingSettingsCache;
|
|
799
1170
|
constructor(options = {}) {
|
|
800
1171
|
const apiKey = options.apiKey?.trim() || readEnvironmentVariable("USAGETAP_API_KEY");
|
|
801
1172
|
const baseUrl = options.baseUrl?.trim() || readEnvironmentVariable("USAGETAP_BASE_URL") || DEFAULT_BASE_URL;
|
|
@@ -818,10 +1189,21 @@ var UsageTapClient = class {
|
|
|
818
1189
|
"A global fetch implementation was not found. Pass fetchImpl in UsageTapClientOptions."
|
|
819
1190
|
);
|
|
820
1191
|
}
|
|
821
|
-
const
|
|
822
|
-
this.baseUrl = new URL(
|
|
1192
|
+
const normalizedBaseUrl2 = normalizeBaseUrl(baseUrl);
|
|
1193
|
+
this.baseUrl = new URL(normalizedBaseUrl2);
|
|
823
1194
|
this.apiKey = apiKey;
|
|
824
1195
|
this.fetchImpl = wrapFetchImplementation(fetchCandidate, !options.fetchImpl);
|
|
1196
|
+
const resourceConfig = {
|
|
1197
|
+
apiKey,
|
|
1198
|
+
apiBaseUrl: normalizedBaseUrl2,
|
|
1199
|
+
gatewayBaseUrl: options.gatewayBaseUrl?.trim() || readEnvironmentVariable("USAGETAP_GATEWAY_URL"),
|
|
1200
|
+
fetchImpl: this.fetchImpl,
|
|
1201
|
+
headers: options.headers,
|
|
1202
|
+
sdkVersion: SDK_VERSION,
|
|
1203
|
+
idempotencyGenerator: options.idempotencyGenerator
|
|
1204
|
+
};
|
|
1205
|
+
this.gateway = new GatewayResource(resourceConfig);
|
|
1206
|
+
this.summarization = new SummarizationResource(resourceConfig);
|
|
825
1207
|
this.defaultFeature = options.defaultFeature;
|
|
826
1208
|
this.defaultTags = options.defaultTags?.length ? dedupeStrings(options.defaultTags) : void 0;
|
|
827
1209
|
this.defaultHeaders = options.headers ? normalizeHeaderDictionary(options.headers) : {};
|
|
@@ -843,11 +1225,122 @@ var UsageTapClient = class {
|
|
|
843
1225
|
this.usageTapCompressionMessagesEndpoint = options.usageTapCompressionMessagesEndpoint;
|
|
844
1226
|
this.usageTapCompressionModel = options.usageTapCompressionModel;
|
|
845
1227
|
this.usageTapCompressionAggressiveness = options.usageTapCompressionAggressiveness;
|
|
1228
|
+
this.sampling = options.sampling;
|
|
1229
|
+
this.samplingSettingsCacheMs = Number.isFinite(options.samplingSettingsCacheMs) ? Math.max(0, Number(options.samplingSettingsCacheMs)) : 5 * 60 * 1e3;
|
|
1230
|
+
if (options.circuitBreaker) {
|
|
1231
|
+
const maxCallsPerRun = options.circuitBreaker.maxCallsPerRun;
|
|
1232
|
+
if (!Number.isInteger(maxCallsPerRun) || maxCallsPerRun < 1) {
|
|
1233
|
+
throw new UsageTapError(
|
|
1234
|
+
"USAGETAP_BAD_REQUEST",
|
|
1235
|
+
"circuitBreaker.maxCallsPerRun must be a positive integer"
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
const runInactivityMs = options.circuitBreaker.runInactivityMs ?? DEFAULT_RUN_INACTIVITY_MS;
|
|
1239
|
+
if (!Number.isFinite(runInactivityMs) || runInactivityMs < 1) {
|
|
1240
|
+
throw new UsageTapError(
|
|
1241
|
+
"USAGETAP_BAD_REQUEST",
|
|
1242
|
+
"circuitBreaker.runInactivityMs must be a positive number"
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1245
|
+
this.circuitBreaker = {
|
|
1246
|
+
maxCallsPerRun,
|
|
1247
|
+
runInactivityMs
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
shouldSample(request, policy = this.sampling || void 0) {
|
|
1252
|
+
if (!policy) return false;
|
|
1253
|
+
const rate = Math.min(1, Math.max(0, Number(policy.rate) || 0));
|
|
1254
|
+
if (rate <= 0) return false;
|
|
1255
|
+
const customerId = request.customerId?.trim();
|
|
1256
|
+
if (customerId && policy.customers?.exclude?.includes(customerId)) return false;
|
|
1257
|
+
const feature = request.feature?.trim();
|
|
1258
|
+
if (feature && policy.features?.exclude?.includes(feature)) return false;
|
|
1259
|
+
const included = policy.features?.include?.filter(Boolean) ?? [];
|
|
1260
|
+
if (included.length > 0 && (!feature || !included.includes(feature))) return false;
|
|
1261
|
+
const minimum = Math.max(0, Math.round(policy.minInputTokens ?? 0));
|
|
1262
|
+
if (minimum > 0 && estimatePromptTokens(request.input) < minimum) return false;
|
|
1263
|
+
return (policy.random ?? Math.random)() < rate;
|
|
1264
|
+
}
|
|
1265
|
+
async getSamplingSettings(options = {}) {
|
|
1266
|
+
const now = Date.now();
|
|
1267
|
+
if (!options.forceRefresh && this.samplingSettingsCache && this.samplingSettingsCache.expiresAtMs > now) {
|
|
1268
|
+
return {
|
|
1269
|
+
result: { status: "ACCEPTED", code: "SAMPLING_SETTINGS_CACHED" },
|
|
1270
|
+
data: this.samplingSettingsCache.settings,
|
|
1271
|
+
correlationId: options.correlationId ?? "local-cache"
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
const response = await this.requestGet(
|
|
1275
|
+
SAMPLING_SETTINGS_PATH,
|
|
1276
|
+
{
|
|
1277
|
+
signal: options.signal,
|
|
1278
|
+
headers: options.headers,
|
|
1279
|
+
retries: options.retries,
|
|
1280
|
+
correlationId: options.correlationId
|
|
1281
|
+
}
|
|
1282
|
+
);
|
|
1283
|
+
const serverCacheMs = Math.max(0, Number(response.data.cacheSeconds) || 0) * 1e3;
|
|
1284
|
+
const cacheMs = Math.min(this.samplingSettingsCacheMs, serverCacheMs);
|
|
1285
|
+
this.samplingSettingsCache = {
|
|
1286
|
+
settings: response.data,
|
|
1287
|
+
expiresAtMs: now + cacheMs
|
|
1288
|
+
};
|
|
1289
|
+
return response;
|
|
1290
|
+
}
|
|
1291
|
+
async shouldSampleAsync(request, policy) {
|
|
1292
|
+
if (policy) return this.shouldSample(request, policy);
|
|
1293
|
+
if (this.sampling === false) return false;
|
|
1294
|
+
if (this.sampling) return this.shouldSample(request, this.sampling);
|
|
1295
|
+
try {
|
|
1296
|
+
const settings = await this.getSamplingSettings();
|
|
1297
|
+
return this.shouldSample(request, settings.data);
|
|
1298
|
+
} catch {
|
|
1299
|
+
return false;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
async decideSample(request, options = {}) {
|
|
1303
|
+
const hasTokens = Number.isFinite(request.inputTokens) && Number(request.inputTokens) >= 0;
|
|
1304
|
+
const hasCharacters = Number.isFinite(request.inputCharacters) && Number(request.inputCharacters) >= 0;
|
|
1305
|
+
if (!hasTokens && !hasCharacters) {
|
|
1306
|
+
throw new UsageTapError(
|
|
1307
|
+
"USAGETAP_BAD_REQUEST",
|
|
1308
|
+
"decideSample requires inputTokens or inputCharacters"
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
return this.request(
|
|
1312
|
+
SAMPLING_DECIDE_PATH,
|
|
1313
|
+
request,
|
|
1314
|
+
options
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
async captureSample(request, options = {}) {
|
|
1318
|
+
if (!request || request.input === void 0) {
|
|
1319
|
+
throw new UsageTapError(
|
|
1320
|
+
"USAGETAP_BAD_REQUEST",
|
|
1321
|
+
"captureSample requires input"
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
if (!request.provider?.trim()) {
|
|
1325
|
+
throw new UsageTapError(
|
|
1326
|
+
"USAGETAP_BAD_REQUEST",
|
|
1327
|
+
"captureSample requires provider"
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
const sampleId = request.sampleId?.trim() || this.idempotencyGenerator();
|
|
1331
|
+
return this.request(
|
|
1332
|
+
SAMPLES_PATH,
|
|
1333
|
+
{ ...request, sampleId },
|
|
1334
|
+
{ ...options, idempotencyKey: sampleId }
|
|
1335
|
+
);
|
|
846
1336
|
}
|
|
847
1337
|
async beginCall(request, options = {}) {
|
|
848
1338
|
const idempotencyKey = request.idempotencyKey ?? request.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
|
|
1339
|
+
this.reserveRunCall(request, idempotencyKey);
|
|
1340
|
+
const apiRequest = { ...request };
|
|
1341
|
+
delete apiRequest.runId;
|
|
849
1342
|
const payload = {
|
|
850
|
-
...
|
|
1343
|
+
...apiRequest,
|
|
851
1344
|
feature: request.feature ?? this.defaultFeature,
|
|
852
1345
|
tags: this.mergeTags(request.tags)
|
|
853
1346
|
};
|
|
@@ -865,6 +1358,28 @@ var UsageTapClient = class {
|
|
|
865
1358
|
);
|
|
866
1359
|
return response;
|
|
867
1360
|
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Inspect a configured run circuit breaker without consuming another call.
|
|
1363
|
+
*/
|
|
1364
|
+
canRunContinue(request) {
|
|
1365
|
+
const identity = this.resolveRunIdentity(request);
|
|
1366
|
+
if (!identity || !this.circuitBreaker) {
|
|
1367
|
+
throw new UsageTapError(
|
|
1368
|
+
"USAGETAP_BAD_REQUEST",
|
|
1369
|
+
"canRunContinue requires circuitBreaker configuration and a non-empty runId"
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
this.expireInactiveRuns();
|
|
1373
|
+
const calls = this.circuitBreakerRuns.get(identity.key)?.calls ?? 0;
|
|
1374
|
+
return this.createCircuitBreakerDecision(identity.customerId, identity.runId, calls);
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Release local state after a workflow finishes. Returns true when state existed.
|
|
1378
|
+
*/
|
|
1379
|
+
resetRun(request) {
|
|
1380
|
+
const identity = this.resolveRunIdentity(request);
|
|
1381
|
+
return identity ? this.circuitBreakerRuns.delete(identity.key) : false;
|
|
1382
|
+
}
|
|
868
1383
|
async promptCompress(request, options = {}) {
|
|
869
1384
|
if (!request?.callId) {
|
|
870
1385
|
throw new UsageTapError(
|
|
@@ -962,6 +1477,10 @@ var UsageTapClient = class {
|
|
|
962
1477
|
usageTapCompressionMessagesEndpoint: this.usageTapCompressionMessagesEndpoint,
|
|
963
1478
|
aggressiveness: options.aggressiveness ?? this.aggressiveness,
|
|
964
1479
|
usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
|
|
1480
|
+
mode: options.mode,
|
|
1481
|
+
latencyBudgetMs: options.latencyBudgetMs,
|
|
1482
|
+
compactEmptyUserMessages: options.compactEmptyUserMessages,
|
|
1483
|
+
compactDuplicateUserTextParts: options.compactDuplicateUserTextParts,
|
|
965
1484
|
fetchImpl: this.fetchImpl,
|
|
966
1485
|
signal: options.signal,
|
|
967
1486
|
failOpen: options.failOpen
|
|
@@ -1003,11 +1522,17 @@ var UsageTapClient = class {
|
|
|
1003
1522
|
callId: request.callId,
|
|
1004
1523
|
feature: feature ?? this.defaultFeature,
|
|
1005
1524
|
tags: tags ?? this.defaultTags,
|
|
1525
|
+
providerUsed: request.providerUsed,
|
|
1006
1526
|
modelUsed: request.modelUsed,
|
|
1527
|
+
reasoningEffort: request.reasoningEffort,
|
|
1528
|
+
reasoningEffortSource: request.reasoningEffortSource,
|
|
1529
|
+
reasoningMode: request.reasoningMode,
|
|
1530
|
+
reasoningBudgetTokens: request.reasoningBudgetTokens,
|
|
1007
1531
|
metrics: {
|
|
1008
1532
|
inputTokens: request.inputTokens,
|
|
1009
1533
|
responseTokens: request.responseTokens,
|
|
1010
1534
|
cachedInputTokens: request.cachedInputTokens,
|
|
1535
|
+
cacheWriteInputTokens: request.cacheWriteInputTokens,
|
|
1011
1536
|
reasoningTokens: request.reasoningTokens,
|
|
1012
1537
|
searches: request.searches,
|
|
1013
1538
|
audioSeconds: request.audioSeconds,
|
|
@@ -1115,6 +1640,15 @@ var UsageTapClient = class {
|
|
|
1115
1640
|
meterSlot: request.meterSlot,
|
|
1116
1641
|
amount: request.amount
|
|
1117
1642
|
};
|
|
1643
|
+
if (request.customerUserId) {
|
|
1644
|
+
payload.customerUserId = request.customerUserId;
|
|
1645
|
+
}
|
|
1646
|
+
if (request.customerUserName) {
|
|
1647
|
+
payload.customerUserName = request.customerUserName;
|
|
1648
|
+
}
|
|
1649
|
+
if (request.customerUserEmail) {
|
|
1650
|
+
payload.customerUserEmail = request.customerUserEmail;
|
|
1651
|
+
}
|
|
1118
1652
|
if (request.feature) {
|
|
1119
1653
|
payload.feature = request.feature;
|
|
1120
1654
|
}
|
|
@@ -1151,6 +1685,11 @@ var UsageTapClient = class {
|
|
|
1151
1685
|
const beginPayload = idempotencyKey ? { ...beginRequest, idempotencyKey, idempotency: idempotencyKey } : { ...beginRequest };
|
|
1152
1686
|
const beginResponse = await this.beginCall(beginPayload, options);
|
|
1153
1687
|
let usage = {};
|
|
1688
|
+
const pricingMode = beginResponse.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
|
|
1689
|
+
if (pricingMode) {
|
|
1690
|
+
usage.pricingMode = pricingMode;
|
|
1691
|
+
usage.batch = pricingMode === "batch";
|
|
1692
|
+
}
|
|
1154
1693
|
const initialStripeCustomerId = typeof beginResponse.data.stripeCustomerId === "string" ? beginResponse.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
|
|
1155
1694
|
if (initialStripeCustomerId) {
|
|
1156
1695
|
usage = { ...usage, stripeCustomerId: initialStripeCustomerId };
|
|
@@ -1218,17 +1757,86 @@ var UsageTapClient = class {
|
|
|
1218
1757
|
toPromptCompressionTelemetry(result) {
|
|
1219
1758
|
return {
|
|
1220
1759
|
provider: result.provider,
|
|
1221
|
-
originalCharacters: result.originalCharacters,
|
|
1222
|
-
compressedCharacters: result.compressedCharacters,
|
|
1223
|
-
savedCharacters: result.savedCharacters,
|
|
1224
1760
|
originalTokens: result.originalTokens,
|
|
1225
1761
|
compressedTokens: result.compressedTokens,
|
|
1226
1762
|
savedTokens: result.savedTokens,
|
|
1227
1763
|
tokenSavingsRatio: result.tokenSavingsRatio,
|
|
1228
|
-
savingsRatio: result.savingsRatio,
|
|
1229
1764
|
techniques: result.techniques
|
|
1230
1765
|
};
|
|
1231
1766
|
}
|
|
1767
|
+
reserveRunCall(request, idempotencyKey) {
|
|
1768
|
+
const identity = this.resolveRunIdentity(request);
|
|
1769
|
+
if (!identity || !this.circuitBreaker) return;
|
|
1770
|
+
this.expireInactiveRuns();
|
|
1771
|
+
const now = Date.now();
|
|
1772
|
+
const state = this.circuitBreakerRuns.get(identity.key) ?? {
|
|
1773
|
+
calls: 0,
|
|
1774
|
+
reservationKeys: /* @__PURE__ */ new Set(),
|
|
1775
|
+
lastSeenAtMs: now
|
|
1776
|
+
};
|
|
1777
|
+
const reservationKey = idempotencyKey ?? this.idempotencyGenerator();
|
|
1778
|
+
state.lastSeenAtMs = now;
|
|
1779
|
+
if (state.reservationKeys.has(reservationKey)) {
|
|
1780
|
+
this.circuitBreakerRuns.set(identity.key, state);
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
const decision = this.createCircuitBreakerDecision(
|
|
1784
|
+
identity.customerId,
|
|
1785
|
+
identity.runId,
|
|
1786
|
+
state.calls
|
|
1787
|
+
);
|
|
1788
|
+
if (!decision.allowed) {
|
|
1789
|
+
throw new UsageTapError(
|
|
1790
|
+
"USAGETAP_CIRCUIT_OPEN",
|
|
1791
|
+
`Run ${identity.runId} reached its ${decision.limit}-call circuit-breaker limit`,
|
|
1792
|
+
{
|
|
1793
|
+
details: {
|
|
1794
|
+
reason: decision.reason,
|
|
1795
|
+
customerId: identity.customerId,
|
|
1796
|
+
runId: identity.runId,
|
|
1797
|
+
calls: decision.calls,
|
|
1798
|
+
limit: decision.limit,
|
|
1799
|
+
remaining: decision.remaining
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
);
|
|
1803
|
+
}
|
|
1804
|
+
state.calls += 1;
|
|
1805
|
+
state.reservationKeys.add(reservationKey);
|
|
1806
|
+
this.circuitBreakerRuns.set(identity.key, state);
|
|
1807
|
+
}
|
|
1808
|
+
resolveRunIdentity(request) {
|
|
1809
|
+
const customerId = request.customerId?.trim();
|
|
1810
|
+
const runId = request.runId?.trim();
|
|
1811
|
+
if (!customerId || !runId) return void 0;
|
|
1812
|
+
return {
|
|
1813
|
+
key: `${customerId}\0${runId}`,
|
|
1814
|
+
customerId,
|
|
1815
|
+
runId
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
createCircuitBreakerDecision(customerId, runId, calls) {
|
|
1819
|
+
const limit = this.circuitBreaker?.maxCallsPerRun ?? 0;
|
|
1820
|
+
const allowed = calls < limit;
|
|
1821
|
+
return {
|
|
1822
|
+
allowed,
|
|
1823
|
+
...allowed ? {} : { reason: "max_calls_per_run" },
|
|
1824
|
+
customerId,
|
|
1825
|
+
runId,
|
|
1826
|
+
calls,
|
|
1827
|
+
limit,
|
|
1828
|
+
remaining: Math.max(0, limit - calls)
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
expireInactiveRuns() {
|
|
1832
|
+
if (!this.circuitBreaker || this.circuitBreakerRuns.size === 0) return;
|
|
1833
|
+
const expiredBefore = Date.now() - this.circuitBreaker.runInactivityMs;
|
|
1834
|
+
for (const [key, state] of this.circuitBreakerRuns) {
|
|
1835
|
+
if (state.lastSeenAtMs < expiredBefore) {
|
|
1836
|
+
this.circuitBreakerRuns.delete(key);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1232
1840
|
async request(path, payload, options) {
|
|
1233
1841
|
const url = new URL(path, this.baseUrl).toString();
|
|
1234
1842
|
const body = payload !== void 0 ? JSON.stringify(payload) : void 0;
|
|
@@ -1416,7 +2024,7 @@ var UsageTapClient = class {
|
|
|
1416
2024
|
...this.defaultHeaders,
|
|
1417
2025
|
[SDK_HEADER]: `js/${SDK_VERSION}`,
|
|
1418
2026
|
"content-type": "application/json",
|
|
1419
|
-
accept:
|
|
2027
|
+
accept: CANONICAL_MEDIA_TYPE2
|
|
1420
2028
|
};
|
|
1421
2029
|
if (!HAS_WINDOW) {
|
|
1422
2030
|
headers["user-agent"] = `${USER_AGENT}/${SDK_VERSION}`;
|
|
@@ -1604,7 +2212,8 @@ function wrapFetch(usageTap, options) {
|
|
|
1604
2212
|
defaultContext,
|
|
1605
2213
|
baseFetch = globalThis.fetch,
|
|
1606
2214
|
autoIdempotency = true,
|
|
1607
|
-
isOpenAIEndpoint = defaultIsOpenAIEndpoint
|
|
2215
|
+
isOpenAIEndpoint = defaultIsOpenAIEndpoint,
|
|
2216
|
+
provider = "openai"
|
|
1608
2217
|
} = options;
|
|
1609
2218
|
return async function wrappedFetch(input, init) {
|
|
1610
2219
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
@@ -1626,6 +2235,7 @@ function wrapFetch(usageTap, options) {
|
|
|
1626
2235
|
const customerIdHeader = headers.get("x-usagetap-customer-id");
|
|
1627
2236
|
const featureHeader = headers.get("x-usagetap-feature");
|
|
1628
2237
|
const tagsHeader = headers.get("x-usagetap-tags");
|
|
2238
|
+
const runIdHeader = headers.get("x-usagetap-run-id");
|
|
1629
2239
|
if (customerIdHeader) {
|
|
1630
2240
|
contextOverride.customerId = customerIdHeader;
|
|
1631
2241
|
}
|
|
@@ -1638,6 +2248,9 @@ function wrapFetch(usageTap, options) {
|
|
|
1638
2248
|
contextOverride.tags = tags;
|
|
1639
2249
|
}
|
|
1640
2250
|
}
|
|
2251
|
+
if (runIdHeader) {
|
|
2252
|
+
contextOverride.runId = runIdHeader;
|
|
2253
|
+
}
|
|
1641
2254
|
}
|
|
1642
2255
|
} catch {
|
|
1643
2256
|
return baseFetch(input, init);
|
|
@@ -1653,10 +2266,25 @@ function wrapFetch(usageTap, options) {
|
|
|
1653
2266
|
callState = {
|
|
1654
2267
|
callId: beginResponse.data.callId,
|
|
1655
2268
|
correlationId: beginResponse.correlationId,
|
|
1656
|
-
usage: {
|
|
2269
|
+
usage: {
|
|
2270
|
+
...executionMetadataFromOpenAIRequest(body, provider),
|
|
2271
|
+
...beginResponse.data.pricingMode ? {
|
|
2272
|
+
pricingMode: beginResponse.data.pricingMode,
|
|
2273
|
+
batch: beginResponse.data.pricingMode === "batch"
|
|
2274
|
+
} : context.pricingMode ? {
|
|
2275
|
+
pricingMode: context.pricingMode,
|
|
2276
|
+
batch: context.pricingMode === "batch"
|
|
2277
|
+
} : typeof context.batch === "boolean" ? {
|
|
2278
|
+
batch: context.batch,
|
|
2279
|
+
pricingMode: context.batch ? "batch" : "standard"
|
|
2280
|
+
} : {}
|
|
2281
|
+
},
|
|
1657
2282
|
finalized: false
|
|
1658
2283
|
};
|
|
1659
2284
|
} catch (error) {
|
|
2285
|
+
if (isUsageTapError(error) && error.code === "USAGETAP_CIRCUIT_OPEN") {
|
|
2286
|
+
throw error;
|
|
2287
|
+
}
|
|
1660
2288
|
console.error("[wrapFetch] Failed to begin call:", error);
|
|
1661
2289
|
return baseFetch(input, init);
|
|
1662
2290
|
}
|
|
@@ -1697,6 +2325,9 @@ async function wrapNonStreamingResponse(response, callState, usageTap, requestBo
|
|
|
1697
2325
|
if (isJsonRecord(usageBlock)) {
|
|
1698
2326
|
const usageRecord = usageBlock;
|
|
1699
2327
|
const promptDetails = isJsonRecord(usageRecord.prompt_tokens_details) ? usageRecord.prompt_tokens_details : void 0;
|
|
2328
|
+
const completionDetails = isJsonRecord(
|
|
2329
|
+
usageRecord.completion_tokens_details ?? usageRecord.output_tokens_details
|
|
2330
|
+
) ? usageRecord.completion_tokens_details ?? usageRecord.output_tokens_details : void 0;
|
|
1700
2331
|
const promptTokens = readNumber(usageRecord.prompt_tokens ?? usageRecord.input_tokens);
|
|
1701
2332
|
if (promptTokens !== void 0) {
|
|
1702
2333
|
usage.inputTokens = promptTokens;
|
|
@@ -1711,7 +2342,9 @@ async function wrapNonStreamingResponse(response, callState, usageTap, requestBo
|
|
|
1711
2342
|
if (cachedTokens !== void 0) {
|
|
1712
2343
|
usage.cachedInputTokens = cachedTokens;
|
|
1713
2344
|
}
|
|
1714
|
-
const reasoningTokens = readNumber(
|
|
2345
|
+
const reasoningTokens = readNumber(
|
|
2346
|
+
usageRecord.reasoning_tokens ?? completionDetails?.reasoning_tokens
|
|
2347
|
+
);
|
|
1715
2348
|
if (reasoningTokens !== void 0) {
|
|
1716
2349
|
usage.reasoningTokens = reasoningTokens;
|
|
1717
2350
|
}
|
|
@@ -1720,6 +2353,14 @@ async function wrapNonStreamingResponse(response, callState, usageTap, requestBo
|
|
|
1720
2353
|
if (modelFromResponse) {
|
|
1721
2354
|
usage.modelUsed = modelFromResponse;
|
|
1722
2355
|
}
|
|
2356
|
+
const reasoning = isJsonRecord(parsed.reasoning) ? parsed.reasoning : void 0;
|
|
2357
|
+
const responseEffort = normalizeReasoningEffort(reasoning?.effort);
|
|
2358
|
+
if (responseEffort) {
|
|
2359
|
+
usage.reasoningEffort = responseEffort;
|
|
2360
|
+
usage.reasoningEffortSource = "provider_response";
|
|
2361
|
+
}
|
|
2362
|
+
const responseMode = readString(reasoning?.type ?? reasoning?.mode);
|
|
2363
|
+
if (responseMode) usage.reasoningMode = responseMode;
|
|
1723
2364
|
}
|
|
1724
2365
|
if (!usage.modelUsed && requestBody) {
|
|
1725
2366
|
const requestModel = readString(requestBody.model);
|
|
@@ -1767,6 +2408,9 @@ function wrapStreamingResponse(response, callState, usageTap) {
|
|
|
1767
2408
|
if (isJsonRecord(usageBlock)) {
|
|
1768
2409
|
const usageRecord = usageBlock;
|
|
1769
2410
|
const promptDetails = isJsonRecord(usageRecord.prompt_tokens_details) ? usageRecord.prompt_tokens_details : void 0;
|
|
2411
|
+
const completionDetails = isJsonRecord(
|
|
2412
|
+
usageRecord.completion_tokens_details ?? usageRecord.output_tokens_details
|
|
2413
|
+
) ? usageRecord.completion_tokens_details ?? usageRecord.output_tokens_details : void 0;
|
|
1770
2414
|
const promptTokens = readNumber(usageRecord.prompt_tokens ?? usageRecord.input_tokens);
|
|
1771
2415
|
if (promptTokens !== void 0) {
|
|
1772
2416
|
accumulatedUsage.inputTokens = promptTokens;
|
|
@@ -1781,7 +2425,9 @@ function wrapStreamingResponse(response, callState, usageTap) {
|
|
|
1781
2425
|
if (cachedTokens !== void 0) {
|
|
1782
2426
|
accumulatedUsage.cachedInputTokens = cachedTokens;
|
|
1783
2427
|
}
|
|
1784
|
-
const reasoningTokens = readNumber(
|
|
2428
|
+
const reasoningTokens = readNumber(
|
|
2429
|
+
usageRecord.reasoning_tokens ?? completionDetails?.reasoning_tokens
|
|
2430
|
+
);
|
|
1785
2431
|
if (reasoningTokens !== void 0) {
|
|
1786
2432
|
accumulatedUsage.reasoningTokens = reasoningTokens;
|
|
1787
2433
|
}
|
|
@@ -1806,6 +2452,25 @@ function wrapStreamingResponse(response, callState, usageTap) {
|
|
|
1806
2452
|
headers: response.headers
|
|
1807
2453
|
});
|
|
1808
2454
|
}
|
|
2455
|
+
function normalizeReasoningEffort(value) {
|
|
2456
|
+
return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
|
|
2457
|
+
}
|
|
2458
|
+
function executionMetadataFromOpenAIRequest(body, provider) {
|
|
2459
|
+
const reasoning = isJsonRecord(body?.reasoning) ? body.reasoning : void 0;
|
|
2460
|
+
const effort = normalizeReasoningEffort(
|
|
2461
|
+
body?.reasoning_effort ?? reasoning?.effort ?? body?.thinking_level
|
|
2462
|
+
);
|
|
2463
|
+
const mode = readString(reasoning?.type ?? reasoning?.mode);
|
|
2464
|
+
const budget = readNumber(
|
|
2465
|
+
reasoning?.budget_tokens ?? body?.thinking_budget ?? body?.thinking_budget_tokens
|
|
2466
|
+
);
|
|
2467
|
+
return {
|
|
2468
|
+
providerUsed: provider,
|
|
2469
|
+
...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
|
|
2470
|
+
...mode ? { reasoningMode: mode } : {},
|
|
2471
|
+
...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
1809
2474
|
async function finalizeCall(callState, usageTap, error, usage) {
|
|
1810
2475
|
if (callState.finalized) return;
|
|
1811
2476
|
callState.finalized = true;
|