@usagetap/sdk 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1058 -863
- package/dist/adapters/anthropic.cjs +990 -24
- package/dist/adapters/anthropic.cjs.map +1 -1
- package/dist/adapters/anthropic.d.cts +45 -3
- package/dist/adapters/anthropic.d.ts +45 -3
- package/dist/adapters/anthropic.mjs +990 -25
- package/dist/adapters/anthropic.mjs.map +1 -1
- package/dist/adapters/openai.cjs +1164 -44
- package/dist/adapters/openai.cjs.map +1 -1
- package/dist/adapters/openai.d.cts +46 -3
- package/dist/adapters/openai.d.ts +46 -3
- package/dist/adapters/openai.mjs +1164 -45
- package/dist/adapters/openai.mjs.map +1 -1
- package/dist/adapters/openrouter.cjs +3899 -22
- package/dist/adapters/openrouter.cjs.map +1 -1
- package/dist/adapters/openrouter.d.cts +6 -3
- package/dist/adapters/openrouter.d.ts +6 -3
- package/dist/adapters/openrouter.mjs +3897 -23
- package/dist/adapters/openrouter.mjs.map +1 -1
- package/dist/anthropic/index.cjs +990 -24
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.d.cts +2 -2
- package/dist/anthropic/index.d.ts +2 -2
- package/dist/anthropic/index.mjs +990 -25
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/client-CExQ8e1T.d.cts +1225 -0
- package/dist/client-CExQ8e1T.d.ts +1225 -0
- package/dist/express/index.cjs +421 -29
- package/dist/express/index.cjs.map +1 -1
- package/dist/express/index.d.cts +2 -2
- package/dist/express/index.d.ts +2 -2
- package/dist/express/index.mjs +421 -29
- package/dist/express/index.mjs.map +1 -1
- package/dist/index.cjs +680 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.mjs +680 -15
- package/dist/index.mjs.map +1 -1
- package/dist/openai/index.cjs +1165 -45
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.d.cts +2 -2
- package/dist/openai/index.d.ts +2 -2
- package/dist/openai/index.mjs +1165 -46
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openrouter/index.cjs +1182 -47
- package/dist/openrouter/index.cjs.map +1 -1
- package/dist/openrouter/index.d.cts +3 -3
- package/dist/openrouter/index.d.ts +3 -3
- package/dist/openrouter/index.mjs +1180 -46
- package/dist/openrouter/index.mjs.map +1 -1
- package/dist/react/index.cjs +19 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +17 -4
- package/dist/react/index.d.ts +17 -4
- package/dist/react/index.mjs +19 -1
- package/dist/react/index.mjs.map +1 -1
- package/package.json +84 -84
- package/dist/client-BD8O2J8Z.d.cts +0 -668
- package/dist/client-BD8O2J8Z.d.ts +0 -668
package/dist/adapters/openai.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,358 @@ 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
|
+
models;
|
|
1026
|
+
batches;
|
|
1027
|
+
transport;
|
|
1028
|
+
idempotencyGenerator;
|
|
1029
|
+
constructor(config) {
|
|
1030
|
+
this.transport = new ResourceTransport(
|
|
1031
|
+
config.gatewayBaseUrl ?? DEFAULT_GATEWAY_BASE_URL,
|
|
1032
|
+
config
|
|
1033
|
+
);
|
|
1034
|
+
this.idempotencyGenerator = config.idempotencyGenerator ?? createIdempotencyKey;
|
|
1035
|
+
this.chat = {
|
|
1036
|
+
completions: {
|
|
1037
|
+
create: (params, options) => this.transport.request({
|
|
1038
|
+
method: "POST",
|
|
1039
|
+
path: "/v1/chat/completions",
|
|
1040
|
+
body: params,
|
|
1041
|
+
options,
|
|
1042
|
+
response: "json"
|
|
1043
|
+
})
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
this.models = {
|
|
1047
|
+
list: (options) => this.transport.request({
|
|
1048
|
+
method: "GET",
|
|
1049
|
+
path: "/v1/models",
|
|
1050
|
+
options,
|
|
1051
|
+
response: "json"
|
|
1052
|
+
})
|
|
1053
|
+
};
|
|
1054
|
+
this.batches = {
|
|
1055
|
+
create: (params, options = {}) => this.transport.request({
|
|
1056
|
+
method: "POST",
|
|
1057
|
+
path: "/v1/batches",
|
|
1058
|
+
body: params,
|
|
1059
|
+
options: {
|
|
1060
|
+
...options,
|
|
1061
|
+
idempotencyKey: options.idempotencyKey ?? this.idempotencyGenerator()
|
|
1062
|
+
},
|
|
1063
|
+
response: "json"
|
|
1064
|
+
}),
|
|
1065
|
+
retrieve: (batchId, options) => this.transport.request({
|
|
1066
|
+
method: "GET",
|
|
1067
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1068
|
+
resourceId(batchId, ["id"], "gateway.batches.retrieve")
|
|
1069
|
+
)}`,
|
|
1070
|
+
options,
|
|
1071
|
+
response: "json"
|
|
1072
|
+
}),
|
|
1073
|
+
wait: (batch, options) => this.waitForBatch(batch, options),
|
|
1074
|
+
cancel: (batchId, options) => this.transport.request({
|
|
1075
|
+
method: "POST",
|
|
1076
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1077
|
+
resourceId(batchId, ["id"], "gateway.batches.cancel")
|
|
1078
|
+
)}/cancel`,
|
|
1079
|
+
options,
|
|
1080
|
+
response: "json"
|
|
1081
|
+
}),
|
|
1082
|
+
results: (batchId, options) => this.transport.request({
|
|
1083
|
+
method: "GET",
|
|
1084
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1085
|
+
resourceId(batchId, ["id"], "gateway.batches.results")
|
|
1086
|
+
)}/results`,
|
|
1087
|
+
options,
|
|
1088
|
+
response: "ndjson"
|
|
1089
|
+
})
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
async waitForBatch(value, options = {}) {
|
|
1093
|
+
const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
|
|
1094
|
+
const deadline = Date.now() + timeoutMs;
|
|
1095
|
+
let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
|
|
1096
|
+
while (!terminalGatewayBatch(batch.status)) {
|
|
1097
|
+
if (Date.now() >= deadline) {
|
|
1098
|
+
throw new UsageTapError(
|
|
1099
|
+
"USAGETAP_RETRY_EXHAUSTED",
|
|
1100
|
+
`Gateway batch ${batch.id} did not finish before timeout`,
|
|
1101
|
+
{ retryable: true }
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
await sleep(pollIntervalMs, options.signal);
|
|
1105
|
+
batch = await this.batches.retrieve(batch.id, options);
|
|
1106
|
+
}
|
|
1107
|
+
return batch;
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
|
|
753
1111
|
// src/client.ts
|
|
754
1112
|
var CALL_BEGIN_PATH = "call_begin";
|
|
755
1113
|
var CALL_END_PATH = "call_end";
|
|
756
1114
|
var COMPRESS_PROMPT_PATH = "compress_prompt";
|
|
1115
|
+
var SAMPLES_PATH = "samples";
|
|
1116
|
+
var SAMPLING_SETTINGS_PATH = "sampling/settings";
|
|
1117
|
+
var SAMPLING_DECIDE_PATH = "sampling/decide";
|
|
757
1118
|
var CHECK_USAGE_PATH = "customers/{customerId}/usage";
|
|
758
1119
|
var CREATE_CUSTOMER_PATH = "customers";
|
|
759
1120
|
var CHANGE_PLAN_PATH = "customers/{customerId}/change_plan";
|
|
@@ -764,11 +1125,16 @@ var CORRELATION_HEADER = "x-usage-correlation-id";
|
|
|
764
1125
|
var IDEMPOTENCY_HEADER = "idempotency-key";
|
|
765
1126
|
var SDK_HEADER = "x-usage-sdk";
|
|
766
1127
|
var USER_AGENT = "UsageTapClient";
|
|
767
|
-
var
|
|
1128
|
+
var CANONICAL_MEDIA_TYPE2 = "application/vnd.usagetap.v1+json";
|
|
768
1129
|
var DEFAULT_BASE_URL = "https://api.usagetap.com";
|
|
769
|
-
var
|
|
1130
|
+
var DEFAULT_RUN_INACTIVITY_MS = 60 * 60 * 1e3;
|
|
1131
|
+
var SDK_VERSION = "1.4.0" ;
|
|
770
1132
|
var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
771
1133
|
var UsageTapClient = class {
|
|
1134
|
+
/** OpenAI-compatible chat, model, and native batch operations. */
|
|
1135
|
+
gateway;
|
|
1136
|
+
/** Published-profile context summarization operations. */
|
|
1137
|
+
summarization;
|
|
772
1138
|
apiKey;
|
|
773
1139
|
baseUrl;
|
|
774
1140
|
fetchImpl;
|
|
@@ -793,6 +1159,11 @@ var UsageTapClient = class {
|
|
|
793
1159
|
usageTapCompressionMessagesEndpoint;
|
|
794
1160
|
usageTapCompressionModel;
|
|
795
1161
|
usageTapCompressionAggressiveness;
|
|
1162
|
+
sampling;
|
|
1163
|
+
samplingSettingsCacheMs;
|
|
1164
|
+
circuitBreaker;
|
|
1165
|
+
circuitBreakerRuns = /* @__PURE__ */ new Map();
|
|
1166
|
+
samplingSettingsCache;
|
|
796
1167
|
constructor(options = {}) {
|
|
797
1168
|
const apiKey = options.apiKey?.trim() || readEnvironmentVariable("USAGETAP_API_KEY");
|
|
798
1169
|
const baseUrl = options.baseUrl?.trim() || readEnvironmentVariable("USAGETAP_BASE_URL") || DEFAULT_BASE_URL;
|
|
@@ -815,10 +1186,21 @@ var UsageTapClient = class {
|
|
|
815
1186
|
"A global fetch implementation was not found. Pass fetchImpl in UsageTapClientOptions."
|
|
816
1187
|
);
|
|
817
1188
|
}
|
|
818
|
-
const
|
|
819
|
-
this.baseUrl = new URL(
|
|
1189
|
+
const normalizedBaseUrl2 = normalizeBaseUrl(baseUrl);
|
|
1190
|
+
this.baseUrl = new URL(normalizedBaseUrl2);
|
|
820
1191
|
this.apiKey = apiKey;
|
|
821
1192
|
this.fetchImpl = wrapFetchImplementation(fetchCandidate, !options.fetchImpl);
|
|
1193
|
+
const resourceConfig = {
|
|
1194
|
+
apiKey,
|
|
1195
|
+
apiBaseUrl: normalizedBaseUrl2,
|
|
1196
|
+
gatewayBaseUrl: options.gatewayBaseUrl?.trim() || readEnvironmentVariable("USAGETAP_GATEWAY_URL"),
|
|
1197
|
+
fetchImpl: this.fetchImpl,
|
|
1198
|
+
headers: options.headers,
|
|
1199
|
+
sdkVersion: SDK_VERSION,
|
|
1200
|
+
idempotencyGenerator: options.idempotencyGenerator
|
|
1201
|
+
};
|
|
1202
|
+
this.gateway = new GatewayResource(resourceConfig);
|
|
1203
|
+
this.summarization = new SummarizationResource(resourceConfig);
|
|
822
1204
|
this.defaultFeature = options.defaultFeature;
|
|
823
1205
|
this.defaultTags = options.defaultTags?.length ? dedupeStrings(options.defaultTags) : void 0;
|
|
824
1206
|
this.defaultHeaders = options.headers ? normalizeHeaderDictionary(options.headers) : {};
|
|
@@ -840,11 +1222,122 @@ var UsageTapClient = class {
|
|
|
840
1222
|
this.usageTapCompressionMessagesEndpoint = options.usageTapCompressionMessagesEndpoint;
|
|
841
1223
|
this.usageTapCompressionModel = options.usageTapCompressionModel;
|
|
842
1224
|
this.usageTapCompressionAggressiveness = options.usageTapCompressionAggressiveness;
|
|
1225
|
+
this.sampling = options.sampling;
|
|
1226
|
+
this.samplingSettingsCacheMs = Number.isFinite(options.samplingSettingsCacheMs) ? Math.max(0, Number(options.samplingSettingsCacheMs)) : 5 * 60 * 1e3;
|
|
1227
|
+
if (options.circuitBreaker) {
|
|
1228
|
+
const maxCallsPerRun = options.circuitBreaker.maxCallsPerRun;
|
|
1229
|
+
if (!Number.isInteger(maxCallsPerRun) || maxCallsPerRun < 1) {
|
|
1230
|
+
throw new UsageTapError(
|
|
1231
|
+
"USAGETAP_BAD_REQUEST",
|
|
1232
|
+
"circuitBreaker.maxCallsPerRun must be a positive integer"
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
const runInactivityMs = options.circuitBreaker.runInactivityMs ?? DEFAULT_RUN_INACTIVITY_MS;
|
|
1236
|
+
if (!Number.isFinite(runInactivityMs) || runInactivityMs < 1) {
|
|
1237
|
+
throw new UsageTapError(
|
|
1238
|
+
"USAGETAP_BAD_REQUEST",
|
|
1239
|
+
"circuitBreaker.runInactivityMs must be a positive number"
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
this.circuitBreaker = {
|
|
1243
|
+
maxCallsPerRun,
|
|
1244
|
+
runInactivityMs
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
shouldSample(request, policy = this.sampling || void 0) {
|
|
1249
|
+
if (!policy) return false;
|
|
1250
|
+
const rate = Math.min(1, Math.max(0, Number(policy.rate) || 0));
|
|
1251
|
+
if (rate <= 0) return false;
|
|
1252
|
+
const customerId = request.customerId?.trim();
|
|
1253
|
+
if (customerId && policy.customers?.exclude?.includes(customerId)) return false;
|
|
1254
|
+
const feature = request.feature?.trim();
|
|
1255
|
+
if (feature && policy.features?.exclude?.includes(feature)) return false;
|
|
1256
|
+
const included = policy.features?.include?.filter(Boolean) ?? [];
|
|
1257
|
+
if (included.length > 0 && (!feature || !included.includes(feature))) return false;
|
|
1258
|
+
const minimum = Math.max(0, Math.round(policy.minInputTokens ?? 0));
|
|
1259
|
+
if (minimum > 0 && estimatePromptTokens(request.input) < minimum) return false;
|
|
1260
|
+
return (policy.random ?? Math.random)() < rate;
|
|
1261
|
+
}
|
|
1262
|
+
async getSamplingSettings(options = {}) {
|
|
1263
|
+
const now = Date.now();
|
|
1264
|
+
if (!options.forceRefresh && this.samplingSettingsCache && this.samplingSettingsCache.expiresAtMs > now) {
|
|
1265
|
+
return {
|
|
1266
|
+
result: { status: "ACCEPTED", code: "SAMPLING_SETTINGS_CACHED" },
|
|
1267
|
+
data: this.samplingSettingsCache.settings,
|
|
1268
|
+
correlationId: options.correlationId ?? "local-cache"
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
const response = await this.requestGet(
|
|
1272
|
+
SAMPLING_SETTINGS_PATH,
|
|
1273
|
+
{
|
|
1274
|
+
signal: options.signal,
|
|
1275
|
+
headers: options.headers,
|
|
1276
|
+
retries: options.retries,
|
|
1277
|
+
correlationId: options.correlationId
|
|
1278
|
+
}
|
|
1279
|
+
);
|
|
1280
|
+
const serverCacheMs = Math.max(0, Number(response.data.cacheSeconds) || 0) * 1e3;
|
|
1281
|
+
const cacheMs = Math.min(this.samplingSettingsCacheMs, serverCacheMs);
|
|
1282
|
+
this.samplingSettingsCache = {
|
|
1283
|
+
settings: response.data,
|
|
1284
|
+
expiresAtMs: now + cacheMs
|
|
1285
|
+
};
|
|
1286
|
+
return response;
|
|
1287
|
+
}
|
|
1288
|
+
async shouldSampleAsync(request, policy) {
|
|
1289
|
+
if (policy) return this.shouldSample(request, policy);
|
|
1290
|
+
if (this.sampling === false) return false;
|
|
1291
|
+
if (this.sampling) return this.shouldSample(request, this.sampling);
|
|
1292
|
+
try {
|
|
1293
|
+
const settings = await this.getSamplingSettings();
|
|
1294
|
+
return this.shouldSample(request, settings.data);
|
|
1295
|
+
} catch {
|
|
1296
|
+
return false;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
async decideSample(request, options = {}) {
|
|
1300
|
+
const hasTokens = Number.isFinite(request.inputTokens) && Number(request.inputTokens) >= 0;
|
|
1301
|
+
const hasCharacters = Number.isFinite(request.inputCharacters) && Number(request.inputCharacters) >= 0;
|
|
1302
|
+
if (!hasTokens && !hasCharacters) {
|
|
1303
|
+
throw new UsageTapError(
|
|
1304
|
+
"USAGETAP_BAD_REQUEST",
|
|
1305
|
+
"decideSample requires inputTokens or inputCharacters"
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
return this.request(
|
|
1309
|
+
SAMPLING_DECIDE_PATH,
|
|
1310
|
+
request,
|
|
1311
|
+
options
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
async captureSample(request, options = {}) {
|
|
1315
|
+
if (!request || request.input === void 0) {
|
|
1316
|
+
throw new UsageTapError(
|
|
1317
|
+
"USAGETAP_BAD_REQUEST",
|
|
1318
|
+
"captureSample requires input"
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
if (!request.provider?.trim()) {
|
|
1322
|
+
throw new UsageTapError(
|
|
1323
|
+
"USAGETAP_BAD_REQUEST",
|
|
1324
|
+
"captureSample requires provider"
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
const sampleId = request.sampleId?.trim() || this.idempotencyGenerator();
|
|
1328
|
+
return this.request(
|
|
1329
|
+
SAMPLES_PATH,
|
|
1330
|
+
{ ...request, sampleId },
|
|
1331
|
+
{ ...options, idempotencyKey: sampleId }
|
|
1332
|
+
);
|
|
843
1333
|
}
|
|
844
1334
|
async beginCall(request, options = {}) {
|
|
845
1335
|
const idempotencyKey = request.idempotencyKey ?? request.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
|
|
1336
|
+
this.reserveRunCall(request, idempotencyKey);
|
|
1337
|
+
const apiRequest = { ...request };
|
|
1338
|
+
delete apiRequest.runId;
|
|
846
1339
|
const payload = {
|
|
847
|
-
...
|
|
1340
|
+
...apiRequest,
|
|
848
1341
|
feature: request.feature ?? this.defaultFeature,
|
|
849
1342
|
tags: this.mergeTags(request.tags)
|
|
850
1343
|
};
|
|
@@ -862,6 +1355,28 @@ var UsageTapClient = class {
|
|
|
862
1355
|
);
|
|
863
1356
|
return response;
|
|
864
1357
|
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Inspect a configured run circuit breaker without consuming another call.
|
|
1360
|
+
*/
|
|
1361
|
+
canRunContinue(request) {
|
|
1362
|
+
const identity = this.resolveRunIdentity(request);
|
|
1363
|
+
if (!identity || !this.circuitBreaker) {
|
|
1364
|
+
throw new UsageTapError(
|
|
1365
|
+
"USAGETAP_BAD_REQUEST",
|
|
1366
|
+
"canRunContinue requires circuitBreaker configuration and a non-empty runId"
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
this.expireInactiveRuns();
|
|
1370
|
+
const calls = this.circuitBreakerRuns.get(identity.key)?.calls ?? 0;
|
|
1371
|
+
return this.createCircuitBreakerDecision(identity.customerId, identity.runId, calls);
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Release local state after a workflow finishes. Returns true when state existed.
|
|
1375
|
+
*/
|
|
1376
|
+
resetRun(request) {
|
|
1377
|
+
const identity = this.resolveRunIdentity(request);
|
|
1378
|
+
return identity ? this.circuitBreakerRuns.delete(identity.key) : false;
|
|
1379
|
+
}
|
|
865
1380
|
async promptCompress(request, options = {}) {
|
|
866
1381
|
if (!request?.callId) {
|
|
867
1382
|
throw new UsageTapError(
|
|
@@ -959,6 +1474,10 @@ var UsageTapClient = class {
|
|
|
959
1474
|
usageTapCompressionMessagesEndpoint: this.usageTapCompressionMessagesEndpoint,
|
|
960
1475
|
aggressiveness: options.aggressiveness ?? this.aggressiveness,
|
|
961
1476
|
usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
|
|
1477
|
+
mode: options.mode,
|
|
1478
|
+
latencyBudgetMs: options.latencyBudgetMs,
|
|
1479
|
+
compactEmptyUserMessages: options.compactEmptyUserMessages,
|
|
1480
|
+
compactDuplicateUserTextParts: options.compactDuplicateUserTextParts,
|
|
962
1481
|
fetchImpl: this.fetchImpl,
|
|
963
1482
|
signal: options.signal,
|
|
964
1483
|
failOpen: options.failOpen
|
|
@@ -1000,11 +1519,17 @@ var UsageTapClient = class {
|
|
|
1000
1519
|
callId: request.callId,
|
|
1001
1520
|
feature: feature ?? this.defaultFeature,
|
|
1002
1521
|
tags: tags ?? this.defaultTags,
|
|
1522
|
+
providerUsed: request.providerUsed,
|
|
1003
1523
|
modelUsed: request.modelUsed,
|
|
1524
|
+
reasoningEffort: request.reasoningEffort,
|
|
1525
|
+
reasoningEffortSource: request.reasoningEffortSource,
|
|
1526
|
+
reasoningMode: request.reasoningMode,
|
|
1527
|
+
reasoningBudgetTokens: request.reasoningBudgetTokens,
|
|
1004
1528
|
metrics: {
|
|
1005
1529
|
inputTokens: request.inputTokens,
|
|
1006
1530
|
responseTokens: request.responseTokens,
|
|
1007
1531
|
cachedInputTokens: request.cachedInputTokens,
|
|
1532
|
+
cacheWriteInputTokens: request.cacheWriteInputTokens,
|
|
1008
1533
|
reasoningTokens: request.reasoningTokens,
|
|
1009
1534
|
searches: request.searches,
|
|
1010
1535
|
audioSeconds: request.audioSeconds,
|
|
@@ -1112,6 +1637,15 @@ var UsageTapClient = class {
|
|
|
1112
1637
|
meterSlot: request.meterSlot,
|
|
1113
1638
|
amount: request.amount
|
|
1114
1639
|
};
|
|
1640
|
+
if (request.customerUserId) {
|
|
1641
|
+
payload.customerUserId = request.customerUserId;
|
|
1642
|
+
}
|
|
1643
|
+
if (request.customerUserName) {
|
|
1644
|
+
payload.customerUserName = request.customerUserName;
|
|
1645
|
+
}
|
|
1646
|
+
if (request.customerUserEmail) {
|
|
1647
|
+
payload.customerUserEmail = request.customerUserEmail;
|
|
1648
|
+
}
|
|
1115
1649
|
if (request.feature) {
|
|
1116
1650
|
payload.feature = request.feature;
|
|
1117
1651
|
}
|
|
@@ -1148,6 +1682,11 @@ var UsageTapClient = class {
|
|
|
1148
1682
|
const beginPayload = idempotencyKey ? { ...beginRequest, idempotencyKey, idempotency: idempotencyKey } : { ...beginRequest };
|
|
1149
1683
|
const beginResponse = await this.beginCall(beginPayload, options);
|
|
1150
1684
|
let usage = {};
|
|
1685
|
+
const pricingMode = beginResponse.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
|
|
1686
|
+
if (pricingMode) {
|
|
1687
|
+
usage.pricingMode = pricingMode;
|
|
1688
|
+
usage.batch = pricingMode === "batch";
|
|
1689
|
+
}
|
|
1151
1690
|
const initialStripeCustomerId = typeof beginResponse.data.stripeCustomerId === "string" ? beginResponse.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
|
|
1152
1691
|
if (initialStripeCustomerId) {
|
|
1153
1692
|
usage = { ...usage, stripeCustomerId: initialStripeCustomerId };
|
|
@@ -1215,17 +1754,86 @@ var UsageTapClient = class {
|
|
|
1215
1754
|
toPromptCompressionTelemetry(result) {
|
|
1216
1755
|
return {
|
|
1217
1756
|
provider: result.provider,
|
|
1218
|
-
originalCharacters: result.originalCharacters,
|
|
1219
|
-
compressedCharacters: result.compressedCharacters,
|
|
1220
|
-
savedCharacters: result.savedCharacters,
|
|
1221
1757
|
originalTokens: result.originalTokens,
|
|
1222
1758
|
compressedTokens: result.compressedTokens,
|
|
1223
1759
|
savedTokens: result.savedTokens,
|
|
1224
1760
|
tokenSavingsRatio: result.tokenSavingsRatio,
|
|
1225
|
-
savingsRatio: result.savingsRatio,
|
|
1226
1761
|
techniques: result.techniques
|
|
1227
1762
|
};
|
|
1228
1763
|
}
|
|
1764
|
+
reserveRunCall(request, idempotencyKey) {
|
|
1765
|
+
const identity = this.resolveRunIdentity(request);
|
|
1766
|
+
if (!identity || !this.circuitBreaker) return;
|
|
1767
|
+
this.expireInactiveRuns();
|
|
1768
|
+
const now = Date.now();
|
|
1769
|
+
const state = this.circuitBreakerRuns.get(identity.key) ?? {
|
|
1770
|
+
calls: 0,
|
|
1771
|
+
reservationKeys: /* @__PURE__ */ new Set(),
|
|
1772
|
+
lastSeenAtMs: now
|
|
1773
|
+
};
|
|
1774
|
+
const reservationKey = idempotencyKey ?? this.idempotencyGenerator();
|
|
1775
|
+
state.lastSeenAtMs = now;
|
|
1776
|
+
if (state.reservationKeys.has(reservationKey)) {
|
|
1777
|
+
this.circuitBreakerRuns.set(identity.key, state);
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
const decision = this.createCircuitBreakerDecision(
|
|
1781
|
+
identity.customerId,
|
|
1782
|
+
identity.runId,
|
|
1783
|
+
state.calls
|
|
1784
|
+
);
|
|
1785
|
+
if (!decision.allowed) {
|
|
1786
|
+
throw new UsageTapError(
|
|
1787
|
+
"USAGETAP_CIRCUIT_OPEN",
|
|
1788
|
+
`Run ${identity.runId} reached its ${decision.limit}-call circuit-breaker limit`,
|
|
1789
|
+
{
|
|
1790
|
+
details: {
|
|
1791
|
+
reason: decision.reason,
|
|
1792
|
+
customerId: identity.customerId,
|
|
1793
|
+
runId: identity.runId,
|
|
1794
|
+
calls: decision.calls,
|
|
1795
|
+
limit: decision.limit,
|
|
1796
|
+
remaining: decision.remaining
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
);
|
|
1800
|
+
}
|
|
1801
|
+
state.calls += 1;
|
|
1802
|
+
state.reservationKeys.add(reservationKey);
|
|
1803
|
+
this.circuitBreakerRuns.set(identity.key, state);
|
|
1804
|
+
}
|
|
1805
|
+
resolveRunIdentity(request) {
|
|
1806
|
+
const customerId = request.customerId?.trim();
|
|
1807
|
+
const runId = request.runId?.trim();
|
|
1808
|
+
if (!customerId || !runId) return void 0;
|
|
1809
|
+
return {
|
|
1810
|
+
key: `${customerId}\0${runId}`,
|
|
1811
|
+
customerId,
|
|
1812
|
+
runId
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
createCircuitBreakerDecision(customerId, runId, calls) {
|
|
1816
|
+
const limit = this.circuitBreaker?.maxCallsPerRun ?? 0;
|
|
1817
|
+
const allowed = calls < limit;
|
|
1818
|
+
return {
|
|
1819
|
+
allowed,
|
|
1820
|
+
...allowed ? {} : { reason: "max_calls_per_run" },
|
|
1821
|
+
customerId,
|
|
1822
|
+
runId,
|
|
1823
|
+
calls,
|
|
1824
|
+
limit,
|
|
1825
|
+
remaining: Math.max(0, limit - calls)
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
expireInactiveRuns() {
|
|
1829
|
+
if (!this.circuitBreaker || this.circuitBreakerRuns.size === 0) return;
|
|
1830
|
+
const expiredBefore = Date.now() - this.circuitBreaker.runInactivityMs;
|
|
1831
|
+
for (const [key, state] of this.circuitBreakerRuns) {
|
|
1832
|
+
if (state.lastSeenAtMs < expiredBefore) {
|
|
1833
|
+
this.circuitBreakerRuns.delete(key);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1229
1837
|
async request(path, payload, options) {
|
|
1230
1838
|
const url = new URL(path, this.baseUrl).toString();
|
|
1231
1839
|
const body = payload !== void 0 ? JSON.stringify(payload) : void 0;
|
|
@@ -1413,7 +2021,7 @@ var UsageTapClient = class {
|
|
|
1413
2021
|
...this.defaultHeaders,
|
|
1414
2022
|
[SDK_HEADER]: `js/${SDK_VERSION}`,
|
|
1415
2023
|
"content-type": "application/json",
|
|
1416
|
-
accept:
|
|
2024
|
+
accept: CANONICAL_MEDIA_TYPE2
|
|
1417
2025
|
};
|
|
1418
2026
|
if (!HAS_WINDOW) {
|
|
1419
2027
|
headers["user-agent"] = `${USER_AGENT}/${SDK_VERSION}`;
|
|
@@ -1614,17 +2222,18 @@ var OpenAIPromptCompressionStats = class {
|
|
|
1614
2222
|
}
|
|
1615
2223
|
};
|
|
1616
2224
|
function createOpenAIAdapter(init) {
|
|
1617
|
-
const { client, usageTap } = init;
|
|
2225
|
+
const { client, usageTap, provider = "openai" } = init;
|
|
1618
2226
|
return {
|
|
1619
2227
|
async invoke(params) {
|
|
1620
2228
|
const result = await usageTap.withUsage(
|
|
1621
2229
|
params.begin,
|
|
1622
2230
|
async (ctx) => {
|
|
2231
|
+
ctx.setUsage({ providerUsed: provider });
|
|
1623
2232
|
const response = await params.call(client, {
|
|
1624
2233
|
hints: ctx.begin.data.vendorHints,
|
|
1625
2234
|
begin: ctx.begin
|
|
1626
2235
|
});
|
|
1627
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx);
|
|
2236
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx, provider);
|
|
1628
2237
|
return {
|
|
1629
2238
|
data: response,
|
|
1630
2239
|
begin: ctx.begin
|
|
@@ -1638,6 +2247,7 @@ function createOpenAIAdapter(init) {
|
|
|
1638
2247
|
const result = await usageTap.withUsage(
|
|
1639
2248
|
params.begin,
|
|
1640
2249
|
async (ctx) => {
|
|
2250
|
+
ctx.setUsage({ providerUsed: provider });
|
|
1641
2251
|
const { stream, onComplete } = await params.call(client, {
|
|
1642
2252
|
hints: ctx.begin.data.vendorHints,
|
|
1643
2253
|
begin: ctx.begin
|
|
@@ -1763,6 +2373,181 @@ async function pipeToResponse(stream, res, options = {}) {
|
|
|
1763
2373
|
}
|
|
1764
2374
|
}
|
|
1765
2375
|
var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
|
|
2376
|
+
function withSampling(client, options = {}) {
|
|
2377
|
+
if (!client || !options) {
|
|
2378
|
+
throw new UsageTapError(
|
|
2379
|
+
"USAGETAP_BAD_REQUEST",
|
|
2380
|
+
"withSampling requires an OpenAI-compatible client and sampling options"
|
|
2381
|
+
);
|
|
2382
|
+
}
|
|
2383
|
+
const { apiKey, usageTapClient, provider = "openai", ...policy } = options;
|
|
2384
|
+
const localPolicy = typeof policy.rate === "number" ? policy : void 0;
|
|
2385
|
+
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey, sampling: localPolicy });
|
|
2386
|
+
const wrapCreate = (create) => async (params, requestOptions) => {
|
|
2387
|
+
const { usageTap: callContextRaw, ...providerOptions } = requestOptions ?? {};
|
|
2388
|
+
const callContext = isObjectRecord(callContextRaw) ? callContextRaw : {};
|
|
2389
|
+
const streaming = params.stream === true;
|
|
2390
|
+
const decision = streaming ? Promise.resolve(false) : usageTap.shouldSampleAsync({
|
|
2391
|
+
customerId: readString(callContext.customerId),
|
|
2392
|
+
feature: readString(callContext.feature),
|
|
2393
|
+
input: params
|
|
2394
|
+
}, localPolicy);
|
|
2395
|
+
const startedAt = Date.now();
|
|
2396
|
+
try {
|
|
2397
|
+
const response = await create(
|
|
2398
|
+
params,
|
|
2399
|
+
Object.keys(providerOptions).length ? providerOptions : void 0
|
|
2400
|
+
);
|
|
2401
|
+
const selected = await decision;
|
|
2402
|
+
if (selected) {
|
|
2403
|
+
const record = isObjectRecord(response) ? response : {};
|
|
2404
|
+
await usageTap.captureSample({
|
|
2405
|
+
customerId: readString(callContext.customerId),
|
|
2406
|
+
feature: readString(callContext.feature),
|
|
2407
|
+
environment: readString(callContext.environment),
|
|
2408
|
+
tags: readStringArray(callContext.tags),
|
|
2409
|
+
provider,
|
|
2410
|
+
model: readString(record.model) ?? readString(params.model),
|
|
2411
|
+
input: params,
|
|
2412
|
+
output: response,
|
|
2413
|
+
usage: record.usage,
|
|
2414
|
+
latencyMs: Date.now() - startedAt
|
|
2415
|
+
}).catch(() => void 0);
|
|
2416
|
+
}
|
|
2417
|
+
return response;
|
|
2418
|
+
} catch (error) {
|
|
2419
|
+
const selected = await decision;
|
|
2420
|
+
if (selected) {
|
|
2421
|
+
await usageTap.captureSample({
|
|
2422
|
+
customerId: readString(callContext.customerId),
|
|
2423
|
+
feature: readString(callContext.feature),
|
|
2424
|
+
environment: readString(callContext.environment),
|
|
2425
|
+
tags: readStringArray(callContext.tags),
|
|
2426
|
+
provider,
|
|
2427
|
+
model: readString(params.model),
|
|
2428
|
+
input: params,
|
|
2429
|
+
latencyMs: Date.now() - startedAt,
|
|
2430
|
+
error: serializeSamplingError(error)
|
|
2431
|
+
}).catch(() => void 0);
|
|
2432
|
+
}
|
|
2433
|
+
throw error;
|
|
2434
|
+
}
|
|
2435
|
+
};
|
|
2436
|
+
const chat = client.chat?.completions ? new Proxy(client.chat, {
|
|
2437
|
+
get(target, prop, receiver) {
|
|
2438
|
+
if (prop !== "completions") return safeReflectGet(target, prop, receiver);
|
|
2439
|
+
const completions = target.completions;
|
|
2440
|
+
return new Proxy(completions, {
|
|
2441
|
+
get(completionTarget, completionProp, completionReceiver) {
|
|
2442
|
+
if (completionProp === "create") {
|
|
2443
|
+
return wrapCreate(
|
|
2444
|
+
completionTarget.create.bind(completionTarget)
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
return safeReflectGet(
|
|
2448
|
+
completionTarget,
|
|
2449
|
+
completionProp,
|
|
2450
|
+
completionReceiver
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
}) : void 0;
|
|
2456
|
+
const responses = typeof client.responses !== "undefined" && client.responses ? new Proxy(client.responses, {
|
|
2457
|
+
get(target, prop, receiver) {
|
|
2458
|
+
if (prop === "create") {
|
|
2459
|
+
const create = Reflect.get(target, prop, receiver);
|
|
2460
|
+
return wrapCreate(create.bind(target));
|
|
2461
|
+
}
|
|
2462
|
+
return safeReflectGet(target, prop, receiver);
|
|
2463
|
+
}
|
|
2464
|
+
}) : void 0;
|
|
2465
|
+
return new Proxy(client, {
|
|
2466
|
+
get(target, prop, receiver) {
|
|
2467
|
+
if (prop === "chat" && chat) return chat;
|
|
2468
|
+
if (prop === "responses" && responses) return responses;
|
|
2469
|
+
if (prop === "unwrap") return () => target;
|
|
2470
|
+
return safeReflectGet(target, prop, receiver);
|
|
2471
|
+
}
|
|
2472
|
+
});
|
|
2473
|
+
}
|
|
2474
|
+
function safeReflectGet(target, prop, receiver) {
|
|
2475
|
+
return Reflect.get(target, prop, receiver);
|
|
2476
|
+
}
|
|
2477
|
+
function readStringArray(value) {
|
|
2478
|
+
if (!Array.isArray(value)) return void 0;
|
|
2479
|
+
const strings = value.filter((item) => typeof item === "string");
|
|
2480
|
+
return strings.length ? strings : void 0;
|
|
2481
|
+
}
|
|
2482
|
+
function readString(value) {
|
|
2483
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2484
|
+
}
|
|
2485
|
+
function serializeSamplingError(error) {
|
|
2486
|
+
if (error instanceof Error) {
|
|
2487
|
+
return { name: error.name, message: error.message };
|
|
2488
|
+
}
|
|
2489
|
+
return { message: String(error) };
|
|
2490
|
+
}
|
|
2491
|
+
function normalizeMeteredOpenAISampling(options) {
|
|
2492
|
+
if (!options) return void 0;
|
|
2493
|
+
if (options === true) return { provider: "openai" };
|
|
2494
|
+
const { provider = "openai", ...policyFields } = options;
|
|
2495
|
+
return {
|
|
2496
|
+
provider,
|
|
2497
|
+
policy: typeof policyFields.rate === "number" ? policyFields : void 0
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
function startMeteredOpenAISampleDecision({
|
|
2501
|
+
usageTap,
|
|
2502
|
+
sampling,
|
|
2503
|
+
beginRequest,
|
|
2504
|
+
input
|
|
2505
|
+
}) {
|
|
2506
|
+
if (!sampling) return Promise.resolve(false);
|
|
2507
|
+
return usageTap.shouldSampleAsync(
|
|
2508
|
+
{
|
|
2509
|
+
customerId: beginRequest.customerId,
|
|
2510
|
+
feature: beginRequest.feature,
|
|
2511
|
+
input
|
|
2512
|
+
},
|
|
2513
|
+
sampling.policy
|
|
2514
|
+
);
|
|
2515
|
+
}
|
|
2516
|
+
async function captureMeteredOpenAISample({
|
|
2517
|
+
usageTap,
|
|
2518
|
+
sampling,
|
|
2519
|
+
decision,
|
|
2520
|
+
ctx,
|
|
2521
|
+
beginRequest,
|
|
2522
|
+
input,
|
|
2523
|
+
response,
|
|
2524
|
+
error,
|
|
2525
|
+
startedAt
|
|
2526
|
+
}) {
|
|
2527
|
+
if (!sampling) return;
|
|
2528
|
+
let selected = false;
|
|
2529
|
+
try {
|
|
2530
|
+
selected = await decision;
|
|
2531
|
+
} catch {
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
if (!selected) return;
|
|
2535
|
+
const record = isObjectRecord(response) ? response : {};
|
|
2536
|
+
await usageTap.captureSample({
|
|
2537
|
+
sampleId: ctx.begin.data.callId,
|
|
2538
|
+
callId: ctx.begin.data.callId,
|
|
2539
|
+
customerId: beginRequest.customerId,
|
|
2540
|
+
feature: beginRequest.feature,
|
|
2541
|
+
tags: beginRequest.tags,
|
|
2542
|
+
provider: sampling.provider,
|
|
2543
|
+
model: readString(record.model) ?? readString(input.model),
|
|
2544
|
+
input,
|
|
2545
|
+
...response === void 0 ? {} : { output: response },
|
|
2546
|
+
usage: record.usage,
|
|
2547
|
+
latencyMs: Date.now() - startedAt,
|
|
2548
|
+
...error === void 0 ? {} : { error: serializeSamplingError(error) }
|
|
2549
|
+
}).catch(() => void 0);
|
|
2550
|
+
}
|
|
1766
2551
|
function withCompression(client, options = {}) {
|
|
1767
2552
|
if (!client) {
|
|
1768
2553
|
throw new UsageTapError(
|
|
@@ -1773,11 +2558,13 @@ function withCompression(client, options = {}) {
|
|
|
1773
2558
|
const {
|
|
1774
2559
|
apiKey,
|
|
1775
2560
|
usageTapClient,
|
|
2561
|
+
sampling,
|
|
1776
2562
|
...compressionOverrides
|
|
1777
2563
|
} = options;
|
|
1778
2564
|
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
|
|
1779
2565
|
const compression = {
|
|
1780
2566
|
provider: "usagetap",
|
|
2567
|
+
roles: { user: { enabled: true, aggressiveness: 0.2 } },
|
|
1781
2568
|
minContextTokens: DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS,
|
|
1782
2569
|
...compressionOverrides
|
|
1783
2570
|
};
|
|
@@ -1787,7 +2574,7 @@ function withCompression(client, options = {}) {
|
|
|
1787
2574
|
usageTap,
|
|
1788
2575
|
compression
|
|
1789
2576
|
) : void 0;
|
|
1790
|
-
|
|
2577
|
+
const compressed = new Proxy(client, {
|
|
1791
2578
|
get(target, prop, receiver) {
|
|
1792
2579
|
if (prop === "chat" && proxiedChat) {
|
|
1793
2580
|
return proxiedChat;
|
|
@@ -1801,6 +2588,11 @@ function withCompression(client, options = {}) {
|
|
|
1801
2588
|
return Reflect.get(target, prop, receiver);
|
|
1802
2589
|
}
|
|
1803
2590
|
});
|
|
2591
|
+
return sampling ? withSampling(compressed, {
|
|
2592
|
+
...sampling === true ? {} : sampling,
|
|
2593
|
+
apiKey,
|
|
2594
|
+
usageTapClient
|
|
2595
|
+
}) : compressed;
|
|
1804
2596
|
}
|
|
1805
2597
|
function withMetering(client, customer) {
|
|
1806
2598
|
const config = typeof customer === "string" ? { customerId: customer } : customer;
|
|
@@ -1815,6 +2607,8 @@ function withMetering(client, customer) {
|
|
|
1815
2607
|
usageTapClient,
|
|
1816
2608
|
applyVendorHints,
|
|
1817
2609
|
promptCompression,
|
|
2610
|
+
sampling,
|
|
2611
|
+
provider,
|
|
1818
2612
|
...defaultContext
|
|
1819
2613
|
} = config;
|
|
1820
2614
|
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
|
|
@@ -1822,7 +2616,9 @@ function withMetering(client, customer) {
|
|
|
1822
2616
|
return wrapOpenAI(client, usageTap, {
|
|
1823
2617
|
defaultContext,
|
|
1824
2618
|
applyVendorHints,
|
|
1825
|
-
promptCompression: normalizedCompression
|
|
2619
|
+
promptCompression: normalizedCompression,
|
|
2620
|
+
sampling,
|
|
2621
|
+
provider
|
|
1826
2622
|
});
|
|
1827
2623
|
}
|
|
1828
2624
|
function wrapOpenAI(client, usageTap, options = {}) {
|
|
@@ -1832,6 +2628,8 @@ function wrapOpenAI(client, usageTap, options = {}) {
|
|
|
1832
2628
|
const defaultContext = options.defaultContext;
|
|
1833
2629
|
const applyVendorHints = options.applyVendorHints !== false;
|
|
1834
2630
|
const defaultPromptCompression = normalizePromptCompressionOptions(options.promptCompression);
|
|
2631
|
+
const defaultSampling = normalizeMeteredOpenAISampling(options.sampling);
|
|
2632
|
+
const provider = options.provider ?? "openai";
|
|
1835
2633
|
const promptCompressionStats = new OpenAIPromptCompressionStats();
|
|
1836
2634
|
const proxiedChat = client.chat ? createChatProxy(
|
|
1837
2635
|
client.chat,
|
|
@@ -1839,7 +2637,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
|
|
|
1839
2637
|
defaultContext,
|
|
1840
2638
|
applyVendorHints,
|
|
1841
2639
|
defaultPromptCompression,
|
|
1842
|
-
promptCompressionStats
|
|
2640
|
+
promptCompressionStats,
|
|
2641
|
+
defaultSampling,
|
|
2642
|
+
provider
|
|
1843
2643
|
) : void 0;
|
|
1844
2644
|
const proxiedResponses = typeof client.responses !== "undefined" ? createResponsesProxy(
|
|
1845
2645
|
client.responses,
|
|
@@ -1847,7 +2647,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
|
|
|
1847
2647
|
defaultContext,
|
|
1848
2648
|
applyVendorHints,
|
|
1849
2649
|
defaultPromptCompression,
|
|
1850
|
-
promptCompressionStats
|
|
2650
|
+
promptCompressionStats,
|
|
2651
|
+
defaultSampling,
|
|
2652
|
+
provider
|
|
1851
2653
|
) : void 0;
|
|
1852
2654
|
const handler = {
|
|
1853
2655
|
get(target, prop, receiver) {
|
|
@@ -1966,14 +2768,16 @@ function streamOpenAIRoute(usageTap, openai, options) {
|
|
|
1966
2768
|
});
|
|
1967
2769
|
};
|
|
1968
2770
|
}
|
|
1969
|
-
function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2771
|
+
function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
1970
2772
|
const completions = createChatCompletionsProxy(
|
|
1971
2773
|
resource.completions,
|
|
1972
2774
|
usageTap,
|
|
1973
2775
|
defaultContext,
|
|
1974
2776
|
applyVendorHints,
|
|
1975
2777
|
defaultPromptCompression,
|
|
1976
|
-
promptCompressionStats
|
|
2778
|
+
promptCompressionStats,
|
|
2779
|
+
defaultSampling,
|
|
2780
|
+
provider
|
|
1977
2781
|
);
|
|
1978
2782
|
const handler = {
|
|
1979
2783
|
get(target, prop, receiver) {
|
|
@@ -1985,7 +2789,7 @@ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, d
|
|
|
1985
2789
|
};
|
|
1986
2790
|
return new Proxy(resource, handler);
|
|
1987
2791
|
}
|
|
1988
|
-
function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2792
|
+
function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
1989
2793
|
if (!resource || typeof resource !== "object") {
|
|
1990
2794
|
return void 0;
|
|
1991
2795
|
}
|
|
@@ -2000,9 +2804,20 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
2000
2804
|
withUsage,
|
|
2001
2805
|
promptCompression
|
|
2002
2806
|
} = splitUsageOptions(options);
|
|
2003
|
-
const beginRequest =
|
|
2807
|
+
const beginRequest = withRuntimeCompressionContext(
|
|
2808
|
+
resolveBeginRequest(defaultContext, usageContext),
|
|
2809
|
+
"openai",
|
|
2810
|
+
String(params.model)
|
|
2811
|
+
);
|
|
2004
2812
|
const wantsStream = isStreamingRequest(params);
|
|
2005
2813
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2814
|
+
const sampleStartedAt = Date.now();
|
|
2815
|
+
const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
|
|
2816
|
+
usageTap,
|
|
2817
|
+
sampling: defaultSampling,
|
|
2818
|
+
beginRequest,
|
|
2819
|
+
input: params
|
|
2820
|
+
});
|
|
2006
2821
|
const hintedParams = applyVendorHints ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
2007
2822
|
const finalParams = await compressResponsesParamsForCall({
|
|
2008
2823
|
params: hintedParams,
|
|
@@ -2014,13 +2829,14 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
2014
2829
|
withUsage,
|
|
2015
2830
|
operation: "responses.create"
|
|
2016
2831
|
});
|
|
2832
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
2017
2833
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
2018
2834
|
if (wantsStream) {
|
|
2019
2835
|
const apiPromise2 = originalCreate(finalParams, request);
|
|
2020
2836
|
const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
|
|
2021
2837
|
ensureAsyncIterable(rawStream, "responses.create");
|
|
2022
2838
|
const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
|
|
2023
|
-
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
|
|
2839
|
+
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
|
|
2024
2840
|
if (usage) {
|
|
2025
2841
|
ctx.setUsage(usage);
|
|
2026
2842
|
}
|
|
@@ -2030,9 +2846,31 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
2030
2846
|
return wrappedPromise2;
|
|
2031
2847
|
}
|
|
2032
2848
|
const apiPromise = originalCreate(finalParams, request);
|
|
2033
|
-
const wrappedPromise = transformApiPromise(apiPromise, (response) => {
|
|
2034
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
|
|
2849
|
+
const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
|
|
2850
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
|
|
2851
|
+
await captureMeteredOpenAISample({
|
|
2852
|
+
usageTap,
|
|
2853
|
+
sampling: defaultSampling,
|
|
2854
|
+
decision: sampleDecision,
|
|
2855
|
+
ctx,
|
|
2856
|
+
beginRequest,
|
|
2857
|
+
input: params,
|
|
2858
|
+
response,
|
|
2859
|
+
startedAt: sampleStartedAt
|
|
2860
|
+
});
|
|
2035
2861
|
return response;
|
|
2862
|
+
}, async (error) => {
|
|
2863
|
+
await captureMeteredOpenAISample({
|
|
2864
|
+
usageTap,
|
|
2865
|
+
sampling: defaultSampling,
|
|
2866
|
+
decision: sampleDecision,
|
|
2867
|
+
ctx,
|
|
2868
|
+
beginRequest,
|
|
2869
|
+
input: params,
|
|
2870
|
+
error,
|
|
2871
|
+
startedAt: sampleStartedAt
|
|
2872
|
+
});
|
|
2873
|
+
throw error;
|
|
2036
2874
|
});
|
|
2037
2875
|
return wrappedPromise;
|
|
2038
2876
|
}, withUsage);
|
|
@@ -2047,7 +2885,7 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
2047
2885
|
};
|
|
2048
2886
|
return new Proxy(resource, handler);
|
|
2049
2887
|
}
|
|
2050
|
-
function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2888
|
+
function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
2051
2889
|
const originalCreate = resource.create.bind(resource);
|
|
2052
2890
|
const streamCandidate = resource.stream;
|
|
2053
2891
|
const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(resource) : void 0;
|
|
@@ -2058,9 +2896,20 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2058
2896
|
withUsage,
|
|
2059
2897
|
promptCompression
|
|
2060
2898
|
} = splitUsageOptions(options);
|
|
2061
|
-
const beginRequest =
|
|
2899
|
+
const beginRequest = withRuntimeCompressionContext(
|
|
2900
|
+
resolveBeginRequest(defaultContext, usageContext),
|
|
2901
|
+
"openai",
|
|
2902
|
+
String(params.model)
|
|
2903
|
+
);
|
|
2062
2904
|
const wantsStream = isStreamingRequest(params);
|
|
2063
2905
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2906
|
+
const sampleStartedAt = Date.now();
|
|
2907
|
+
const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
|
|
2908
|
+
usageTap,
|
|
2909
|
+
sampling: defaultSampling,
|
|
2910
|
+
beginRequest,
|
|
2911
|
+
input: params
|
|
2912
|
+
});
|
|
2064
2913
|
const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
2065
2914
|
const finalParams = await compressChatParamsForCall({
|
|
2066
2915
|
params: hintedParams,
|
|
@@ -2072,13 +2921,14 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2072
2921
|
withUsage,
|
|
2073
2922
|
operation: "chat.completions.create"
|
|
2074
2923
|
});
|
|
2924
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
2075
2925
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
2076
2926
|
if (wantsStream) {
|
|
2077
2927
|
const apiPromise2 = originalCreate(finalParams, request);
|
|
2078
2928
|
const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
|
|
2079
2929
|
ensureAsyncIterable(rawStream, "chat.completions.create");
|
|
2080
2930
|
const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
|
|
2081
|
-
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
|
|
2931
|
+
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
|
|
2082
2932
|
if (usage) {
|
|
2083
2933
|
ctx.setUsage(usage);
|
|
2084
2934
|
}
|
|
@@ -2088,9 +2938,31 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2088
2938
|
return wrappedPromise2;
|
|
2089
2939
|
}
|
|
2090
2940
|
const apiPromise = originalCreate(finalParams, request);
|
|
2091
|
-
const wrappedPromise = transformApiPromise(apiPromise, (response) => {
|
|
2092
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
|
|
2941
|
+
const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
|
|
2942
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
|
|
2943
|
+
await captureMeteredOpenAISample({
|
|
2944
|
+
usageTap,
|
|
2945
|
+
sampling: defaultSampling,
|
|
2946
|
+
decision: sampleDecision,
|
|
2947
|
+
ctx,
|
|
2948
|
+
beginRequest,
|
|
2949
|
+
input: params,
|
|
2950
|
+
response,
|
|
2951
|
+
startedAt: sampleStartedAt
|
|
2952
|
+
});
|
|
2093
2953
|
return response;
|
|
2954
|
+
}, async (error) => {
|
|
2955
|
+
await captureMeteredOpenAISample({
|
|
2956
|
+
usageTap,
|
|
2957
|
+
sampling: defaultSampling,
|
|
2958
|
+
decision: sampleDecision,
|
|
2959
|
+
ctx,
|
|
2960
|
+
beginRequest,
|
|
2961
|
+
input: params,
|
|
2962
|
+
error,
|
|
2963
|
+
startedAt: sampleStartedAt
|
|
2964
|
+
});
|
|
2965
|
+
throw error;
|
|
2094
2966
|
});
|
|
2095
2967
|
return wrappedPromise;
|
|
2096
2968
|
}, withUsage);
|
|
@@ -2102,7 +2974,11 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2102
2974
|
withUsage,
|
|
2103
2975
|
promptCompression
|
|
2104
2976
|
} = splitUsageOptions(options);
|
|
2105
|
-
const beginRequest =
|
|
2977
|
+
const beginRequest = withRuntimeCompressionContext(
|
|
2978
|
+
resolveBeginRequest(defaultContext, usageContext),
|
|
2979
|
+
"openai",
|
|
2980
|
+
String(params.model)
|
|
2981
|
+
);
|
|
2106
2982
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2107
2983
|
const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
2108
2984
|
const finalParams = await compressChatParamsForCall({
|
|
@@ -2115,12 +2991,17 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2115
2991
|
withUsage,
|
|
2116
2992
|
operation: "chat.completions.stream"
|
|
2117
2993
|
});
|
|
2994
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
2118
2995
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
2119
2996
|
const apiPromise = originalStream(finalParams, request);
|
|
2120
2997
|
const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
|
|
2121
2998
|
ensureAsyncIterable(rawStream, "chat.completions.stream");
|
|
2122
2999
|
const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
|
|
2123
|
-
const usage = await extractUsageFromStream(
|
|
3000
|
+
const usage = await extractUsageFromStream(
|
|
3001
|
+
rawStream,
|
|
3002
|
+
ctx.begin.data.vendorHints,
|
|
3003
|
+
provider
|
|
3004
|
+
);
|
|
2124
3005
|
if (usage) {
|
|
2125
3006
|
ctx.setUsage(usage);
|
|
2126
3007
|
}
|
|
@@ -2144,19 +3025,70 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2144
3025
|
return new Proxy(resource, handler);
|
|
2145
3026
|
}
|
|
2146
3027
|
async function compressChatParamsForCall(args) {
|
|
2147
|
-
|
|
3028
|
+
let compression = resolveEffectivePromptCompressionOptions(
|
|
2148
3029
|
args.defaultPromptCompression,
|
|
2149
3030
|
args.callPromptCompression
|
|
2150
3031
|
);
|
|
3032
|
+
const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
|
|
3033
|
+
const policyDriven = !compression && Boolean(runtimePolicy);
|
|
3034
|
+
if (policyDriven && runtimePolicy) {
|
|
3035
|
+
if (!runtimePolicy.selected) {
|
|
3036
|
+
args.ctx.setUsage({
|
|
3037
|
+
runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
|
|
3038
|
+
});
|
|
3039
|
+
return args.params;
|
|
3040
|
+
}
|
|
3041
|
+
compression = openAIPolicyCompressionOptions(runtimePolicy);
|
|
3042
|
+
if (runtimePolicy.mode === "SHADOW") {
|
|
3043
|
+
const startedAt2 = Date.now();
|
|
3044
|
+
void compressChatParams(
|
|
3045
|
+
args.params,
|
|
3046
|
+
args.usageTap,
|
|
3047
|
+
compression,
|
|
3048
|
+
args.withUsage?.signal
|
|
3049
|
+
).then((outcome2) => {
|
|
3050
|
+
args.ctx.setUsage({
|
|
3051
|
+
runtimeCompression: openAIRuntimeMeasurement({
|
|
3052
|
+
policy: runtimePolicy,
|
|
3053
|
+
telemetry: buildPromptCompressionTelemetry(outcome2.segments),
|
|
3054
|
+
latencyMs: Date.now() - startedAt2,
|
|
3055
|
+
shadow: true,
|
|
3056
|
+
originalPayload: args.params,
|
|
3057
|
+
resultPayload: outcome2.params
|
|
3058
|
+
})
|
|
3059
|
+
});
|
|
3060
|
+
}).catch(() => {
|
|
3061
|
+
args.ctx.setUsage({
|
|
3062
|
+
runtimeCompression: failedRuntimeMeasurement(
|
|
3063
|
+
runtimePolicy,
|
|
3064
|
+
Date.now() - startedAt2
|
|
3065
|
+
)
|
|
3066
|
+
});
|
|
3067
|
+
});
|
|
3068
|
+
return args.params;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
2151
3071
|
if (!compression) {
|
|
2152
3072
|
return args.params;
|
|
2153
3073
|
}
|
|
3074
|
+
const startedAt = Date.now();
|
|
2154
3075
|
const outcome = await compressChatParams(
|
|
2155
3076
|
args.params,
|
|
2156
3077
|
args.usageTap,
|
|
2157
3078
|
compression,
|
|
2158
3079
|
args.withUsage?.signal
|
|
2159
3080
|
);
|
|
3081
|
+
if (policyDriven && runtimePolicy) {
|
|
3082
|
+
const runtimeMeasurement = openAIRuntimeMeasurement({
|
|
3083
|
+
policy: runtimePolicy,
|
|
3084
|
+
telemetry: buildPromptCompressionTelemetry(outcome.segments),
|
|
3085
|
+
latencyMs: Date.now() - startedAt,
|
|
3086
|
+
originalPayload: args.params,
|
|
3087
|
+
resultPayload: outcome.params
|
|
3088
|
+
});
|
|
3089
|
+
args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
|
|
3090
|
+
if (runtimeMeasurement.decision !== "compressed") return args.params;
|
|
3091
|
+
}
|
|
2160
3092
|
await recordCompressionOutcome({
|
|
2161
3093
|
outcome,
|
|
2162
3094
|
compression,
|
|
@@ -2169,19 +3101,70 @@ async function compressChatParamsForCall(args) {
|
|
|
2169
3101
|
return outcome.params;
|
|
2170
3102
|
}
|
|
2171
3103
|
async function compressResponsesParamsForCall(args) {
|
|
2172
|
-
|
|
3104
|
+
let compression = resolveEffectivePromptCompressionOptions(
|
|
2173
3105
|
args.defaultPromptCompression,
|
|
2174
3106
|
args.callPromptCompression
|
|
2175
3107
|
);
|
|
3108
|
+
const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
|
|
3109
|
+
const policyDriven = !compression && Boolean(runtimePolicy);
|
|
3110
|
+
if (policyDriven && runtimePolicy) {
|
|
3111
|
+
if (!runtimePolicy.selected) {
|
|
3112
|
+
args.ctx.setUsage({
|
|
3113
|
+
runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
|
|
3114
|
+
});
|
|
3115
|
+
return args.params;
|
|
3116
|
+
}
|
|
3117
|
+
compression = openAIPolicyCompressionOptions(runtimePolicy);
|
|
3118
|
+
if (runtimePolicy.mode === "SHADOW") {
|
|
3119
|
+
const startedAt2 = Date.now();
|
|
3120
|
+
void compressResponsesParams(
|
|
3121
|
+
args.params,
|
|
3122
|
+
args.usageTap,
|
|
3123
|
+
compression,
|
|
3124
|
+
args.withUsage?.signal
|
|
3125
|
+
).then((outcome2) => {
|
|
3126
|
+
args.ctx.setUsage({
|
|
3127
|
+
runtimeCompression: openAIRuntimeMeasurement({
|
|
3128
|
+
policy: runtimePolicy,
|
|
3129
|
+
telemetry: buildPromptCompressionTelemetry(outcome2.segments),
|
|
3130
|
+
latencyMs: Date.now() - startedAt2,
|
|
3131
|
+
shadow: true,
|
|
3132
|
+
originalPayload: args.params,
|
|
3133
|
+
resultPayload: outcome2.params
|
|
3134
|
+
})
|
|
3135
|
+
});
|
|
3136
|
+
}).catch(() => {
|
|
3137
|
+
args.ctx.setUsage({
|
|
3138
|
+
runtimeCompression: failedRuntimeMeasurement(
|
|
3139
|
+
runtimePolicy,
|
|
3140
|
+
Date.now() - startedAt2
|
|
3141
|
+
)
|
|
3142
|
+
});
|
|
3143
|
+
});
|
|
3144
|
+
return args.params;
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
2176
3147
|
if (!compression) {
|
|
2177
3148
|
return args.params;
|
|
2178
3149
|
}
|
|
3150
|
+
const startedAt = Date.now();
|
|
2179
3151
|
const outcome = await compressResponsesParams(
|
|
2180
3152
|
args.params,
|
|
2181
3153
|
args.usageTap,
|
|
2182
3154
|
compression,
|
|
2183
3155
|
args.withUsage?.signal
|
|
2184
3156
|
);
|
|
3157
|
+
if (policyDriven && runtimePolicy) {
|
|
3158
|
+
const runtimeMeasurement = openAIRuntimeMeasurement({
|
|
3159
|
+
policy: runtimePolicy,
|
|
3160
|
+
telemetry: buildPromptCompressionTelemetry(outcome.segments),
|
|
3161
|
+
latencyMs: Date.now() - startedAt,
|
|
3162
|
+
originalPayload: args.params,
|
|
3163
|
+
resultPayload: outcome.params
|
|
3164
|
+
});
|
|
3165
|
+
args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
|
|
3166
|
+
if (runtimeMeasurement.decision !== "compressed") return args.params;
|
|
3167
|
+
}
|
|
2185
3168
|
await recordCompressionOutcome({
|
|
2186
3169
|
outcome,
|
|
2187
3170
|
compression,
|
|
@@ -2193,6 +3176,79 @@ async function compressResponsesParamsForCall(args) {
|
|
|
2193
3176
|
});
|
|
2194
3177
|
return outcome.params;
|
|
2195
3178
|
}
|
|
3179
|
+
function openAIPolicyCompressionOptions(policy) {
|
|
3180
|
+
const roles = Object.fromEntries(
|
|
3181
|
+
["system", "user", "tool", "assistant"].filter((role) => policy.snapshot.scope.messageRoles.includes(role)).map((role) => [role, true])
|
|
3182
|
+
);
|
|
3183
|
+
return {
|
|
3184
|
+
provider: "usagetap",
|
|
3185
|
+
roles,
|
|
3186
|
+
minContextTokens: policy.snapshot.scope.minimumTokens,
|
|
3187
|
+
failOpen: true
|
|
3188
|
+
};
|
|
3189
|
+
}
|
|
3190
|
+
function bypassedRuntimeMeasurement(policy) {
|
|
3191
|
+
return {
|
|
3192
|
+
policyId: policy.policyId,
|
|
3193
|
+
policyVersionId: policy.policyVersionId,
|
|
3194
|
+
policyVersion: policy.version,
|
|
3195
|
+
rolloutMode: policy.mode,
|
|
3196
|
+
decision: "bypassed",
|
|
3197
|
+
reasonCode: "not_in_rollout_sample",
|
|
3198
|
+
methodsAttempted: [],
|
|
3199
|
+
methodsApplied: [],
|
|
3200
|
+
originalTokens: 0,
|
|
3201
|
+
resultTokens: 0,
|
|
3202
|
+
savedTokens: 0,
|
|
3203
|
+
reductionPercentage: 0,
|
|
3204
|
+
compressionLatencyMs: 0,
|
|
3205
|
+
compressionComputeCostUsd: 0,
|
|
3206
|
+
financialsEstimated: true
|
|
3207
|
+
};
|
|
3208
|
+
}
|
|
3209
|
+
function failedRuntimeMeasurement(policy, latencyMs) {
|
|
3210
|
+
return {
|
|
3211
|
+
...bypassedRuntimeMeasurement(policy),
|
|
3212
|
+
decision: "fallback",
|
|
3213
|
+
reasonCode: "transformation_failed",
|
|
3214
|
+
methodsAttempted: ["deterministic"],
|
|
3215
|
+
compressionLatencyMs: latencyMs
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
3218
|
+
function openAIRuntimeMeasurement(input) {
|
|
3219
|
+
const telemetry = input.telemetry;
|
|
3220
|
+
const originalTokens = telemetry?.originalTokens ?? estimatePromptTokens(JSON.stringify(input.originalPayload));
|
|
3221
|
+
const resultTokens = telemetry?.compressedTokens ?? originalTokens;
|
|
3222
|
+
const savedTokens = Math.max(0, telemetry?.savedTokens ?? 0);
|
|
3223
|
+
const reductionPercentage = originalTokens > 0 ? savedTokens / originalTokens * 100 : 0;
|
|
3224
|
+
const belowMinimumSize = originalTokens < input.policy.snapshot.scope.minimumTokens;
|
|
3225
|
+
const latencyExceeded = input.latencyMs > input.policy.snapshot.gates.maximumLatencyMs;
|
|
3226
|
+
const belowSavings = savedTokens < input.policy.snapshot.gates.minimumTokensRemoved || reductionPercentage < input.policy.snapshot.gates.minimumReductionPercentage;
|
|
3227
|
+
const accepted = !belowMinimumSize && !latencyExceeded && !belowSavings;
|
|
3228
|
+
const deepText = input.policy.snapshot.methods.deepText.enabled && !input.policy.snapshot.rollout.deterministicOnly && (input.policy.mode !== "CANARY" || input.policy.snapshot.rollout.deepTextCanaryAllowed);
|
|
3229
|
+
const reasonCode = belowMinimumSize ? "below_minimum_size" : latencyExceeded ? "latency_budget_exceeded" : belowSavings ? "below_minimum_savings" : void 0;
|
|
3230
|
+
const measurement = {
|
|
3231
|
+
policyId: input.policy.policyId,
|
|
3232
|
+
policyVersionId: input.policy.policyVersionId,
|
|
3233
|
+
policyVersion: input.policy.version,
|
|
3234
|
+
rolloutMode: input.policy.mode,
|
|
3235
|
+
decision: accepted ? input.shadow ? "bypassed" : "compressed" : latencyExceeded ? "fallback" : "skipped",
|
|
3236
|
+
...reasonCode ? { reasonCode } : {},
|
|
3237
|
+
methodsAttempted: [
|
|
3238
|
+
"deterministic",
|
|
3239
|
+
...deepText ? ["deep_text"] : []
|
|
3240
|
+
],
|
|
3241
|
+
methodsApplied: accepted ? ["deterministic"] : [],
|
|
3242
|
+
originalTokens,
|
|
3243
|
+
resultTokens: accepted ? resultTokens : originalTokens,
|
|
3244
|
+
savedTokens: accepted ? savedTokens : 0,
|
|
3245
|
+
reductionPercentage: accepted ? reductionPercentage : 0,
|
|
3246
|
+
compressionLatencyMs: input.latencyMs,
|
|
3247
|
+
compressionComputeCostUsd: 0,
|
|
3248
|
+
financialsEstimated: true
|
|
3249
|
+
};
|
|
3250
|
+
return measurement;
|
|
3251
|
+
}
|
|
2196
3252
|
async function recordCompressionOutcome(args) {
|
|
2197
3253
|
const telemetry = buildPromptCompressionTelemetry(args.outcome.segments);
|
|
2198
3254
|
if (!telemetry) {
|
|
@@ -2246,6 +3302,10 @@ async function compressChatParams(params, usageTap, compression, signal) {
|
|
|
2246
3302
|
const result = await usageTap.compressPromptMessages(source, {
|
|
2247
3303
|
provider: "usagetap",
|
|
2248
3304
|
failOpen: compression.failOpen,
|
|
3305
|
+
mode: compression.mode,
|
|
3306
|
+
latencyBudgetMs: compression.latencyBudgetMs,
|
|
3307
|
+
compactEmptyUserMessages: compression.compactEmptyUserMessages,
|
|
3308
|
+
compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
|
|
2249
3309
|
aggressiveness: resolveMessageEndpointAggressiveness(compression),
|
|
2250
3310
|
signal
|
|
2251
3311
|
});
|
|
@@ -2711,23 +3771,53 @@ function resolveBeginRequest(defaults, override) {
|
|
|
2711
3771
|
}
|
|
2712
3772
|
const tags = mergeTags(base.tags, current.tags);
|
|
2713
3773
|
const begin = { customerId };
|
|
3774
|
+
const runtimeCompressionContext = current.runtimeCompressionContext ?? base.runtimeCompressionContext;
|
|
3775
|
+
if (runtimeCompressionContext) {
|
|
3776
|
+
begin.runtimeCompressionContext = runtimeCompressionContext;
|
|
3777
|
+
}
|
|
2714
3778
|
const requested = current.requested ?? base.requested;
|
|
2715
3779
|
if (requested) begin.requested = requested;
|
|
2716
3780
|
const feature = current.feature ?? base.feature;
|
|
2717
3781
|
if (feature) begin.feature = feature;
|
|
3782
|
+
const runId = current.runId ?? base.runId;
|
|
3783
|
+
if (runId) begin.runId = runId;
|
|
2718
3784
|
const idempotency = current.idempotency ?? base.idempotency;
|
|
2719
3785
|
if (idempotency) begin.idempotency = idempotency;
|
|
2720
3786
|
const customerName = current.customerName ?? base.customerName;
|
|
2721
3787
|
if (customerName) begin.customerName = customerName;
|
|
2722
3788
|
const customerEmail = current.customerEmail ?? base.customerEmail;
|
|
2723
3789
|
if (customerEmail) begin.customerEmail = customerEmail;
|
|
3790
|
+
const customerUserId = current.customerUserId ?? base.customerUserId;
|
|
3791
|
+
if (customerUserId) begin.customerUserId = customerUserId;
|
|
3792
|
+
const customerUserName = current.customerUserName ?? base.customerUserName;
|
|
3793
|
+
if (customerUserName) begin.customerUserName = customerUserName;
|
|
3794
|
+
const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
|
|
3795
|
+
if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
|
|
3796
|
+
const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
|
|
3797
|
+
if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
|
|
3798
|
+
const holdUsd = current.holdUsd ?? base.holdUsd;
|
|
3799
|
+
if (typeof holdUsd === "number") begin.holdUsd = holdUsd;
|
|
3800
|
+
const batch = current.batch ?? base.batch;
|
|
3801
|
+
if (typeof batch === "boolean") begin.batch = batch;
|
|
3802
|
+
const pricingMode = current.pricingMode ?? base.pricingMode;
|
|
3803
|
+
if (pricingMode) begin.pricingMode = pricingMode;
|
|
2724
3804
|
if (tags?.length) {
|
|
2725
3805
|
begin.tags = tags;
|
|
2726
3806
|
}
|
|
2727
3807
|
return begin;
|
|
2728
3808
|
}
|
|
2729
|
-
function
|
|
2730
|
-
|
|
3809
|
+
function withRuntimeCompressionContext(begin, provider, model) {
|
|
3810
|
+
return {
|
|
3811
|
+
...begin,
|
|
3812
|
+
runtimeCompressionContext: {
|
|
3813
|
+
...begin.runtimeCompressionContext ?? {},
|
|
3814
|
+
provider,
|
|
3815
|
+
model
|
|
3816
|
+
}
|
|
3817
|
+
};
|
|
3818
|
+
}
|
|
3819
|
+
function transformApiPromise(apiPromise, onResolve, onReject) {
|
|
3820
|
+
const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
|
|
2731
3821
|
if (isObjectRecord(apiPromise)) {
|
|
2732
3822
|
const proto = Object.getPrototypeOf(apiPromise);
|
|
2733
3823
|
if (proto) {
|
|
@@ -2862,12 +3952,12 @@ function applyResponsesVendorHints(params, hints) {
|
|
|
2862
3952
|
}
|
|
2863
3953
|
return next;
|
|
2864
3954
|
}
|
|
2865
|
-
async function extractUsageFromStream(stream, hints) {
|
|
3955
|
+
async function extractUsageFromStream(stream, hints, provider = "openai") {
|
|
2866
3956
|
const finalPayload = await resolveStreamFinalPayload(stream);
|
|
2867
3957
|
if (!finalPayload) {
|
|
2868
3958
|
return void 0;
|
|
2869
3959
|
}
|
|
2870
|
-
return inferUsageFromResponse(finalPayload, hints);
|
|
3960
|
+
return inferUsageFromResponse(finalPayload, hints, provider);
|
|
2871
3961
|
}
|
|
2872
3962
|
async function resolveStreamFinalPayload(stream) {
|
|
2873
3963
|
if (!stream || typeof stream !== "object") {
|
|
@@ -2940,14 +4030,14 @@ function setHeaderIfPossible(res, key, value) {
|
|
|
2940
4030
|
res.setHeader(key, value);
|
|
2941
4031
|
}
|
|
2942
4032
|
}
|
|
2943
|
-
function tryInferUsage(response, hints, extractor, ctx) {
|
|
4033
|
+
function tryInferUsage(response, hints, extractor, ctx, provider = "openai") {
|
|
2944
4034
|
const explicit = extractor?.(response);
|
|
2945
|
-
const inferred = explicit ?? inferUsageFromResponse(response, hints);
|
|
4035
|
+
const inferred = explicit ?? inferUsageFromResponse(response, hints, provider);
|
|
2946
4036
|
if (inferred) {
|
|
2947
4037
|
ctx.setUsage(inferred);
|
|
2948
4038
|
}
|
|
2949
4039
|
}
|
|
2950
|
-
function inferUsageFromResponse(response, hints) {
|
|
4040
|
+
function inferUsageFromResponse(response, hints, provider = "openai") {
|
|
2951
4041
|
if (!response || typeof response !== "object") {
|
|
2952
4042
|
return void 0;
|
|
2953
4043
|
}
|
|
@@ -2955,12 +4045,41 @@ function inferUsageFromResponse(response, hints) {
|
|
|
2955
4045
|
if (!candidate.usage) {
|
|
2956
4046
|
return void 0;
|
|
2957
4047
|
}
|
|
2958
|
-
const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
|
|
4048
|
+
const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
|
|
4049
|
+
const responseEffort = normalizeExecutionReasoningEffort(
|
|
4050
|
+
candidate.reasoning?.effort
|
|
4051
|
+
);
|
|
2959
4052
|
return {
|
|
4053
|
+
providerUsed: provider,
|
|
2960
4054
|
modelUsed: candidate.model ?? hints?.preferredModel,
|
|
2961
|
-
inputTokens: candidate.usage.prompt_tokens,
|
|
2962
|
-
responseTokens: candidate.usage.completion_tokens,
|
|
2963
|
-
cachedInputTokens
|
|
4055
|
+
inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
|
|
4056
|
+
responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
|
|
4057
|
+
cachedInputTokens,
|
|
4058
|
+
reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
|
|
4059
|
+
...responseEffort ? {
|
|
4060
|
+
reasoningEffort: responseEffort,
|
|
4061
|
+
reasoningEffortSource: "provider_response"
|
|
4062
|
+
} : {},
|
|
4063
|
+
...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {}
|
|
4064
|
+
};
|
|
4065
|
+
}
|
|
4066
|
+
function normalizeExecutionReasoningEffort(value) {
|
|
4067
|
+
return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
|
|
4068
|
+
}
|
|
4069
|
+
function openAIRequestExecutionMetadata(params, provider) {
|
|
4070
|
+
const record = params && typeof params === "object" ? params : {};
|
|
4071
|
+
const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
|
|
4072
|
+
const effort = normalizeExecutionReasoningEffort(
|
|
4073
|
+
record.reasoning_effort ?? reasoning?.effort ?? record.thinking_level
|
|
4074
|
+
);
|
|
4075
|
+
const mode = typeof reasoning?.type === "string" ? reasoning.type : typeof reasoning?.mode === "string" ? reasoning.mode : void 0;
|
|
4076
|
+
const rawBudget = reasoning?.budget_tokens ?? record.thinking_budget ?? record.thinking_budget_tokens;
|
|
4077
|
+
const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
|
|
4078
|
+
return {
|
|
4079
|
+
providerUsed: provider,
|
|
4080
|
+
...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
|
|
4081
|
+
...mode ? { reasoningMode: mode } : {},
|
|
4082
|
+
...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
|
|
2964
4083
|
};
|
|
2965
4084
|
}
|
|
2966
4085
|
function wrapStreamForUsageTap(source, finalize, ctx) {
|
|
@@ -3060,6 +4179,6 @@ function isIteratorResult(value) {
|
|
|
3060
4179
|
return isObjectRecord(value) && "done" in value;
|
|
3061
4180
|
}
|
|
3062
4181
|
|
|
3063
|
-
export { OpenAIPromptCompressionStats, createOpenAIAdapter, pipeToResponse, streamOpenAIRoute, toNextResponse, withCompression, withMetering, wrapOpenAI };
|
|
4182
|
+
export { OpenAIPromptCompressionStats, createOpenAIAdapter, pipeToResponse, streamOpenAIRoute, toNextResponse, withCompression, withMetering, withSampling, wrapOpenAI };
|
|
3064
4183
|
//# sourceMappingURL=openai.mjs.map
|
|
3065
4184
|
//# sourceMappingURL=openai.mjs.map
|