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