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