@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.
- package/README.md +372 -39
- package/dist/adapters/anthropic.cjs +995 -69
- 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 +995 -70
- package/dist/adapters/anthropic.mjs.map +1 -1
- package/dist/adapters/openai.cjs +1208 -106
- 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 +1208 -107
- package/dist/adapters/openai.mjs.map +1 -1
- package/dist/adapters/openrouter.cjs +3912 -53
- 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 +3910 -54
- package/dist/adapters/openrouter.mjs.map +1 -1
- package/dist/anthropic/index.cjs +995 -69
- 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 +995 -70
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/client-C0UiaqVB.d.cts +1305 -0
- package/dist/client-C0UiaqVB.d.ts +1305 -0
- package/dist/express/index.cjs +399 -64
- 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 +399 -64
- package/dist/express/index.mjs.map +1 -1
- package/dist/index.cjs +1044 -163
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -5
- package/dist/index.d.ts +16 -5
- package/dist/index.mjs +1044 -163
- package/dist/index.mjs.map +1 -1
- package/dist/openai/index.cjs +1209 -107
- 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 +1209 -108
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openrouter/index.cjs +1226 -109
- 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 +1224 -108
- 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 +2 -2
- package/dist/client-BD8O2J8Z.d.cts +0 -668
- package/dist/client-BD8O2J8Z.d.ts +0 -668
package/dist/openai/index.cjs
CHANGED
|
@@ -242,6 +242,11 @@ async function compressMessagesWithUsageTap(options) {
|
|
|
242
242
|
aggressiveness,
|
|
243
243
|
"UsageTap prompt message compression"
|
|
244
244
|
);
|
|
245
|
+
if (options.latencyBudgetMs !== void 0 && (!Number.isFinite(options.latencyBudgetMs) || options.latencyBudgetMs < 0)) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
"UsageTap prompt message compression latencyBudgetMs must be a non-negative number"
|
|
248
|
+
);
|
|
249
|
+
}
|
|
245
250
|
const original = stableStringifyInput(options.input);
|
|
246
251
|
const headers = {
|
|
247
252
|
"content-type": "application/json"
|
|
@@ -256,7 +261,15 @@ async function compressMessagesWithUsageTap(options) {
|
|
|
256
261
|
headers,
|
|
257
262
|
body: JSON.stringify({
|
|
258
263
|
...cloneInputRecord(options.input),
|
|
259
|
-
compression_settings: {
|
|
264
|
+
compression_settings: {
|
|
265
|
+
aggressiveness,
|
|
266
|
+
...options.mode === void 0 ? {} : { mode: options.mode },
|
|
267
|
+
...options.latencyBudgetMs === void 0 ? {} : { latency_budget_ms: options.latencyBudgetMs },
|
|
268
|
+
...options.compactEmptyUserMessages === void 0 ? {} : { compact_empty_user_messages: options.compactEmptyUserMessages },
|
|
269
|
+
...options.compactDuplicateUserTextParts === void 0 ? {} : {
|
|
270
|
+
compact_duplicate_user_text_parts: options.compactDuplicateUserTextParts
|
|
271
|
+
}
|
|
272
|
+
}
|
|
260
273
|
}),
|
|
261
274
|
signal: options.signal
|
|
262
275
|
}
|
|
@@ -752,10 +765,369 @@ function scalarToToon(value) {
|
|
|
752
765
|
return JSON.stringify(text);
|
|
753
766
|
}
|
|
754
767
|
|
|
768
|
+
// src/resources.ts
|
|
769
|
+
var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
|
|
770
|
+
var DEFAULT_GATEWAY_BASE_URL = "https://gateway.usagetap.com";
|
|
771
|
+
function normalizedBaseUrl(value) {
|
|
772
|
+
return `${value.replace(/\/+$/, "")}/`;
|
|
773
|
+
}
|
|
774
|
+
function errorMessage(payload, status) {
|
|
775
|
+
if (payload && typeof payload === "object") {
|
|
776
|
+
const record = payload;
|
|
777
|
+
const error = record.error;
|
|
778
|
+
if (error && typeof error === "object") {
|
|
779
|
+
const message2 = error.message;
|
|
780
|
+
if (typeof message2 === "string" && message2) return message2;
|
|
781
|
+
}
|
|
782
|
+
const message = record.message;
|
|
783
|
+
if (typeof message === "string" && message) return message;
|
|
784
|
+
}
|
|
785
|
+
return `UsageTap request failed with HTTP ${status}`;
|
|
786
|
+
}
|
|
787
|
+
function errorCode(status) {
|
|
788
|
+
if (status === 401 || status === 403) return "USAGETAP_AUTH_ERROR";
|
|
789
|
+
if (status === 429) return "USAGETAP_RATE_LIMITED";
|
|
790
|
+
if (status >= 500) return "USAGETAP_SERVER_ERROR";
|
|
791
|
+
return "USAGETAP_BAD_REQUEST";
|
|
792
|
+
}
|
|
793
|
+
var ResourceTransport = class {
|
|
794
|
+
baseUrl;
|
|
795
|
+
apiKey;
|
|
796
|
+
fetchImpl;
|
|
797
|
+
defaultHeaders;
|
|
798
|
+
sdkVersion;
|
|
799
|
+
constructor(baseUrl, config) {
|
|
800
|
+
this.baseUrl = normalizedBaseUrl(baseUrl);
|
|
801
|
+
this.apiKey = config.apiKey;
|
|
802
|
+
this.fetchImpl = config.fetchImpl;
|
|
803
|
+
this.defaultHeaders = config.headers ?? {};
|
|
804
|
+
this.sdkVersion = config.sdkVersion;
|
|
805
|
+
}
|
|
806
|
+
async request(request) {
|
|
807
|
+
const body = request.body === void 0 ? void 0 : JSON.stringify(request.body);
|
|
808
|
+
const headers = {
|
|
809
|
+
...this.defaultHeaders,
|
|
810
|
+
accept: request.response === "data" ? CANONICAL_MEDIA_TYPE : request.response === "ndjson" ? "application/x-ndjson" : "application/json",
|
|
811
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
812
|
+
"x-usage-sdk": `js/${this.sdkVersion}`,
|
|
813
|
+
...body ? { "content-type": "application/json" } : {},
|
|
814
|
+
...request.options?.idempotencyKey ? { "idempotency-key": request.options.idempotencyKey } : {},
|
|
815
|
+
...request.options?.headers
|
|
816
|
+
};
|
|
817
|
+
let response;
|
|
818
|
+
try {
|
|
819
|
+
response = await this.fetchImpl(
|
|
820
|
+
new URL(request.path.replace(/^\/+/, ""), this.baseUrl),
|
|
821
|
+
{
|
|
822
|
+
method: request.method,
|
|
823
|
+
headers,
|
|
824
|
+
body,
|
|
825
|
+
signal: request.options?.signal
|
|
826
|
+
}
|
|
827
|
+
);
|
|
828
|
+
} catch (error) {
|
|
829
|
+
throw new UsageTapError(
|
|
830
|
+
"USAGETAP_NETWORK_ERROR",
|
|
831
|
+
"Failed to reach UsageTap",
|
|
832
|
+
{ retryable: true, cause: error }
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
const requestId = response.headers.get("x-request-id") ?? response.headers.get("x-usage-correlation-id") ?? void 0;
|
|
836
|
+
const text = await response.text();
|
|
837
|
+
let payload;
|
|
838
|
+
if (text && request.response !== "ndjson") {
|
|
839
|
+
try {
|
|
840
|
+
payload = JSON.parse(text);
|
|
841
|
+
} catch (error) {
|
|
842
|
+
throw new UsageTapError(
|
|
843
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
844
|
+
"UsageTap returned invalid JSON",
|
|
845
|
+
{ status: response.status, correlationId: requestId, cause: error }
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (!response.ok) {
|
|
850
|
+
throw new UsageTapError(
|
|
851
|
+
errorCode(response.status),
|
|
852
|
+
errorMessage(payload, response.status),
|
|
853
|
+
{
|
|
854
|
+
status: response.status,
|
|
855
|
+
retryable: response.status === 429 || response.status >= 500,
|
|
856
|
+
correlationId: requestId,
|
|
857
|
+
details: payload && typeof payload === "object" ? payload : void 0
|
|
858
|
+
}
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
if (request.response === "ndjson") {
|
|
862
|
+
if (!text.trim()) return [];
|
|
863
|
+
try {
|
|
864
|
+
return text.trim().split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
865
|
+
} catch (error) {
|
|
866
|
+
throw new UsageTapError(
|
|
867
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
868
|
+
"UsageTap returned invalid NDJSON",
|
|
869
|
+
{ status: response.status, correlationId: requestId, cause: error }
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (request.response === "data") {
|
|
874
|
+
if (!payload || typeof payload !== "object" || !("data" in payload)) {
|
|
875
|
+
throw new UsageTapError(
|
|
876
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
877
|
+
"UsageTap response missing data",
|
|
878
|
+
{ status: response.status, correlationId: requestId }
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
return payload.data;
|
|
882
|
+
}
|
|
883
|
+
if (payload === void 0) {
|
|
884
|
+
throw new UsageTapError(
|
|
885
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
886
|
+
"UsageTap response was empty",
|
|
887
|
+
{ status: response.status, correlationId: requestId }
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
return payload;
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
function resourceId(value, keys, label) {
|
|
894
|
+
const id = typeof value === "string" ? value : keys.map((key) => value[key]).find((candidate) => Boolean(candidate?.trim()));
|
|
895
|
+
if (!id?.trim()) {
|
|
896
|
+
throw new UsageTapError(
|
|
897
|
+
"USAGETAP_BAD_REQUEST",
|
|
898
|
+
`${label} requires a non-empty ID`
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
return id.trim();
|
|
902
|
+
}
|
|
903
|
+
function terminalSummary(status) {
|
|
904
|
+
return status === "COMPLETE" || status === "FAILED";
|
|
905
|
+
}
|
|
906
|
+
function terminalGatewayBatch(status) {
|
|
907
|
+
return ["completed", "failed", "expired", "cancelled"].includes(status);
|
|
908
|
+
}
|
|
909
|
+
function validateWaitOptions(options) {
|
|
910
|
+
const pollIntervalMs = options.pollIntervalMs ?? 1500;
|
|
911
|
+
const timeoutMs = options.timeoutMs ?? 3 * 6e4;
|
|
912
|
+
if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 0) {
|
|
913
|
+
throw new UsageTapError(
|
|
914
|
+
"USAGETAP_BAD_REQUEST",
|
|
915
|
+
"pollIntervalMs must be a non-negative number"
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
|
|
919
|
+
throw new UsageTapError(
|
|
920
|
+
"USAGETAP_BAD_REQUEST",
|
|
921
|
+
"timeoutMs must be a positive number"
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
return { pollIntervalMs, timeoutMs };
|
|
925
|
+
}
|
|
926
|
+
var SummarizationResource = class {
|
|
927
|
+
summaries;
|
|
928
|
+
batches;
|
|
929
|
+
profiles;
|
|
930
|
+
measurements;
|
|
931
|
+
transport;
|
|
932
|
+
constructor(config) {
|
|
933
|
+
this.transport = new ResourceTransport(config.apiBaseUrl, config);
|
|
934
|
+
this.summaries = {
|
|
935
|
+
create: (params, options) => this.transport.request({
|
|
936
|
+
method: "POST",
|
|
937
|
+
path: "/v1/compression/summaries",
|
|
938
|
+
body: params,
|
|
939
|
+
options,
|
|
940
|
+
response: "data"
|
|
941
|
+
}),
|
|
942
|
+
retrieve: (jobId, options) => this.transport.request({
|
|
943
|
+
method: "GET",
|
|
944
|
+
path: `/v1/compression/jobs/${encodeURIComponent(
|
|
945
|
+
resourceId(jobId, ["jobId"], "summaries.retrieve")
|
|
946
|
+
)}`,
|
|
947
|
+
options,
|
|
948
|
+
response: "data"
|
|
949
|
+
}),
|
|
950
|
+
wait: (job, options) => this.waitForSummary(job, options)
|
|
951
|
+
};
|
|
952
|
+
this.batches = {
|
|
953
|
+
create: (params, options) => this.transport.request({
|
|
954
|
+
method: "POST",
|
|
955
|
+
path: "/v1/compression/batches",
|
|
956
|
+
body: params,
|
|
957
|
+
options,
|
|
958
|
+
response: "data"
|
|
959
|
+
}),
|
|
960
|
+
retrieve: (batchId, options) => this.transport.request({
|
|
961
|
+
method: "GET",
|
|
962
|
+
path: `/v1/compression/batches/${encodeURIComponent(
|
|
963
|
+
resourceId(batchId, ["batchId"], "summarization.batches.retrieve")
|
|
964
|
+
)}`,
|
|
965
|
+
options,
|
|
966
|
+
response: "data"
|
|
967
|
+
}),
|
|
968
|
+
wait: (batch, options) => this.waitForBatch(batch, options)
|
|
969
|
+
};
|
|
970
|
+
this.profiles = {
|
|
971
|
+
retrieve: (profile, options) => this.transport.request({
|
|
972
|
+
method: "GET",
|
|
973
|
+
path: `/v1/compression/profiles/${encodeURIComponent(
|
|
974
|
+
resourceId(profile, [], "summarization.profiles.retrieve")
|
|
975
|
+
)}`,
|
|
976
|
+
options,
|
|
977
|
+
response: "data"
|
|
978
|
+
})
|
|
979
|
+
};
|
|
980
|
+
this.measurements = {
|
|
981
|
+
create: (params, options) => this.transport.request({
|
|
982
|
+
method: "POST",
|
|
983
|
+
path: "/v1/compression/measurements",
|
|
984
|
+
body: params,
|
|
985
|
+
options,
|
|
986
|
+
response: "data"
|
|
987
|
+
})
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
async waitForSummary(value, options = {}) {
|
|
991
|
+
const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
|
|
992
|
+
const deadline = Date.now() + timeoutMs;
|
|
993
|
+
let job = typeof value === "string" ? await this.summaries.retrieve(value, options) : value;
|
|
994
|
+
while (!terminalSummary(job.status)) {
|
|
995
|
+
if (Date.now() >= deadline) {
|
|
996
|
+
throw new UsageTapError(
|
|
997
|
+
"USAGETAP_RETRY_EXHAUSTED",
|
|
998
|
+
`Summarization job ${job.jobId} did not finish before timeout`,
|
|
999
|
+
{ retryable: true }
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
await sleep(pollIntervalMs, options.signal);
|
|
1003
|
+
job = await this.summaries.retrieve(job.jobId, options);
|
|
1004
|
+
}
|
|
1005
|
+
return job;
|
|
1006
|
+
}
|
|
1007
|
+
async waitForBatch(value, options = {}) {
|
|
1008
|
+
const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
|
|
1009
|
+
const deadline = Date.now() + timeoutMs;
|
|
1010
|
+
let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
|
|
1011
|
+
while (!terminalSummary(batch.status)) {
|
|
1012
|
+
if (Date.now() >= deadline) {
|
|
1013
|
+
throw new UsageTapError(
|
|
1014
|
+
"USAGETAP_RETRY_EXHAUSTED",
|
|
1015
|
+
`Summarization batch ${batch.batchId} did not finish before timeout`,
|
|
1016
|
+
{ retryable: true }
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
await sleep(pollIntervalMs, options.signal);
|
|
1020
|
+
batch = await this.batches.retrieve(batch.batchId, options);
|
|
1021
|
+
}
|
|
1022
|
+
return batch;
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
var GatewayResource = class {
|
|
1026
|
+
chat;
|
|
1027
|
+
/** Buffered OpenAI Responses-compatible requests. */
|
|
1028
|
+
responses;
|
|
1029
|
+
models;
|
|
1030
|
+
batches;
|
|
1031
|
+
transport;
|
|
1032
|
+
idempotencyGenerator;
|
|
1033
|
+
constructor(config) {
|
|
1034
|
+
this.transport = new ResourceTransport(
|
|
1035
|
+
config.gatewayBaseUrl ?? DEFAULT_GATEWAY_BASE_URL,
|
|
1036
|
+
config
|
|
1037
|
+
);
|
|
1038
|
+
this.idempotencyGenerator = config.idempotencyGenerator ?? createIdempotencyKey;
|
|
1039
|
+
this.chat = {
|
|
1040
|
+
completions: {
|
|
1041
|
+
create: (params, options) => this.transport.request({
|
|
1042
|
+
method: "POST",
|
|
1043
|
+
path: "/v1/chat/completions",
|
|
1044
|
+
body: params,
|
|
1045
|
+
options,
|
|
1046
|
+
response: "json"
|
|
1047
|
+
})
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
this.responses = {
|
|
1051
|
+
create: (params, options) => this.transport.request({
|
|
1052
|
+
method: "POST",
|
|
1053
|
+
path: "/v1/responses",
|
|
1054
|
+
body: params,
|
|
1055
|
+
options,
|
|
1056
|
+
response: "json"
|
|
1057
|
+
})
|
|
1058
|
+
};
|
|
1059
|
+
this.models = {
|
|
1060
|
+
list: (options) => this.transport.request({
|
|
1061
|
+
method: "GET",
|
|
1062
|
+
path: "/v1/models",
|
|
1063
|
+
options,
|
|
1064
|
+
response: "json"
|
|
1065
|
+
})
|
|
1066
|
+
};
|
|
1067
|
+
this.batches = {
|
|
1068
|
+
create: (params, options = {}) => this.transport.request({
|
|
1069
|
+
method: "POST",
|
|
1070
|
+
path: "/v1/batches",
|
|
1071
|
+
body: params,
|
|
1072
|
+
options: {
|
|
1073
|
+
...options,
|
|
1074
|
+
idempotencyKey: options.idempotencyKey ?? this.idempotencyGenerator()
|
|
1075
|
+
},
|
|
1076
|
+
response: "json"
|
|
1077
|
+
}),
|
|
1078
|
+
retrieve: (batchId, options) => this.transport.request({
|
|
1079
|
+
method: "GET",
|
|
1080
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1081
|
+
resourceId(batchId, ["id"], "gateway.batches.retrieve")
|
|
1082
|
+
)}`,
|
|
1083
|
+
options,
|
|
1084
|
+
response: "json"
|
|
1085
|
+
}),
|
|
1086
|
+
wait: (batch, options) => this.waitForBatch(batch, options),
|
|
1087
|
+
cancel: (batchId, options) => this.transport.request({
|
|
1088
|
+
method: "POST",
|
|
1089
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1090
|
+
resourceId(batchId, ["id"], "gateway.batches.cancel")
|
|
1091
|
+
)}/cancel`,
|
|
1092
|
+
options,
|
|
1093
|
+
response: "json"
|
|
1094
|
+
}),
|
|
1095
|
+
results: (batchId, options) => this.transport.request({
|
|
1096
|
+
method: "GET",
|
|
1097
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1098
|
+
resourceId(batchId, ["id"], "gateway.batches.results")
|
|
1099
|
+
)}/results`,
|
|
1100
|
+
options,
|
|
1101
|
+
response: "ndjson"
|
|
1102
|
+
})
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
async waitForBatch(value, options = {}) {
|
|
1106
|
+
const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
|
|
1107
|
+
const deadline = Date.now() + timeoutMs;
|
|
1108
|
+
let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
|
|
1109
|
+
while (!terminalGatewayBatch(batch.status)) {
|
|
1110
|
+
if (Date.now() >= deadline) {
|
|
1111
|
+
throw new UsageTapError(
|
|
1112
|
+
"USAGETAP_RETRY_EXHAUSTED",
|
|
1113
|
+
`Gateway batch ${batch.id} did not finish before timeout`,
|
|
1114
|
+
{ retryable: true }
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
await sleep(pollIntervalMs, options.signal);
|
|
1118
|
+
batch = await this.batches.retrieve(batch.id, options);
|
|
1119
|
+
}
|
|
1120
|
+
return batch;
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
|
|
755
1124
|
// src/client.ts
|
|
756
1125
|
var CALL_BEGIN_PATH = "call_begin";
|
|
757
1126
|
var CALL_END_PATH = "call_end";
|
|
758
1127
|
var COMPRESS_PROMPT_PATH = "compress_prompt";
|
|
1128
|
+
var SAMPLES_PATH = "samples";
|
|
1129
|
+
var SAMPLING_SETTINGS_PATH = "sampling/settings";
|
|
1130
|
+
var SAMPLING_DECIDE_PATH = "sampling/decide";
|
|
759
1131
|
var CHECK_USAGE_PATH = "customers/{customerId}/usage";
|
|
760
1132
|
var CREATE_CUSTOMER_PATH = "customers";
|
|
761
1133
|
var CHANGE_PLAN_PATH = "customers/{customerId}/change_plan";
|
|
@@ -766,11 +1138,16 @@ var CORRELATION_HEADER = "x-usage-correlation-id";
|
|
|
766
1138
|
var IDEMPOTENCY_HEADER = "idempotency-key";
|
|
767
1139
|
var SDK_HEADER = "x-usage-sdk";
|
|
768
1140
|
var USER_AGENT = "UsageTapClient";
|
|
769
|
-
var
|
|
1141
|
+
var CANONICAL_MEDIA_TYPE2 = "application/vnd.usagetap.v1+json";
|
|
770
1142
|
var DEFAULT_BASE_URL = "https://api.usagetap.com";
|
|
771
|
-
var
|
|
1143
|
+
var DEFAULT_RUN_INACTIVITY_MS = 60 * 60 * 1e3;
|
|
1144
|
+
var SDK_VERSION = "1.7.0" ;
|
|
772
1145
|
var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
773
1146
|
var UsageTapClient = class {
|
|
1147
|
+
/** OpenAI-compatible chat, model, and native batch operations. */
|
|
1148
|
+
gateway;
|
|
1149
|
+
/** Published-profile context summarization operations. */
|
|
1150
|
+
summarization;
|
|
774
1151
|
apiKey;
|
|
775
1152
|
baseUrl;
|
|
776
1153
|
fetchImpl;
|
|
@@ -795,6 +1172,11 @@ var UsageTapClient = class {
|
|
|
795
1172
|
usageTapCompressionMessagesEndpoint;
|
|
796
1173
|
usageTapCompressionModel;
|
|
797
1174
|
usageTapCompressionAggressiveness;
|
|
1175
|
+
sampling;
|
|
1176
|
+
samplingSettingsCacheMs;
|
|
1177
|
+
circuitBreaker;
|
|
1178
|
+
circuitBreakerRuns = /* @__PURE__ */ new Map();
|
|
1179
|
+
samplingSettingsCache;
|
|
798
1180
|
constructor(options = {}) {
|
|
799
1181
|
const apiKey = options.apiKey?.trim() || readEnvironmentVariable("USAGETAP_API_KEY");
|
|
800
1182
|
const baseUrl = options.baseUrl?.trim() || readEnvironmentVariable("USAGETAP_BASE_URL") || DEFAULT_BASE_URL;
|
|
@@ -817,10 +1199,21 @@ var UsageTapClient = class {
|
|
|
817
1199
|
"A global fetch implementation was not found. Pass fetchImpl in UsageTapClientOptions."
|
|
818
1200
|
);
|
|
819
1201
|
}
|
|
820
|
-
const
|
|
821
|
-
this.baseUrl = new URL(
|
|
1202
|
+
const normalizedBaseUrl2 = normalizeBaseUrl(baseUrl);
|
|
1203
|
+
this.baseUrl = new URL(normalizedBaseUrl2);
|
|
822
1204
|
this.apiKey = apiKey;
|
|
823
1205
|
this.fetchImpl = wrapFetchImplementation(fetchCandidate, !options.fetchImpl);
|
|
1206
|
+
const resourceConfig = {
|
|
1207
|
+
apiKey,
|
|
1208
|
+
apiBaseUrl: normalizedBaseUrl2,
|
|
1209
|
+
gatewayBaseUrl: options.gatewayBaseUrl?.trim() || readEnvironmentVariable("USAGETAP_GATEWAY_URL"),
|
|
1210
|
+
fetchImpl: this.fetchImpl,
|
|
1211
|
+
headers: options.headers,
|
|
1212
|
+
sdkVersion: SDK_VERSION,
|
|
1213
|
+
idempotencyGenerator: options.idempotencyGenerator
|
|
1214
|
+
};
|
|
1215
|
+
this.gateway = new GatewayResource(resourceConfig);
|
|
1216
|
+
this.summarization = new SummarizationResource(resourceConfig);
|
|
824
1217
|
this.defaultFeature = options.defaultFeature;
|
|
825
1218
|
this.defaultTags = options.defaultTags?.length ? dedupeStrings(options.defaultTags) : void 0;
|
|
826
1219
|
this.defaultHeaders = options.headers ? normalizeHeaderDictionary(options.headers) : {};
|
|
@@ -842,11 +1235,122 @@ var UsageTapClient = class {
|
|
|
842
1235
|
this.usageTapCompressionMessagesEndpoint = options.usageTapCompressionMessagesEndpoint;
|
|
843
1236
|
this.usageTapCompressionModel = options.usageTapCompressionModel;
|
|
844
1237
|
this.usageTapCompressionAggressiveness = options.usageTapCompressionAggressiveness;
|
|
1238
|
+
this.sampling = options.sampling;
|
|
1239
|
+
this.samplingSettingsCacheMs = Number.isFinite(options.samplingSettingsCacheMs) ? Math.max(0, Number(options.samplingSettingsCacheMs)) : 5 * 60 * 1e3;
|
|
1240
|
+
if (options.circuitBreaker) {
|
|
1241
|
+
const maxCallsPerRun = options.circuitBreaker.maxCallsPerRun;
|
|
1242
|
+
if (!Number.isInteger(maxCallsPerRun) || maxCallsPerRun < 1) {
|
|
1243
|
+
throw new UsageTapError(
|
|
1244
|
+
"USAGETAP_BAD_REQUEST",
|
|
1245
|
+
"circuitBreaker.maxCallsPerRun must be a positive integer"
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
const runInactivityMs = options.circuitBreaker.runInactivityMs ?? DEFAULT_RUN_INACTIVITY_MS;
|
|
1249
|
+
if (!Number.isFinite(runInactivityMs) || runInactivityMs < 1) {
|
|
1250
|
+
throw new UsageTapError(
|
|
1251
|
+
"USAGETAP_BAD_REQUEST",
|
|
1252
|
+
"circuitBreaker.runInactivityMs must be a positive number"
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
this.circuitBreaker = {
|
|
1256
|
+
maxCallsPerRun,
|
|
1257
|
+
runInactivityMs
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
shouldSample(request, policy = this.sampling || void 0) {
|
|
1262
|
+
if (!policy) return false;
|
|
1263
|
+
const rate = Math.min(1, Math.max(0, Number(policy.rate) || 0));
|
|
1264
|
+
if (rate <= 0) return false;
|
|
1265
|
+
const customerId = request.customerId?.trim();
|
|
1266
|
+
if (customerId && policy.customers?.exclude?.includes(customerId)) return false;
|
|
1267
|
+
const feature = request.feature?.trim();
|
|
1268
|
+
if (feature && policy.features?.exclude?.includes(feature)) return false;
|
|
1269
|
+
const included = policy.features?.include?.filter(Boolean) ?? [];
|
|
1270
|
+
if (included.length > 0 && (!feature || !included.includes(feature))) return false;
|
|
1271
|
+
const minimum = Math.max(0, Math.round(policy.minInputTokens ?? 0));
|
|
1272
|
+
if (minimum > 0 && estimatePromptTokens(request.input) < minimum) return false;
|
|
1273
|
+
return (policy.random ?? Math.random)() < rate;
|
|
1274
|
+
}
|
|
1275
|
+
async getSamplingSettings(options = {}) {
|
|
1276
|
+
const now = Date.now();
|
|
1277
|
+
if (!options.forceRefresh && this.samplingSettingsCache && this.samplingSettingsCache.expiresAtMs > now) {
|
|
1278
|
+
return {
|
|
1279
|
+
result: { status: "ACCEPTED", code: "SAMPLING_SETTINGS_CACHED" },
|
|
1280
|
+
data: this.samplingSettingsCache.settings,
|
|
1281
|
+
correlationId: options.correlationId ?? "local-cache"
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
const response = await this.requestGet(
|
|
1285
|
+
SAMPLING_SETTINGS_PATH,
|
|
1286
|
+
{
|
|
1287
|
+
signal: options.signal,
|
|
1288
|
+
headers: options.headers,
|
|
1289
|
+
retries: options.retries,
|
|
1290
|
+
correlationId: options.correlationId
|
|
1291
|
+
}
|
|
1292
|
+
);
|
|
1293
|
+
const serverCacheMs = Math.max(0, Number(response.data.cacheSeconds) || 0) * 1e3;
|
|
1294
|
+
const cacheMs = Math.min(this.samplingSettingsCacheMs, serverCacheMs);
|
|
1295
|
+
this.samplingSettingsCache = {
|
|
1296
|
+
settings: response.data,
|
|
1297
|
+
expiresAtMs: now + cacheMs
|
|
1298
|
+
};
|
|
1299
|
+
return response;
|
|
1300
|
+
}
|
|
1301
|
+
async shouldSampleAsync(request, policy) {
|
|
1302
|
+
if (policy) return this.shouldSample(request, policy);
|
|
1303
|
+
if (this.sampling === false) return false;
|
|
1304
|
+
if (this.sampling) return this.shouldSample(request, this.sampling);
|
|
1305
|
+
try {
|
|
1306
|
+
const settings = await this.getSamplingSettings();
|
|
1307
|
+
return this.shouldSample(request, settings.data);
|
|
1308
|
+
} catch {
|
|
1309
|
+
return false;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
async decideSample(request, options = {}) {
|
|
1313
|
+
const hasTokens = Number.isFinite(request.inputTokens) && Number(request.inputTokens) >= 0;
|
|
1314
|
+
const hasCharacters = Number.isFinite(request.inputCharacters) && Number(request.inputCharacters) >= 0;
|
|
1315
|
+
if (!hasTokens && !hasCharacters) {
|
|
1316
|
+
throw new UsageTapError(
|
|
1317
|
+
"USAGETAP_BAD_REQUEST",
|
|
1318
|
+
"decideSample requires inputTokens or inputCharacters"
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
return this.request(
|
|
1322
|
+
SAMPLING_DECIDE_PATH,
|
|
1323
|
+
request,
|
|
1324
|
+
options
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
async captureSample(request, options = {}) {
|
|
1328
|
+
if (!request || request.input === void 0) {
|
|
1329
|
+
throw new UsageTapError(
|
|
1330
|
+
"USAGETAP_BAD_REQUEST",
|
|
1331
|
+
"captureSample requires input"
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
if (!request.provider?.trim()) {
|
|
1335
|
+
throw new UsageTapError(
|
|
1336
|
+
"USAGETAP_BAD_REQUEST",
|
|
1337
|
+
"captureSample requires provider"
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
const sampleId = request.sampleId?.trim() || this.idempotencyGenerator();
|
|
1341
|
+
return this.request(
|
|
1342
|
+
SAMPLES_PATH,
|
|
1343
|
+
{ ...request, sampleId },
|
|
1344
|
+
{ ...options, idempotencyKey: sampleId }
|
|
1345
|
+
);
|
|
845
1346
|
}
|
|
846
1347
|
async beginCall(request, options = {}) {
|
|
847
1348
|
const idempotencyKey = request.idempotencyKey ?? request.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
|
|
1349
|
+
this.reserveRunCall(request, idempotencyKey);
|
|
1350
|
+
const apiRequest = { ...request };
|
|
1351
|
+
delete apiRequest.runId;
|
|
848
1352
|
const payload = {
|
|
849
|
-
...
|
|
1353
|
+
...apiRequest,
|
|
850
1354
|
feature: request.feature ?? this.defaultFeature,
|
|
851
1355
|
tags: this.mergeTags(request.tags)
|
|
852
1356
|
};
|
|
@@ -864,6 +1368,28 @@ var UsageTapClient = class {
|
|
|
864
1368
|
);
|
|
865
1369
|
return response;
|
|
866
1370
|
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Inspect a configured run circuit breaker without consuming another call.
|
|
1373
|
+
*/
|
|
1374
|
+
canRunContinue(request) {
|
|
1375
|
+
const identity = this.resolveRunIdentity(request);
|
|
1376
|
+
if (!identity || !this.circuitBreaker) {
|
|
1377
|
+
throw new UsageTapError(
|
|
1378
|
+
"USAGETAP_BAD_REQUEST",
|
|
1379
|
+
"canRunContinue requires circuitBreaker configuration and a non-empty runId"
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
this.expireInactiveRuns();
|
|
1383
|
+
const calls = this.circuitBreakerRuns.get(identity.key)?.calls ?? 0;
|
|
1384
|
+
return this.createCircuitBreakerDecision(identity.customerId, identity.runId, calls);
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Release local state after a workflow finishes. Returns true when state existed.
|
|
1388
|
+
*/
|
|
1389
|
+
resetRun(request) {
|
|
1390
|
+
const identity = this.resolveRunIdentity(request);
|
|
1391
|
+
return identity ? this.circuitBreakerRuns.delete(identity.key) : false;
|
|
1392
|
+
}
|
|
867
1393
|
async promptCompress(request, options = {}) {
|
|
868
1394
|
if (!request?.callId) {
|
|
869
1395
|
throw new UsageTapError(
|
|
@@ -961,6 +1487,10 @@ var UsageTapClient = class {
|
|
|
961
1487
|
usageTapCompressionMessagesEndpoint: this.usageTapCompressionMessagesEndpoint,
|
|
962
1488
|
aggressiveness: options.aggressiveness ?? this.aggressiveness,
|
|
963
1489
|
usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
|
|
1490
|
+
mode: options.mode,
|
|
1491
|
+
latencyBudgetMs: options.latencyBudgetMs,
|
|
1492
|
+
compactEmptyUserMessages: options.compactEmptyUserMessages,
|
|
1493
|
+
compactDuplicateUserTextParts: options.compactDuplicateUserTextParts,
|
|
964
1494
|
fetchImpl: this.fetchImpl,
|
|
965
1495
|
signal: options.signal,
|
|
966
1496
|
failOpen: options.failOpen
|
|
@@ -1002,14 +1532,29 @@ var UsageTapClient = class {
|
|
|
1002
1532
|
callId: request.callId,
|
|
1003
1533
|
feature: feature ?? this.defaultFeature,
|
|
1004
1534
|
tags: tags ?? this.defaultTags,
|
|
1535
|
+
providerUsed: request.providerUsed,
|
|
1005
1536
|
modelUsed: request.modelUsed,
|
|
1537
|
+
reasoningEffort: request.reasoningEffort,
|
|
1538
|
+
reasoningEffortSource: request.reasoningEffortSource,
|
|
1539
|
+
reasoningMode: request.reasoningMode,
|
|
1540
|
+
reasoningBudgetTokens: request.reasoningBudgetTokens,
|
|
1006
1541
|
metrics: {
|
|
1007
1542
|
inputTokens: request.inputTokens,
|
|
1008
1543
|
responseTokens: request.responseTokens,
|
|
1009
1544
|
cachedInputTokens: request.cachedInputTokens,
|
|
1545
|
+
cacheWriteInputTokens: request.cacheWriteInputTokens,
|
|
1546
|
+
cacheWrite5mInputTokens: request.cacheWrite5mInputTokens,
|
|
1547
|
+
cacheWrite1hInputTokens: request.cacheWrite1hInputTokens,
|
|
1010
1548
|
reasoningTokens: request.reasoningTokens,
|
|
1011
1549
|
searches: request.searches,
|
|
1012
1550
|
audioSeconds: request.audioSeconds,
|
|
1551
|
+
imageInputCount: request.imageInputCount,
|
|
1552
|
+
imageInputTokens: request.imageInputTokens,
|
|
1553
|
+
imageOutputCount: request.imageOutputCount,
|
|
1554
|
+
imageOutputTokens: request.imageOutputTokens,
|
|
1555
|
+
audioInputTokens: request.audioInputTokens,
|
|
1556
|
+
cachedAudioInputTokens: request.cachedAudioInputTokens,
|
|
1557
|
+
audioOutputTokens: request.audioOutputTokens,
|
|
1013
1558
|
costUsd: response.data.costUSD
|
|
1014
1559
|
},
|
|
1015
1560
|
correlationId: response.correlationId
|
|
@@ -1096,10 +1641,10 @@ var UsageTapClient = class {
|
|
|
1096
1641
|
"incrementCustomMeter requires meterSlot"
|
|
1097
1642
|
);
|
|
1098
1643
|
}
|
|
1099
|
-
if (!["CUSTOM1", "CUSTOM2"].includes(request.meterSlot)) {
|
|
1644
|
+
if (!["CUSTOM1", "CUSTOM2", "AGENTIC_API"].includes(request.meterSlot)) {
|
|
1100
1645
|
throw new UsageTapError(
|
|
1101
1646
|
"USAGETAP_BAD_REQUEST",
|
|
1102
|
-
"meterSlot must be CUSTOM1 or
|
|
1647
|
+
"meterSlot must be CUSTOM1, CUSTOM2 or AGENTIC_API"
|
|
1103
1648
|
);
|
|
1104
1649
|
}
|
|
1105
1650
|
if (typeof request.amount !== "number" || !Number.isFinite(request.amount) || request.amount <= 0) {
|
|
@@ -1114,6 +1659,15 @@ var UsageTapClient = class {
|
|
|
1114
1659
|
meterSlot: request.meterSlot,
|
|
1115
1660
|
amount: request.amount
|
|
1116
1661
|
};
|
|
1662
|
+
if (request.customerUserId) {
|
|
1663
|
+
payload.customerUserId = request.customerUserId;
|
|
1664
|
+
}
|
|
1665
|
+
if (request.customerUserName) {
|
|
1666
|
+
payload.customerUserName = request.customerUserName;
|
|
1667
|
+
}
|
|
1668
|
+
if (request.customerUserEmail) {
|
|
1669
|
+
payload.customerUserEmail = request.customerUserEmail;
|
|
1670
|
+
}
|
|
1117
1671
|
if (request.feature) {
|
|
1118
1672
|
payload.feature = request.feature;
|
|
1119
1673
|
}
|
|
@@ -1150,6 +1704,11 @@ var UsageTapClient = class {
|
|
|
1150
1704
|
const beginPayload = idempotencyKey ? { ...beginRequest, idempotencyKey, idempotency: idempotencyKey } : { ...beginRequest };
|
|
1151
1705
|
const beginResponse = await this.beginCall(beginPayload, options);
|
|
1152
1706
|
let usage = {};
|
|
1707
|
+
const pricingMode = beginResponse.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
|
|
1708
|
+
if (pricingMode) {
|
|
1709
|
+
usage.pricingMode = pricingMode;
|
|
1710
|
+
usage.batch = pricingMode === "batch";
|
|
1711
|
+
}
|
|
1153
1712
|
const initialStripeCustomerId = typeof beginResponse.data.stripeCustomerId === "string" ? beginResponse.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
|
|
1154
1713
|
if (initialStripeCustomerId) {
|
|
1155
1714
|
usage = { ...usage, stripeCustomerId: initialStripeCustomerId };
|
|
@@ -1158,6 +1717,28 @@ var UsageTapClient = class {
|
|
|
1158
1717
|
let handlerResult;
|
|
1159
1718
|
let handlerError;
|
|
1160
1719
|
let endCallError;
|
|
1720
|
+
let finalizationDeferred = false;
|
|
1721
|
+
let finalizationPromise;
|
|
1722
|
+
const finalize = () => {
|
|
1723
|
+
if (!finalizationPromise) {
|
|
1724
|
+
finalizationPromise = this.endCall(
|
|
1725
|
+
{
|
|
1726
|
+
callId: beginResponse.data.callId,
|
|
1727
|
+
// Pass context for metric tracking
|
|
1728
|
+
customerId: beginRequest.customerId,
|
|
1729
|
+
feature: beginRequest.feature ?? this.defaultFeature,
|
|
1730
|
+
tags: beginRequest.tags ?? this.defaultTags,
|
|
1731
|
+
...usage,
|
|
1732
|
+
error: errorPayload
|
|
1733
|
+
},
|
|
1734
|
+
{
|
|
1735
|
+
...options,
|
|
1736
|
+
correlationId: beginResponse.correlationId
|
|
1737
|
+
}
|
|
1738
|
+
).then(() => void 0);
|
|
1739
|
+
}
|
|
1740
|
+
return finalizationPromise;
|
|
1741
|
+
};
|
|
1161
1742
|
const context = {
|
|
1162
1743
|
begin: beginResponse,
|
|
1163
1744
|
setUsage: (u) => {
|
|
@@ -1165,6 +1746,10 @@ var UsageTapClient = class {
|
|
|
1165
1746
|
},
|
|
1166
1747
|
setError: (err) => {
|
|
1167
1748
|
errorPayload = err;
|
|
1749
|
+
},
|
|
1750
|
+
deferFinalization: () => {
|
|
1751
|
+
finalizationDeferred = true;
|
|
1752
|
+
return finalize;
|
|
1168
1753
|
}
|
|
1169
1754
|
};
|
|
1170
1755
|
try {
|
|
@@ -1178,24 +1763,12 @@ var UsageTapClient = class {
|
|
|
1178
1763
|
};
|
|
1179
1764
|
}
|
|
1180
1765
|
} finally {
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
feature: beginRequest.feature ?? this.defaultFeature,
|
|
1188
|
-
tags: beginRequest.tags ?? this.defaultTags,
|
|
1189
|
-
...usage,
|
|
1190
|
-
error: errorPayload
|
|
1191
|
-
},
|
|
1192
|
-
{
|
|
1193
|
-
...options,
|
|
1194
|
-
correlationId: beginResponse.correlationId
|
|
1195
|
-
}
|
|
1196
|
-
);
|
|
1197
|
-
} catch (error) {
|
|
1198
|
-
endCallError = error;
|
|
1766
|
+
if (handlerError || !finalizationDeferred) {
|
|
1767
|
+
try {
|
|
1768
|
+
await finalize();
|
|
1769
|
+
} catch (error) {
|
|
1770
|
+
endCallError = error;
|
|
1771
|
+
}
|
|
1199
1772
|
}
|
|
1200
1773
|
}
|
|
1201
1774
|
if (handlerError) {
|
|
@@ -1217,17 +1790,86 @@ var UsageTapClient = class {
|
|
|
1217
1790
|
toPromptCompressionTelemetry(result) {
|
|
1218
1791
|
return {
|
|
1219
1792
|
provider: result.provider,
|
|
1220
|
-
originalCharacters: result.originalCharacters,
|
|
1221
|
-
compressedCharacters: result.compressedCharacters,
|
|
1222
|
-
savedCharacters: result.savedCharacters,
|
|
1223
1793
|
originalTokens: result.originalTokens,
|
|
1224
1794
|
compressedTokens: result.compressedTokens,
|
|
1225
1795
|
savedTokens: result.savedTokens,
|
|
1226
1796
|
tokenSavingsRatio: result.tokenSavingsRatio,
|
|
1227
|
-
savingsRatio: result.savingsRatio,
|
|
1228
1797
|
techniques: result.techniques
|
|
1229
1798
|
};
|
|
1230
1799
|
}
|
|
1800
|
+
reserveRunCall(request, idempotencyKey) {
|
|
1801
|
+
const identity = this.resolveRunIdentity(request);
|
|
1802
|
+
if (!identity || !this.circuitBreaker) return;
|
|
1803
|
+
this.expireInactiveRuns();
|
|
1804
|
+
const now = Date.now();
|
|
1805
|
+
const state = this.circuitBreakerRuns.get(identity.key) ?? {
|
|
1806
|
+
calls: 0,
|
|
1807
|
+
reservationKeys: /* @__PURE__ */ new Set(),
|
|
1808
|
+
lastSeenAtMs: now
|
|
1809
|
+
};
|
|
1810
|
+
const reservationKey = idempotencyKey ?? this.idempotencyGenerator();
|
|
1811
|
+
state.lastSeenAtMs = now;
|
|
1812
|
+
if (state.reservationKeys.has(reservationKey)) {
|
|
1813
|
+
this.circuitBreakerRuns.set(identity.key, state);
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
const decision = this.createCircuitBreakerDecision(
|
|
1817
|
+
identity.customerId,
|
|
1818
|
+
identity.runId,
|
|
1819
|
+
state.calls
|
|
1820
|
+
);
|
|
1821
|
+
if (!decision.allowed) {
|
|
1822
|
+
throw new UsageTapError(
|
|
1823
|
+
"USAGETAP_CIRCUIT_OPEN",
|
|
1824
|
+
`Run ${identity.runId} reached its ${decision.limit}-call circuit-breaker limit`,
|
|
1825
|
+
{
|
|
1826
|
+
details: {
|
|
1827
|
+
reason: decision.reason,
|
|
1828
|
+
customerId: identity.customerId,
|
|
1829
|
+
runId: identity.runId,
|
|
1830
|
+
calls: decision.calls,
|
|
1831
|
+
limit: decision.limit,
|
|
1832
|
+
remaining: decision.remaining
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
state.calls += 1;
|
|
1838
|
+
state.reservationKeys.add(reservationKey);
|
|
1839
|
+
this.circuitBreakerRuns.set(identity.key, state);
|
|
1840
|
+
}
|
|
1841
|
+
resolveRunIdentity(request) {
|
|
1842
|
+
const customerId = request.customerId?.trim();
|
|
1843
|
+
const runId = request.runId?.trim();
|
|
1844
|
+
if (!customerId || !runId) return void 0;
|
|
1845
|
+
return {
|
|
1846
|
+
key: `${customerId}\0${runId}`,
|
|
1847
|
+
customerId,
|
|
1848
|
+
runId
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
createCircuitBreakerDecision(customerId, runId, calls) {
|
|
1852
|
+
const limit = this.circuitBreaker?.maxCallsPerRun ?? 0;
|
|
1853
|
+
const allowed = calls < limit;
|
|
1854
|
+
return {
|
|
1855
|
+
allowed,
|
|
1856
|
+
...allowed ? {} : { reason: "max_calls_per_run" },
|
|
1857
|
+
customerId,
|
|
1858
|
+
runId,
|
|
1859
|
+
calls,
|
|
1860
|
+
limit,
|
|
1861
|
+
remaining: Math.max(0, limit - calls)
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
expireInactiveRuns() {
|
|
1865
|
+
if (!this.circuitBreaker || this.circuitBreakerRuns.size === 0) return;
|
|
1866
|
+
const expiredBefore = Date.now() - this.circuitBreaker.runInactivityMs;
|
|
1867
|
+
for (const [key, state] of this.circuitBreakerRuns) {
|
|
1868
|
+
if (state.lastSeenAtMs < expiredBefore) {
|
|
1869
|
+
this.circuitBreakerRuns.delete(key);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1231
1873
|
async request(path, payload, options) {
|
|
1232
1874
|
const url = new URL(path, this.baseUrl).toString();
|
|
1233
1875
|
const body = payload !== void 0 ? JSON.stringify(payload) : void 0;
|
|
@@ -1415,7 +2057,7 @@ var UsageTapClient = class {
|
|
|
1415
2057
|
...this.defaultHeaders,
|
|
1416
2058
|
[SDK_HEADER]: `js/${SDK_VERSION}`,
|
|
1417
2059
|
"content-type": "application/json",
|
|
1418
|
-
accept:
|
|
2060
|
+
accept: CANONICAL_MEDIA_TYPE2
|
|
1419
2061
|
};
|
|
1420
2062
|
if (!HAS_WINDOW) {
|
|
1421
2063
|
headers["user-agent"] = `${USER_AGENT}/${SDK_VERSION}`;
|
|
@@ -1468,7 +2110,8 @@ var UsageTapClient = class {
|
|
|
1468
2110
|
}
|
|
1469
2111
|
toHttpError(status, payload, correlationId) {
|
|
1470
2112
|
const code = mapStatusToErrorCode(status);
|
|
1471
|
-
const
|
|
2113
|
+
const apiCode = payload?.error?.code ?? payload?.result?.code ?? "UNKNOWN";
|
|
2114
|
+
const retryable = isRetryableStatus(status) || isRetryableApiCode(apiCode);
|
|
1472
2115
|
const message = payload?.error?.message ?? payload?.result?.message ?? `UsageTap responded with HTTP ${status}`;
|
|
1473
2116
|
return new UsageTapError(code, message, {
|
|
1474
2117
|
status,
|
|
@@ -1501,7 +2144,7 @@ function isRetryableStatus(status) {
|
|
|
1501
2144
|
}
|
|
1502
2145
|
function isRetryableApiCode(code) {
|
|
1503
2146
|
const normalized = code.toUpperCase();
|
|
1504
|
-
return normalized.includes("TRANSIENT") || normalized.includes("RETRY") || normalized.includes("TIMEOUT") || normalized.includes("THROTTLE") || normalized.includes("RATE_LIMIT");
|
|
2147
|
+
return normalized === "PAYG_CONFLICT" || normalized.includes("TRANSIENT") || normalized.includes("RETRY") || normalized.includes("TIMEOUT") || normalized.includes("THROTTLE") || normalized.includes("RATE_LIMIT");
|
|
1505
2148
|
}
|
|
1506
2149
|
function mapApiCodeToError(code) {
|
|
1507
2150
|
const normalized = code.toUpperCase();
|
|
@@ -1616,17 +2259,18 @@ var OpenAIPromptCompressionStats = class {
|
|
|
1616
2259
|
}
|
|
1617
2260
|
};
|
|
1618
2261
|
function createOpenAIAdapter(init) {
|
|
1619
|
-
const { client, usageTap } = init;
|
|
2262
|
+
const { client, usageTap, provider = "openai" } = init;
|
|
1620
2263
|
return {
|
|
1621
2264
|
async invoke(params) {
|
|
1622
2265
|
const result = await usageTap.withUsage(
|
|
1623
2266
|
params.begin,
|
|
1624
2267
|
async (ctx) => {
|
|
2268
|
+
ctx.setUsage({ providerUsed: provider });
|
|
1625
2269
|
const response = await params.call(client, {
|
|
1626
2270
|
hints: ctx.begin.data.vendorHints,
|
|
1627
2271
|
begin: ctx.begin
|
|
1628
2272
|
});
|
|
1629
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx);
|
|
2273
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx, provider);
|
|
1630
2274
|
return {
|
|
1631
2275
|
data: response,
|
|
1632
2276
|
begin: ctx.begin
|
|
@@ -1640,24 +2284,29 @@ function createOpenAIAdapter(init) {
|
|
|
1640
2284
|
const result = await usageTap.withUsage(
|
|
1641
2285
|
params.begin,
|
|
1642
2286
|
async (ctx) => {
|
|
2287
|
+
const settle = deferUsageFinalization(ctx);
|
|
2288
|
+
ctx.setUsage({ providerUsed: provider });
|
|
1643
2289
|
const { stream, onComplete } = await params.call(client, {
|
|
1644
2290
|
hints: ctx.begin.data.vendorHints,
|
|
1645
2291
|
begin: ctx.begin
|
|
1646
2292
|
});
|
|
1647
|
-
const wrapped = wrapStreamForUsageTap(stream, async () => {
|
|
1648
|
-
if (!onComplete) return;
|
|
2293
|
+
const wrapped = wrapStreamForUsageTap(stream, async (termination) => {
|
|
1649
2294
|
try {
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
2295
|
+
if (termination === "complete" && onComplete) {
|
|
2296
|
+
const maybeUsage = await onComplete();
|
|
2297
|
+
if (maybeUsage) {
|
|
2298
|
+
ctx.setUsage(maybeUsage);
|
|
2299
|
+
}
|
|
1653
2300
|
}
|
|
1654
2301
|
} catch (error) {
|
|
1655
2302
|
ctx.setError({
|
|
1656
2303
|
code: "USAGE_FINALIZE_ERROR",
|
|
1657
2304
|
message: error instanceof Error ? error.message : String(error)
|
|
1658
2305
|
});
|
|
2306
|
+
await settle();
|
|
1659
2307
|
throw error;
|
|
1660
2308
|
}
|
|
2309
|
+
await settle();
|
|
1661
2310
|
}, ctx);
|
|
1662
2311
|
const finalize = async () => {
|
|
1663
2312
|
await wrapped.__usageTapFinalize?.();
|
|
@@ -1765,6 +2414,181 @@ async function pipeToResponse(stream, res, options = {}) {
|
|
|
1765
2414
|
}
|
|
1766
2415
|
}
|
|
1767
2416
|
var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
|
|
2417
|
+
function withSampling(client, options = {}) {
|
|
2418
|
+
if (!client || !options) {
|
|
2419
|
+
throw new UsageTapError(
|
|
2420
|
+
"USAGETAP_BAD_REQUEST",
|
|
2421
|
+
"withSampling requires an OpenAI-compatible client and sampling options"
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
const { apiKey, usageTapClient, provider = "openai", ...policy } = options;
|
|
2425
|
+
const localPolicy = typeof policy.rate === "number" ? policy : void 0;
|
|
2426
|
+
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey, sampling: localPolicy });
|
|
2427
|
+
const wrapCreate = (create) => async (params, requestOptions) => {
|
|
2428
|
+
const { usageTap: callContextRaw, ...providerOptions } = requestOptions ?? {};
|
|
2429
|
+
const callContext = isObjectRecord(callContextRaw) ? callContextRaw : {};
|
|
2430
|
+
const streaming = params.stream === true;
|
|
2431
|
+
const decision = streaming ? Promise.resolve(false) : usageTap.shouldSampleAsync({
|
|
2432
|
+
customerId: readString(callContext.customerId),
|
|
2433
|
+
feature: readString(callContext.feature),
|
|
2434
|
+
input: params
|
|
2435
|
+
}, localPolicy);
|
|
2436
|
+
const startedAt = Date.now();
|
|
2437
|
+
try {
|
|
2438
|
+
const response = await create(
|
|
2439
|
+
params,
|
|
2440
|
+
Object.keys(providerOptions).length ? providerOptions : void 0
|
|
2441
|
+
);
|
|
2442
|
+
const selected = await decision;
|
|
2443
|
+
if (selected) {
|
|
2444
|
+
const record = isObjectRecord(response) ? response : {};
|
|
2445
|
+
await usageTap.captureSample({
|
|
2446
|
+
customerId: readString(callContext.customerId),
|
|
2447
|
+
feature: readString(callContext.feature),
|
|
2448
|
+
environment: readString(callContext.environment),
|
|
2449
|
+
tags: readStringArray(callContext.tags),
|
|
2450
|
+
provider,
|
|
2451
|
+
model: readString(record.model) ?? readString(params.model),
|
|
2452
|
+
input: params,
|
|
2453
|
+
output: response,
|
|
2454
|
+
usage: record.usage,
|
|
2455
|
+
latencyMs: Date.now() - startedAt
|
|
2456
|
+
}).catch(() => void 0);
|
|
2457
|
+
}
|
|
2458
|
+
return response;
|
|
2459
|
+
} catch (error) {
|
|
2460
|
+
const selected = await decision;
|
|
2461
|
+
if (selected) {
|
|
2462
|
+
await usageTap.captureSample({
|
|
2463
|
+
customerId: readString(callContext.customerId),
|
|
2464
|
+
feature: readString(callContext.feature),
|
|
2465
|
+
environment: readString(callContext.environment),
|
|
2466
|
+
tags: readStringArray(callContext.tags),
|
|
2467
|
+
provider,
|
|
2468
|
+
model: readString(params.model),
|
|
2469
|
+
input: params,
|
|
2470
|
+
latencyMs: Date.now() - startedAt,
|
|
2471
|
+
error: serializeSamplingError(error)
|
|
2472
|
+
}).catch(() => void 0);
|
|
2473
|
+
}
|
|
2474
|
+
throw error;
|
|
2475
|
+
}
|
|
2476
|
+
};
|
|
2477
|
+
const chat = client.chat?.completions ? new Proxy(client.chat, {
|
|
2478
|
+
get(target, prop, receiver) {
|
|
2479
|
+
if (prop !== "completions") return safeReflectGet(target, prop, receiver);
|
|
2480
|
+
const completions = target.completions;
|
|
2481
|
+
return new Proxy(completions, {
|
|
2482
|
+
get(completionTarget, completionProp, completionReceiver) {
|
|
2483
|
+
if (completionProp === "create") {
|
|
2484
|
+
return wrapCreate(
|
|
2485
|
+
completionTarget.create.bind(completionTarget)
|
|
2486
|
+
);
|
|
2487
|
+
}
|
|
2488
|
+
return safeReflectGet(
|
|
2489
|
+
completionTarget,
|
|
2490
|
+
completionProp,
|
|
2491
|
+
completionReceiver
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
}) : void 0;
|
|
2497
|
+
const responses = typeof client.responses !== "undefined" && client.responses ? new Proxy(client.responses, {
|
|
2498
|
+
get(target, prop, receiver) {
|
|
2499
|
+
if (prop === "create") {
|
|
2500
|
+
const create = Reflect.get(target, prop, receiver);
|
|
2501
|
+
return wrapCreate(create.bind(target));
|
|
2502
|
+
}
|
|
2503
|
+
return safeReflectGet(target, prop, receiver);
|
|
2504
|
+
}
|
|
2505
|
+
}) : void 0;
|
|
2506
|
+
return new Proxy(client, {
|
|
2507
|
+
get(target, prop, receiver) {
|
|
2508
|
+
if (prop === "chat" && chat) return chat;
|
|
2509
|
+
if (prop === "responses" && responses) return responses;
|
|
2510
|
+
if (prop === "unwrap") return () => target;
|
|
2511
|
+
return safeReflectGet(target, prop, receiver);
|
|
2512
|
+
}
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
function safeReflectGet(target, prop, receiver) {
|
|
2516
|
+
return Reflect.get(target, prop, receiver);
|
|
2517
|
+
}
|
|
2518
|
+
function readStringArray(value) {
|
|
2519
|
+
if (!Array.isArray(value)) return void 0;
|
|
2520
|
+
const strings = value.filter((item) => typeof item === "string");
|
|
2521
|
+
return strings.length ? strings : void 0;
|
|
2522
|
+
}
|
|
2523
|
+
function readString(value) {
|
|
2524
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2525
|
+
}
|
|
2526
|
+
function serializeSamplingError(error) {
|
|
2527
|
+
if (error instanceof Error) {
|
|
2528
|
+
return { name: error.name, message: error.message };
|
|
2529
|
+
}
|
|
2530
|
+
return { message: String(error) };
|
|
2531
|
+
}
|
|
2532
|
+
function normalizeMeteredOpenAISampling(options) {
|
|
2533
|
+
if (!options) return void 0;
|
|
2534
|
+
if (options === true) return { provider: "openai" };
|
|
2535
|
+
const { provider = "openai", ...policyFields } = options;
|
|
2536
|
+
return {
|
|
2537
|
+
provider,
|
|
2538
|
+
policy: typeof policyFields.rate === "number" ? policyFields : void 0
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
function startMeteredOpenAISampleDecision({
|
|
2542
|
+
usageTap,
|
|
2543
|
+
sampling,
|
|
2544
|
+
beginRequest,
|
|
2545
|
+
input
|
|
2546
|
+
}) {
|
|
2547
|
+
if (!sampling) return Promise.resolve(false);
|
|
2548
|
+
return usageTap.shouldSampleAsync(
|
|
2549
|
+
{
|
|
2550
|
+
customerId: beginRequest.customerId,
|
|
2551
|
+
feature: beginRequest.feature,
|
|
2552
|
+
input
|
|
2553
|
+
},
|
|
2554
|
+
sampling.policy
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
async function captureMeteredOpenAISample({
|
|
2558
|
+
usageTap,
|
|
2559
|
+
sampling,
|
|
2560
|
+
decision,
|
|
2561
|
+
ctx,
|
|
2562
|
+
beginRequest,
|
|
2563
|
+
input,
|
|
2564
|
+
response,
|
|
2565
|
+
error,
|
|
2566
|
+
startedAt
|
|
2567
|
+
}) {
|
|
2568
|
+
if (!sampling) return;
|
|
2569
|
+
let selected = false;
|
|
2570
|
+
try {
|
|
2571
|
+
selected = await decision;
|
|
2572
|
+
} catch {
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
if (!selected) return;
|
|
2576
|
+
const record = isObjectRecord(response) ? response : {};
|
|
2577
|
+
await usageTap.captureSample({
|
|
2578
|
+
sampleId: ctx.begin.data.callId,
|
|
2579
|
+
callId: ctx.begin.data.callId,
|
|
2580
|
+
customerId: beginRequest.customerId,
|
|
2581
|
+
feature: beginRequest.feature,
|
|
2582
|
+
tags: beginRequest.tags,
|
|
2583
|
+
provider: sampling.provider,
|
|
2584
|
+
model: readString(record.model) ?? readString(input.model),
|
|
2585
|
+
input,
|
|
2586
|
+
...response === void 0 ? {} : { output: response },
|
|
2587
|
+
usage: record.usage,
|
|
2588
|
+
latencyMs: Date.now() - startedAt,
|
|
2589
|
+
...error === void 0 ? {} : { error: serializeSamplingError(error) }
|
|
2590
|
+
}).catch(() => void 0);
|
|
2591
|
+
}
|
|
1768
2592
|
function withCompression(client, options = {}) {
|
|
1769
2593
|
if (!client) {
|
|
1770
2594
|
throw new UsageTapError(
|
|
@@ -1775,11 +2599,13 @@ function withCompression(client, options = {}) {
|
|
|
1775
2599
|
const {
|
|
1776
2600
|
apiKey,
|
|
1777
2601
|
usageTapClient,
|
|
2602
|
+
sampling,
|
|
1778
2603
|
...compressionOverrides
|
|
1779
2604
|
} = options;
|
|
1780
2605
|
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
|
|
1781
2606
|
const compression = {
|
|
1782
2607
|
provider: "usagetap",
|
|
2608
|
+
roles: { user: { enabled: true, aggressiveness: 0.2 } },
|
|
1783
2609
|
minContextTokens: DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS,
|
|
1784
2610
|
...compressionOverrides
|
|
1785
2611
|
};
|
|
@@ -1789,7 +2615,7 @@ function withCompression(client, options = {}) {
|
|
|
1789
2615
|
usageTap,
|
|
1790
2616
|
compression
|
|
1791
2617
|
) : void 0;
|
|
1792
|
-
|
|
2618
|
+
const compressed = new Proxy(client, {
|
|
1793
2619
|
get(target, prop, receiver) {
|
|
1794
2620
|
if (prop === "chat" && proxiedChat) {
|
|
1795
2621
|
return proxiedChat;
|
|
@@ -1803,6 +2629,11 @@ function withCompression(client, options = {}) {
|
|
|
1803
2629
|
return Reflect.get(target, prop, receiver);
|
|
1804
2630
|
}
|
|
1805
2631
|
});
|
|
2632
|
+
return sampling ? withSampling(compressed, {
|
|
2633
|
+
...sampling === true ? {} : sampling,
|
|
2634
|
+
apiKey,
|
|
2635
|
+
usageTapClient
|
|
2636
|
+
}) : compressed;
|
|
1806
2637
|
}
|
|
1807
2638
|
function withMetering(client, customer) {
|
|
1808
2639
|
const config = typeof customer === "string" ? { customerId: customer } : customer;
|
|
@@ -1817,6 +2648,8 @@ function withMetering(client, customer) {
|
|
|
1817
2648
|
usageTapClient,
|
|
1818
2649
|
applyVendorHints,
|
|
1819
2650
|
promptCompression,
|
|
2651
|
+
sampling,
|
|
2652
|
+
provider,
|
|
1820
2653
|
...defaultContext
|
|
1821
2654
|
} = config;
|
|
1822
2655
|
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
|
|
@@ -1824,7 +2657,9 @@ function withMetering(client, customer) {
|
|
|
1824
2657
|
return wrapOpenAI(client, usageTap, {
|
|
1825
2658
|
defaultContext,
|
|
1826
2659
|
applyVendorHints,
|
|
1827
|
-
promptCompression: normalizedCompression
|
|
2660
|
+
promptCompression: normalizedCompression,
|
|
2661
|
+
sampling,
|
|
2662
|
+
provider
|
|
1828
2663
|
});
|
|
1829
2664
|
}
|
|
1830
2665
|
function wrapOpenAI(client, usageTap, options = {}) {
|
|
@@ -1834,6 +2669,8 @@ function wrapOpenAI(client, usageTap, options = {}) {
|
|
|
1834
2669
|
const defaultContext = options.defaultContext;
|
|
1835
2670
|
const applyVendorHints = options.applyVendorHints !== false;
|
|
1836
2671
|
const defaultPromptCompression = normalizePromptCompressionOptions(options.promptCompression);
|
|
2672
|
+
const defaultSampling = normalizeMeteredOpenAISampling(options.sampling);
|
|
2673
|
+
const provider = options.provider ?? "openai";
|
|
1837
2674
|
const promptCompressionStats = new OpenAIPromptCompressionStats();
|
|
1838
2675
|
const proxiedChat = client.chat ? createChatProxy(
|
|
1839
2676
|
client.chat,
|
|
@@ -1841,7 +2678,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
|
|
|
1841
2678
|
defaultContext,
|
|
1842
2679
|
applyVendorHints,
|
|
1843
2680
|
defaultPromptCompression,
|
|
1844
|
-
promptCompressionStats
|
|
2681
|
+
promptCompressionStats,
|
|
2682
|
+
defaultSampling,
|
|
2683
|
+
provider
|
|
1845
2684
|
) : void 0;
|
|
1846
2685
|
const proxiedResponses = typeof client.responses !== "undefined" ? createResponsesProxy(
|
|
1847
2686
|
client.responses,
|
|
@@ -1849,7 +2688,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
|
|
|
1849
2688
|
defaultContext,
|
|
1850
2689
|
applyVendorHints,
|
|
1851
2690
|
defaultPromptCompression,
|
|
1852
|
-
promptCompressionStats
|
|
2691
|
+
promptCompressionStats,
|
|
2692
|
+
defaultSampling,
|
|
2693
|
+
provider
|
|
1853
2694
|
) : void 0;
|
|
1854
2695
|
const handler = {
|
|
1855
2696
|
get(target, prop, receiver) {
|
|
@@ -1968,14 +2809,16 @@ function streamOpenAIRoute(usageTap, openai, options) {
|
|
|
1968
2809
|
});
|
|
1969
2810
|
};
|
|
1970
2811
|
}
|
|
1971
|
-
function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2812
|
+
function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
1972
2813
|
const completions = createChatCompletionsProxy(
|
|
1973
2814
|
resource.completions,
|
|
1974
2815
|
usageTap,
|
|
1975
2816
|
defaultContext,
|
|
1976
2817
|
applyVendorHints,
|
|
1977
2818
|
defaultPromptCompression,
|
|
1978
|
-
promptCompressionStats
|
|
2819
|
+
promptCompressionStats,
|
|
2820
|
+
defaultSampling,
|
|
2821
|
+
provider
|
|
1979
2822
|
);
|
|
1980
2823
|
const handler = {
|
|
1981
2824
|
get(target, prop, receiver) {
|
|
@@ -1987,7 +2830,7 @@ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, d
|
|
|
1987
2830
|
};
|
|
1988
2831
|
return new Proxy(resource, handler);
|
|
1989
2832
|
}
|
|
1990
|
-
function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2833
|
+
function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
1991
2834
|
if (!resource || typeof resource !== "object") {
|
|
1992
2835
|
return void 0;
|
|
1993
2836
|
}
|
|
@@ -2002,9 +2845,20 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
2002
2845
|
withUsage,
|
|
2003
2846
|
promptCompression
|
|
2004
2847
|
} = splitUsageOptions(options);
|
|
2005
|
-
const beginRequest =
|
|
2848
|
+
const beginRequest = responsesBeginRequest(
|
|
2849
|
+
resolveBeginRequest(defaultContext, usageContext),
|
|
2850
|
+
params
|
|
2851
|
+
);
|
|
2006
2852
|
const wantsStream = isStreamingRequest(params);
|
|
2007
2853
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2854
|
+
const settle = wantsStream ? deferUsageFinalization(ctx) : void 0;
|
|
2855
|
+
const sampleStartedAt = Date.now();
|
|
2856
|
+
const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
|
|
2857
|
+
usageTap,
|
|
2858
|
+
sampling: defaultSampling,
|
|
2859
|
+
beginRequest,
|
|
2860
|
+
input: params
|
|
2861
|
+
});
|
|
2008
2862
|
const hintedParams = applyVendorHints ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
2009
2863
|
const finalParams = await compressResponsesParamsForCall({
|
|
2010
2864
|
params: hintedParams,
|
|
@@ -2016,25 +2870,67 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
2016
2870
|
withUsage,
|
|
2017
2871
|
operation: "responses.create"
|
|
2018
2872
|
});
|
|
2873
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
2019
2874
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
2020
2875
|
if (wantsStream) {
|
|
2021
2876
|
const apiPromise2 = originalCreate(finalParams, request);
|
|
2022
2877
|
const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
|
|
2023
2878
|
ensureAsyncIterable(rawStream, "responses.create");
|
|
2024
|
-
const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2879
|
+
const wrappedStream = wrapStreamForUsageTap(rawStream, async (termination) => {
|
|
2880
|
+
try {
|
|
2881
|
+
if (termination === "complete") {
|
|
2882
|
+
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
|
|
2883
|
+
if (usage) {
|
|
2884
|
+
ctx.setUsage(usage);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
} catch (error) {
|
|
2888
|
+
ctx.setError({
|
|
2889
|
+
code: "USAGE_FINALIZE_ERROR",
|
|
2890
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2891
|
+
});
|
|
2892
|
+
throw error;
|
|
2893
|
+
} finally {
|
|
2894
|
+
await settle?.();
|
|
2028
2895
|
}
|
|
2029
|
-
}, ctx)
|
|
2896
|
+
}, ctx, (chunk) => {
|
|
2897
|
+
tryInferUsageFromStreamChunk(
|
|
2898
|
+
chunk,
|
|
2899
|
+
ctx.begin.data.vendorHints,
|
|
2900
|
+
ctx,
|
|
2901
|
+
provider
|
|
2902
|
+
);
|
|
2903
|
+
});
|
|
2030
2904
|
return wrappedStream;
|
|
2031
2905
|
});
|
|
2032
2906
|
return wrappedPromise2;
|
|
2033
2907
|
}
|
|
2034
2908
|
const apiPromise = originalCreate(finalParams, request);
|
|
2035
|
-
const wrappedPromise = transformApiPromise(apiPromise, (response) => {
|
|
2036
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
|
|
2909
|
+
const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
|
|
2910
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
|
|
2911
|
+
await captureMeteredOpenAISample({
|
|
2912
|
+
usageTap,
|
|
2913
|
+
sampling: defaultSampling,
|
|
2914
|
+
decision: sampleDecision,
|
|
2915
|
+
ctx,
|
|
2916
|
+
beginRequest,
|
|
2917
|
+
input: params,
|
|
2918
|
+
response,
|
|
2919
|
+
startedAt: sampleStartedAt
|
|
2920
|
+
});
|
|
2037
2921
|
return response;
|
|
2922
|
+
}, async (error) => {
|
|
2923
|
+
await captureMeteredOpenAISample({
|
|
2924
|
+
usageTap,
|
|
2925
|
+
sampling: defaultSampling,
|
|
2926
|
+
decision: sampleDecision,
|
|
2927
|
+
ctx,
|
|
2928
|
+
beginRequest,
|
|
2929
|
+
input: params,
|
|
2930
|
+
error,
|
|
2931
|
+
startedAt: sampleStartedAt
|
|
2932
|
+
});
|
|
2933
|
+
throw error;
|
|
2038
2934
|
});
|
|
2039
2935
|
return wrappedPromise;
|
|
2040
2936
|
}, withUsage);
|
|
@@ -2049,7 +2945,7 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
2049
2945
|
};
|
|
2050
2946
|
return new Proxy(resource, handler);
|
|
2051
2947
|
}
|
|
2052
|
-
function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2948
|
+
function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
2053
2949
|
const originalCreate = resource.create.bind(resource);
|
|
2054
2950
|
const streamCandidate = resource.stream;
|
|
2055
2951
|
const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(resource) : void 0;
|
|
@@ -2063,8 +2959,16 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2063
2959
|
const beginRequest = resolveBeginRequest(defaultContext, usageContext);
|
|
2064
2960
|
const wantsStream = isStreamingRequest(params);
|
|
2065
2961
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2962
|
+
const settle = wantsStream ? deferUsageFinalization(ctx) : void 0;
|
|
2963
|
+
const sampleStartedAt = Date.now();
|
|
2964
|
+
const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
|
|
2965
|
+
usageTap,
|
|
2966
|
+
sampling: defaultSampling,
|
|
2967
|
+
beginRequest,
|
|
2968
|
+
input: params
|
|
2969
|
+
});
|
|
2066
2970
|
const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
2067
|
-
const
|
|
2971
|
+
const compressedParams = await compressChatParamsForCall({
|
|
2068
2972
|
params: hintedParams,
|
|
2069
2973
|
usageTap,
|
|
2070
2974
|
ctx,
|
|
@@ -2074,25 +2978,68 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2074
2978
|
withUsage,
|
|
2075
2979
|
operation: "chat.completions.create"
|
|
2076
2980
|
});
|
|
2981
|
+
const finalParams = wantsStream ? ensureOpenAIStreamUsage(compressedParams) : compressedParams;
|
|
2982
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
2077
2983
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
2078
2984
|
if (wantsStream) {
|
|
2079
2985
|
const apiPromise2 = originalCreate(finalParams, request);
|
|
2080
2986
|
const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
|
|
2081
2987
|
ensureAsyncIterable(rawStream, "chat.completions.create");
|
|
2082
|
-
const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2988
|
+
const wrappedStream2 = wrapStreamForUsageTap(rawStream, async (termination) => {
|
|
2989
|
+
try {
|
|
2990
|
+
if (termination === "complete") {
|
|
2991
|
+
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
|
|
2992
|
+
if (usage) {
|
|
2993
|
+
ctx.setUsage(usage);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
} catch (error) {
|
|
2997
|
+
ctx.setError({
|
|
2998
|
+
code: "USAGE_FINALIZE_ERROR",
|
|
2999
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3000
|
+
});
|
|
3001
|
+
throw error;
|
|
3002
|
+
} finally {
|
|
3003
|
+
await settle?.();
|
|
2086
3004
|
}
|
|
2087
|
-
}, ctx)
|
|
3005
|
+
}, ctx, (chunk) => {
|
|
3006
|
+
tryInferUsageFromStreamChunk(
|
|
3007
|
+
chunk,
|
|
3008
|
+
ctx.begin.data.vendorHints,
|
|
3009
|
+
ctx,
|
|
3010
|
+
provider
|
|
3011
|
+
);
|
|
3012
|
+
});
|
|
2088
3013
|
return wrappedStream2;
|
|
2089
3014
|
});
|
|
2090
3015
|
return wrappedPromise2;
|
|
2091
3016
|
}
|
|
2092
3017
|
const apiPromise = originalCreate(finalParams, request);
|
|
2093
|
-
const wrappedPromise = transformApiPromise(apiPromise, (response) => {
|
|
2094
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
|
|
3018
|
+
const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
|
|
3019
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
|
|
3020
|
+
await captureMeteredOpenAISample({
|
|
3021
|
+
usageTap,
|
|
3022
|
+
sampling: defaultSampling,
|
|
3023
|
+
decision: sampleDecision,
|
|
3024
|
+
ctx,
|
|
3025
|
+
beginRequest,
|
|
3026
|
+
input: params,
|
|
3027
|
+
response,
|
|
3028
|
+
startedAt: sampleStartedAt
|
|
3029
|
+
});
|
|
2095
3030
|
return response;
|
|
3031
|
+
}, async (error) => {
|
|
3032
|
+
await captureMeteredOpenAISample({
|
|
3033
|
+
usageTap,
|
|
3034
|
+
sampling: defaultSampling,
|
|
3035
|
+
decision: sampleDecision,
|
|
3036
|
+
ctx,
|
|
3037
|
+
beginRequest,
|
|
3038
|
+
input: params,
|
|
3039
|
+
error,
|
|
3040
|
+
startedAt: sampleStartedAt
|
|
3041
|
+
});
|
|
3042
|
+
throw error;
|
|
2096
3043
|
});
|
|
2097
3044
|
return wrappedPromise;
|
|
2098
3045
|
}, withUsage);
|
|
@@ -2106,8 +3053,9 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2106
3053
|
} = splitUsageOptions(options);
|
|
2107
3054
|
const beginRequest = resolveBeginRequest(defaultContext, usageContext);
|
|
2108
3055
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
3056
|
+
const settle = deferUsageFinalization(ctx);
|
|
2109
3057
|
const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
2110
|
-
const
|
|
3058
|
+
const compressedParams = await compressChatParamsForCall({
|
|
2111
3059
|
params: hintedParams,
|
|
2112
3060
|
usageTap,
|
|
2113
3061
|
ctx,
|
|
@@ -2117,16 +3065,41 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2117
3065
|
withUsage,
|
|
2118
3066
|
operation: "chat.completions.stream"
|
|
2119
3067
|
});
|
|
3068
|
+
const finalParams = ensureOpenAIStreamUsage(compressedParams);
|
|
3069
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
2120
3070
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
2121
3071
|
const apiPromise = originalStream(finalParams, request);
|
|
2122
3072
|
const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
|
|
2123
3073
|
ensureAsyncIterable(rawStream, "chat.completions.stream");
|
|
2124
|
-
const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
3074
|
+
const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async (termination) => {
|
|
3075
|
+
try {
|
|
3076
|
+
if (termination === "complete") {
|
|
3077
|
+
const usage = await extractUsageFromStream(
|
|
3078
|
+
rawStream,
|
|
3079
|
+
ctx.begin.data.vendorHints,
|
|
3080
|
+
provider
|
|
3081
|
+
);
|
|
3082
|
+
if (usage) {
|
|
3083
|
+
ctx.setUsage(usage);
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
} catch (error) {
|
|
3087
|
+
ctx.setError({
|
|
3088
|
+
code: "USAGE_FINALIZE_ERROR",
|
|
3089
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3090
|
+
});
|
|
3091
|
+
throw error;
|
|
3092
|
+
} finally {
|
|
3093
|
+
await settle();
|
|
2128
3094
|
}
|
|
2129
|
-
}, ctx)
|
|
3095
|
+
}, ctx, (chunk) => {
|
|
3096
|
+
tryInferUsageFromStreamChunk(
|
|
3097
|
+
chunk,
|
|
3098
|
+
ctx.begin.data.vendorHints,
|
|
3099
|
+
ctx,
|
|
3100
|
+
provider
|
|
3101
|
+
);
|
|
3102
|
+
});
|
|
2130
3103
|
return wrappedStreamInner;
|
|
2131
3104
|
});
|
|
2132
3105
|
return wrappedPromise;
|
|
@@ -2248,6 +3221,10 @@ async function compressChatParams(params, usageTap, compression, signal) {
|
|
|
2248
3221
|
const result = await usageTap.compressPromptMessages(source, {
|
|
2249
3222
|
provider: "usagetap",
|
|
2250
3223
|
failOpen: compression.failOpen,
|
|
3224
|
+
mode: compression.mode,
|
|
3225
|
+
latencyBudgetMs: compression.latencyBudgetMs,
|
|
3226
|
+
compactEmptyUserMessages: compression.compactEmptyUserMessages,
|
|
3227
|
+
compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
|
|
2251
3228
|
aggressiveness: resolveMessageEndpointAggressiveness(compression),
|
|
2252
3229
|
signal
|
|
2253
3230
|
});
|
|
@@ -2717,19 +3694,52 @@ function resolveBeginRequest(defaults, override) {
|
|
|
2717
3694
|
if (requested) begin.requested = requested;
|
|
2718
3695
|
const feature = current.feature ?? base.feature;
|
|
2719
3696
|
if (feature) begin.feature = feature;
|
|
3697
|
+
const runId = current.runId ?? base.runId;
|
|
3698
|
+
if (runId) begin.runId = runId;
|
|
2720
3699
|
const idempotency = current.idempotency ?? base.idempotency;
|
|
2721
3700
|
if (idempotency) begin.idempotency = idempotency;
|
|
2722
3701
|
const customerName = current.customerName ?? base.customerName;
|
|
2723
3702
|
if (customerName) begin.customerName = customerName;
|
|
2724
3703
|
const customerEmail = current.customerEmail ?? base.customerEmail;
|
|
2725
3704
|
if (customerEmail) begin.customerEmail = customerEmail;
|
|
3705
|
+
const customerUserId = current.customerUserId ?? base.customerUserId;
|
|
3706
|
+
if (customerUserId) begin.customerUserId = customerUserId;
|
|
3707
|
+
const customerUserName = current.customerUserName ?? base.customerUserName;
|
|
3708
|
+
if (customerUserName) begin.customerUserName = customerUserName;
|
|
3709
|
+
const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
|
|
3710
|
+
if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
|
|
3711
|
+
const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
|
|
3712
|
+
if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
|
|
3713
|
+
const batch = current.batch ?? base.batch;
|
|
3714
|
+
if (typeof batch === "boolean") begin.batch = batch;
|
|
3715
|
+
const pricingMode = current.pricingMode ?? base.pricingMode;
|
|
3716
|
+
if (pricingMode) begin.pricingMode = pricingMode;
|
|
2726
3717
|
if (tags?.length) {
|
|
2727
3718
|
begin.tags = tags;
|
|
2728
3719
|
}
|
|
2729
3720
|
return begin;
|
|
2730
3721
|
}
|
|
2731
|
-
function
|
|
2732
|
-
|
|
3722
|
+
function responsesBeginRequest(begin, params) {
|
|
3723
|
+
if (!responsesRequestUsesWebSearch(params)) return begin;
|
|
3724
|
+
return {
|
|
3725
|
+
...begin,
|
|
3726
|
+
requested: {
|
|
3727
|
+
...begin.requested ?? {},
|
|
3728
|
+
search: true
|
|
3729
|
+
}
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
function responsesRequestUsesWebSearch(params) {
|
|
3733
|
+
if (!params || typeof params !== "object") return false;
|
|
3734
|
+
const tools = params.tools;
|
|
3735
|
+
return Array.isArray(tools) && tools.some((tool) => {
|
|
3736
|
+
if (!tool || typeof tool !== "object") return false;
|
|
3737
|
+
const type = tool.type;
|
|
3738
|
+
return type === "web_search" || type === "web_search_preview";
|
|
3739
|
+
});
|
|
3740
|
+
}
|
|
3741
|
+
function transformApiPromise(apiPromise, onResolve, onReject) {
|
|
3742
|
+
const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
|
|
2733
3743
|
if (isObjectRecord(apiPromise)) {
|
|
2734
3744
|
const proto = Object.getPrototypeOf(apiPromise);
|
|
2735
3745
|
if (proto) {
|
|
@@ -2864,12 +3874,12 @@ function applyResponsesVendorHints(params, hints) {
|
|
|
2864
3874
|
}
|
|
2865
3875
|
return next;
|
|
2866
3876
|
}
|
|
2867
|
-
async function extractUsageFromStream(stream, hints) {
|
|
3877
|
+
async function extractUsageFromStream(stream, hints, provider = "openai") {
|
|
2868
3878
|
const finalPayload = await resolveStreamFinalPayload(stream);
|
|
2869
3879
|
if (!finalPayload) {
|
|
2870
3880
|
return void 0;
|
|
2871
3881
|
}
|
|
2872
|
-
return inferUsageFromResponse(finalPayload, hints);
|
|
3882
|
+
return inferUsageFromResponse(finalPayload, hints, provider);
|
|
2873
3883
|
}
|
|
2874
3884
|
async function resolveStreamFinalPayload(stream) {
|
|
2875
3885
|
if (!stream || typeof stream !== "object") {
|
|
@@ -2942,14 +3952,14 @@ function setHeaderIfPossible(res, key, value) {
|
|
|
2942
3952
|
res.setHeader(key, value);
|
|
2943
3953
|
}
|
|
2944
3954
|
}
|
|
2945
|
-
function tryInferUsage(response, hints, extractor, ctx) {
|
|
3955
|
+
function tryInferUsage(response, hints, extractor, ctx, provider = "openai") {
|
|
2946
3956
|
const explicit = extractor?.(response);
|
|
2947
|
-
const inferred = explicit ?? inferUsageFromResponse(response, hints);
|
|
3957
|
+
const inferred = explicit ?? inferUsageFromResponse(response, hints, provider);
|
|
2948
3958
|
if (inferred) {
|
|
2949
3959
|
ctx.setUsage(inferred);
|
|
2950
3960
|
}
|
|
2951
3961
|
}
|
|
2952
|
-
function inferUsageFromResponse(response, hints) {
|
|
3962
|
+
function inferUsageFromResponse(response, hints, provider = "openai") {
|
|
2953
3963
|
if (!response || typeof response !== "object") {
|
|
2954
3964
|
return void 0;
|
|
2955
3965
|
}
|
|
@@ -2957,32 +3967,110 @@ function inferUsageFromResponse(response, hints) {
|
|
|
2957
3967
|
if (!candidate.usage) {
|
|
2958
3968
|
return void 0;
|
|
2959
3969
|
}
|
|
2960
|
-
const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
|
|
3970
|
+
const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
|
|
3971
|
+
const cacheWriteInputTokens = candidate.usage.prompt_tokens_details?.cache_write_tokens ?? candidate.usage.prompt_tokens_details?.cache_creation_tokens ?? candidate.usage.input_tokens_details?.cache_write_tokens ?? candidate.usage.input_tokens_details?.cache_creation_tokens ?? candidate.usage.cache_creation_input_tokens ?? candidate.usage.cache_write_input_tokens ?? candidate.usage.cache_write_tokens;
|
|
3972
|
+
const cacheWrite5mInputTokens = candidate.usage.cache_write_5m_input_tokens ?? candidate.usage.cache_creation?.ephemeral_5m_input_tokens;
|
|
3973
|
+
const cacheWrite1hInputTokens = candidate.usage.cache_write_1h_input_tokens ?? candidate.usage.cache_creation?.ephemeral_1h_input_tokens;
|
|
3974
|
+
const outputSearches = Array.isArray(candidate.output) ? candidate.output.filter((item) => item?.type === "web_search_call").length : 0;
|
|
3975
|
+
const searches = candidate.usage.searches ?? candidate.usage.web_search_queries ?? candidate.usage.server_tool_use?.web_search_requests ?? outputSearches;
|
|
3976
|
+
const responseEffort = normalizeExecutionReasoningEffort(
|
|
3977
|
+
candidate.reasoning?.effort
|
|
3978
|
+
);
|
|
2961
3979
|
return {
|
|
3980
|
+
providerUsed: provider,
|
|
2962
3981
|
modelUsed: candidate.model ?? hints?.preferredModel,
|
|
2963
|
-
inputTokens: candidate.usage.prompt_tokens,
|
|
2964
|
-
responseTokens: candidate.usage.completion_tokens,
|
|
2965
|
-
cachedInputTokens
|
|
3982
|
+
inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
|
|
3983
|
+
responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
|
|
3984
|
+
cachedInputTokens,
|
|
3985
|
+
cacheWriteInputTokens,
|
|
3986
|
+
cacheWrite5mInputTokens,
|
|
3987
|
+
cacheWrite1hInputTokens,
|
|
3988
|
+
audioInputTokens: candidate.usage.prompt_tokens_details?.audio_tokens ?? candidate.usage.input_tokens_details?.audio_tokens,
|
|
3989
|
+
cachedAudioInputTokens: candidate.usage.prompt_tokens_details?.cached_audio_tokens ?? candidate.usage.input_tokens_details?.cached_audio_tokens ?? candidate.usage.prompt_tokens_details?.cached_tokens_details?.audio_tokens ?? candidate.usage.input_tokens_details?.cached_tokens_details?.audio_tokens,
|
|
3990
|
+
imageInputTokens: candidate.usage.prompt_tokens_details?.image_tokens ?? candidate.usage.input_tokens_details?.image_tokens,
|
|
3991
|
+
imageOutputTokens: candidate.usage.completion_tokens_details?.image_tokens ?? candidate.usage.output_tokens_details?.image_tokens,
|
|
3992
|
+
audioOutputTokens: candidate.usage.completion_tokens_details?.audio_tokens ?? candidate.usage.output_tokens_details?.audio_tokens,
|
|
3993
|
+
reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
|
|
3994
|
+
...responseEffort ? {
|
|
3995
|
+
reasoningEffort: responseEffort,
|
|
3996
|
+
reasoningEffortSource: "provider_response"
|
|
3997
|
+
} : {},
|
|
3998
|
+
...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {},
|
|
3999
|
+
...typeof searches === "number" && searches > 0 ? { searches } : {}
|
|
2966
4000
|
};
|
|
2967
4001
|
}
|
|
2968
|
-
function
|
|
4002
|
+
function normalizeExecutionReasoningEffort(value) {
|
|
4003
|
+
return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
|
|
4004
|
+
}
|
|
4005
|
+
function openAIRequestExecutionMetadata(params, provider) {
|
|
4006
|
+
const record = params && typeof params === "object" ? params : {};
|
|
4007
|
+
const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
|
|
4008
|
+
const effort = normalizeExecutionReasoningEffort(
|
|
4009
|
+
record.reasoning_effort ?? reasoning?.effort ?? record.thinking_level
|
|
4010
|
+
);
|
|
4011
|
+
const mode = typeof reasoning?.type === "string" ? reasoning.type : typeof reasoning?.mode === "string" ? reasoning.mode : void 0;
|
|
4012
|
+
const rawBudget = reasoning?.budget_tokens ?? record.thinking_budget ?? record.thinking_budget_tokens;
|
|
4013
|
+
const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
|
|
4014
|
+
return {
|
|
4015
|
+
providerUsed: provider,
|
|
4016
|
+
...typeof record.model === "string" ? { modelUsed: record.model } : {},
|
|
4017
|
+
...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
|
|
4018
|
+
...mode ? { reasoningMode: mode } : {},
|
|
4019
|
+
...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
|
|
4020
|
+
};
|
|
4021
|
+
}
|
|
4022
|
+
function tryInferUsageFromStreamChunk(chunk, hints, ctx, provider) {
|
|
4023
|
+
const payload = isObjectRecord(chunk) && isObjectRecord(chunk.response) ? chunk.response : chunk;
|
|
4024
|
+
const inferred = inferUsageFromResponse(payload, hints, provider);
|
|
4025
|
+
if (inferred) {
|
|
4026
|
+
ctx.setUsage(inferred);
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
function ensureOpenAIStreamUsage(params) {
|
|
4030
|
+
if (!isObjectRecord(params)) {
|
|
4031
|
+
return params;
|
|
4032
|
+
}
|
|
4033
|
+
const streamOptions = isObjectRecord(params.stream_options) ? params.stream_options : {};
|
|
4034
|
+
return {
|
|
4035
|
+
...params,
|
|
4036
|
+
stream_options: {
|
|
4037
|
+
...streamOptions,
|
|
4038
|
+
include_usage: true
|
|
4039
|
+
}
|
|
4040
|
+
};
|
|
4041
|
+
}
|
|
4042
|
+
function deferUsageFinalization(ctx) {
|
|
4043
|
+
return ctx.deferFinalization?.() ?? (() => Promise.resolve());
|
|
4044
|
+
}
|
|
4045
|
+
function wrapStreamForUsageTap(source, finalize, ctx, onChunk) {
|
|
2969
4046
|
const getIterator = source[Symbol.asyncIterator];
|
|
2970
4047
|
if (typeof getIterator !== "function") {
|
|
2971
4048
|
throw new TypeError("Stream is not async iterable");
|
|
2972
4049
|
}
|
|
2973
4050
|
const iterator = getIterator.call(source);
|
|
2974
4051
|
let completed = false;
|
|
2975
|
-
const invokeFinalize = async () => {
|
|
4052
|
+
const invokeFinalize = async (termination, error) => {
|
|
2976
4053
|
if (completed) return;
|
|
2977
4054
|
completed = true;
|
|
4055
|
+
if (termination === "cancel" || termination === "manual") {
|
|
4056
|
+
ctx.setError({
|
|
4057
|
+
code: "STREAM_ABORTED",
|
|
4058
|
+
message: termination === "cancel" ? "Provider stream consumption was cancelled before completion" : "Provider stream was finalized before completion"
|
|
4059
|
+
});
|
|
4060
|
+
} else if (termination === "error") {
|
|
4061
|
+
ctx.setError({
|
|
4062
|
+
code: "VENDOR_ERROR",
|
|
4063
|
+
message: error instanceof Error ? error.message : String(error)
|
|
4064
|
+
});
|
|
4065
|
+
}
|
|
2978
4066
|
try {
|
|
2979
|
-
await finalize();
|
|
2980
|
-
} catch (
|
|
4067
|
+
await finalize(termination);
|
|
4068
|
+
} catch (error2) {
|
|
2981
4069
|
ctx.setError({
|
|
2982
4070
|
code: "USAGE_FINALIZE_ERROR",
|
|
2983
|
-
message:
|
|
4071
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
2984
4072
|
});
|
|
2985
|
-
throw
|
|
4073
|
+
throw error2;
|
|
2986
4074
|
}
|
|
2987
4075
|
};
|
|
2988
4076
|
const prototype = Object.getPrototypeOf(source) ?? Object.prototype;
|
|
@@ -3006,12 +4094,15 @@ function wrapStreamForUsageTap(source, finalize, ctx) {
|
|
|
3006
4094
|
value: async (...args) => {
|
|
3007
4095
|
try {
|
|
3008
4096
|
const result = await iterator.next(...args);
|
|
4097
|
+
if (!result.done) {
|
|
4098
|
+
onChunk?.(result.value);
|
|
4099
|
+
}
|
|
3009
4100
|
if (result.done) {
|
|
3010
|
-
await invokeFinalize();
|
|
4101
|
+
await invokeFinalize("complete");
|
|
3011
4102
|
}
|
|
3012
4103
|
return result;
|
|
3013
4104
|
} catch (error) {
|
|
3014
|
-
await invokeFinalize().catch(() => void 0);
|
|
4105
|
+
await invokeFinalize("error", error).catch(() => void 0);
|
|
3015
4106
|
throw error;
|
|
3016
4107
|
}
|
|
3017
4108
|
},
|
|
@@ -3020,39 +4111,49 @@ function wrapStreamForUsageTap(source, finalize, ctx) {
|
|
|
3020
4111
|
});
|
|
3021
4112
|
Object.defineProperty(wrapped, "return", {
|
|
3022
4113
|
value: async (value) => {
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
4114
|
+
try {
|
|
4115
|
+
if (typeof iterator.return === "function") {
|
|
4116
|
+
const rawResult = await iterator.return(value);
|
|
4117
|
+
if (!isIteratorResult(rawResult)) {
|
|
4118
|
+
throw new TypeError("Iterator.return() returned an invalid result");
|
|
4119
|
+
}
|
|
4120
|
+
await invokeFinalize("cancel");
|
|
4121
|
+
return rawResult;
|
|
3027
4122
|
}
|
|
3028
|
-
await invokeFinalize();
|
|
3029
|
-
return
|
|
4123
|
+
await invokeFinalize("cancel");
|
|
4124
|
+
return { done: true, value };
|
|
4125
|
+
} catch (error) {
|
|
4126
|
+
await invokeFinalize("error", error).catch(() => void 0);
|
|
4127
|
+
throw error;
|
|
3030
4128
|
}
|
|
3031
|
-
await invokeFinalize();
|
|
3032
|
-
return { done: true, value };
|
|
3033
4129
|
},
|
|
3034
4130
|
configurable: true,
|
|
3035
4131
|
writable: true
|
|
3036
4132
|
});
|
|
3037
4133
|
Object.defineProperty(wrapped, "throw", {
|
|
3038
4134
|
value: async (error) => {
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
4135
|
+
try {
|
|
4136
|
+
if (typeof iterator.throw === "function") {
|
|
4137
|
+
const rawResult = await iterator.throw(error);
|
|
4138
|
+
if (!isIteratorResult(rawResult)) {
|
|
4139
|
+
throw new TypeError("Iterator.throw() returned an invalid result");
|
|
4140
|
+
}
|
|
4141
|
+
await invokeFinalize("error", error);
|
|
4142
|
+
return rawResult;
|
|
3043
4143
|
}
|
|
3044
|
-
await invokeFinalize();
|
|
3045
|
-
|
|
4144
|
+
await invokeFinalize("error", error);
|
|
4145
|
+
throw error;
|
|
4146
|
+
} catch (thrownError) {
|
|
4147
|
+
await invokeFinalize("error", thrownError).catch(() => void 0);
|
|
4148
|
+
throw thrownError;
|
|
3046
4149
|
}
|
|
3047
|
-
await invokeFinalize();
|
|
3048
|
-
throw error;
|
|
3049
4150
|
},
|
|
3050
4151
|
configurable: true,
|
|
3051
4152
|
writable: true
|
|
3052
4153
|
});
|
|
3053
4154
|
Object.defineProperty(wrapped, "__usageTapFinalize", {
|
|
3054
4155
|
value: async () => {
|
|
3055
|
-
await invokeFinalize();
|
|
4156
|
+
await invokeFinalize("manual");
|
|
3056
4157
|
},
|
|
3057
4158
|
configurable: true
|
|
3058
4159
|
});
|
|
@@ -3064,7 +4165,7 @@ function isIteratorResult(value) {
|
|
|
3064
4165
|
|
|
3065
4166
|
// src/adapters/openrouter.ts
|
|
3066
4167
|
function createOpenRouterAdapter(init) {
|
|
3067
|
-
return createOpenAIAdapter(init);
|
|
4168
|
+
return createOpenAIAdapter({ ...init, provider: "openrouter" });
|
|
3068
4169
|
}
|
|
3069
4170
|
|
|
3070
4171
|
exports.OpenAIPromptCompressionStats = OpenAIPromptCompressionStats;
|
|
@@ -3075,6 +4176,7 @@ exports.streamOpenAIRoute = streamOpenAIRoute;
|
|
|
3075
4176
|
exports.toNextResponse = toNextResponse;
|
|
3076
4177
|
exports.withCompression = withCompression;
|
|
3077
4178
|
exports.withMetering = withMetering;
|
|
4179
|
+
exports.withSampling = withSampling;
|
|
3078
4180
|
exports.wrapOpenAI = wrapOpenAI;
|
|
3079
4181
|
//# sourceMappingURL=index.cjs.map
|
|
3080
4182
|
//# sourceMappingURL=index.cjs.map
|