@usagetap/sdk 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -24
- package/dist/adapters/anthropic.cjs +990 -24
- package/dist/adapters/anthropic.cjs.map +1 -1
- package/dist/adapters/anthropic.d.cts +45 -3
- package/dist/adapters/anthropic.d.ts +45 -3
- package/dist/adapters/anthropic.mjs +990 -25
- package/dist/adapters/anthropic.mjs.map +1 -1
- package/dist/adapters/openai.cjs +1164 -44
- package/dist/adapters/openai.cjs.map +1 -1
- package/dist/adapters/openai.d.cts +46 -3
- package/dist/adapters/openai.d.ts +46 -3
- package/dist/adapters/openai.mjs +1164 -45
- package/dist/adapters/openai.mjs.map +1 -1
- package/dist/adapters/openrouter.cjs +3899 -22
- package/dist/adapters/openrouter.cjs.map +1 -1
- package/dist/adapters/openrouter.d.cts +6 -3
- package/dist/adapters/openrouter.d.ts +6 -3
- package/dist/adapters/openrouter.mjs +3897 -23
- package/dist/adapters/openrouter.mjs.map +1 -1
- package/dist/anthropic/index.cjs +990 -24
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.d.cts +2 -2
- package/dist/anthropic/index.d.ts +2 -2
- package/dist/anthropic/index.mjs +990 -25
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/client-CExQ8e1T.d.cts +1225 -0
- package/dist/client-CExQ8e1T.d.ts +1225 -0
- package/dist/express/index.cjs +421 -29
- package/dist/express/index.cjs.map +1 -1
- package/dist/express/index.d.cts +2 -2
- package/dist/express/index.d.ts +2 -2
- package/dist/express/index.mjs +421 -29
- package/dist/express/index.mjs.map +1 -1
- package/dist/index.cjs +680 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.mjs +680 -15
- package/dist/index.mjs.map +1 -1
- package/dist/openai/index.cjs +1165 -45
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.d.cts +2 -2
- package/dist/openai/index.d.ts +2 -2
- package/dist/openai/index.mjs +1165 -46
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openrouter/index.cjs +1182 -47
- package/dist/openrouter/index.cjs.map +1 -1
- package/dist/openrouter/index.d.cts +3 -3
- package/dist/openrouter/index.d.ts +3 -3
- package/dist/openrouter/index.mjs +1180 -46
- package/dist/openrouter/index.mjs.map +1 -1
- package/dist/react/index.cjs +19 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +17 -4
- package/dist/react/index.d.ts +17 -4
- package/dist/react/index.mjs +19 -1
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/client-BD8O2J8Z.d.cts +0 -668
- package/dist/client-BD8O2J8Z.d.ts +0 -668
package/dist/adapters/openai.cjs
CHANGED
|
@@ -242,6 +242,11 @@ async function compressMessagesWithUsageTap(options) {
|
|
|
242
242
|
aggressiveness,
|
|
243
243
|
"UsageTap prompt message compression"
|
|
244
244
|
);
|
|
245
|
+
if (options.latencyBudgetMs !== void 0 && (!Number.isFinite(options.latencyBudgetMs) || options.latencyBudgetMs < 0)) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
"UsageTap prompt message compression latencyBudgetMs must be a non-negative number"
|
|
248
|
+
);
|
|
249
|
+
}
|
|
245
250
|
const original = stableStringifyInput(options.input);
|
|
246
251
|
const headers = {
|
|
247
252
|
"content-type": "application/json"
|
|
@@ -256,7 +261,15 @@ async function compressMessagesWithUsageTap(options) {
|
|
|
256
261
|
headers,
|
|
257
262
|
body: JSON.stringify({
|
|
258
263
|
...cloneInputRecord(options.input),
|
|
259
|
-
compression_settings: {
|
|
264
|
+
compression_settings: {
|
|
265
|
+
aggressiveness,
|
|
266
|
+
...options.mode === void 0 ? {} : { mode: options.mode },
|
|
267
|
+
...options.latencyBudgetMs === void 0 ? {} : { latency_budget_ms: options.latencyBudgetMs },
|
|
268
|
+
...options.compactEmptyUserMessages === void 0 ? {} : { compact_empty_user_messages: options.compactEmptyUserMessages },
|
|
269
|
+
...options.compactDuplicateUserTextParts === void 0 ? {} : {
|
|
270
|
+
compact_duplicate_user_text_parts: options.compactDuplicateUserTextParts
|
|
271
|
+
}
|
|
272
|
+
}
|
|
260
273
|
}),
|
|
261
274
|
signal: options.signal
|
|
262
275
|
}
|
|
@@ -752,10 +765,358 @@ function scalarToToon(value) {
|
|
|
752
765
|
return JSON.stringify(text);
|
|
753
766
|
}
|
|
754
767
|
|
|
768
|
+
// src/resources.ts
|
|
769
|
+
var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
|
|
770
|
+
var DEFAULT_GATEWAY_BASE_URL = "https://gateway.usagetap.com";
|
|
771
|
+
function normalizedBaseUrl(value) {
|
|
772
|
+
return `${value.replace(/\/+$/, "")}/`;
|
|
773
|
+
}
|
|
774
|
+
function errorMessage(payload, status) {
|
|
775
|
+
if (payload && typeof payload === "object") {
|
|
776
|
+
const record = payload;
|
|
777
|
+
const error = record.error;
|
|
778
|
+
if (error && typeof error === "object") {
|
|
779
|
+
const message2 = error.message;
|
|
780
|
+
if (typeof message2 === "string" && message2) return message2;
|
|
781
|
+
}
|
|
782
|
+
const message = record.message;
|
|
783
|
+
if (typeof message === "string" && message) return message;
|
|
784
|
+
}
|
|
785
|
+
return `UsageTap request failed with HTTP ${status}`;
|
|
786
|
+
}
|
|
787
|
+
function errorCode(status) {
|
|
788
|
+
if (status === 401 || status === 403) return "USAGETAP_AUTH_ERROR";
|
|
789
|
+
if (status === 429) return "USAGETAP_RATE_LIMITED";
|
|
790
|
+
if (status >= 500) return "USAGETAP_SERVER_ERROR";
|
|
791
|
+
return "USAGETAP_BAD_REQUEST";
|
|
792
|
+
}
|
|
793
|
+
var ResourceTransport = class {
|
|
794
|
+
baseUrl;
|
|
795
|
+
apiKey;
|
|
796
|
+
fetchImpl;
|
|
797
|
+
defaultHeaders;
|
|
798
|
+
sdkVersion;
|
|
799
|
+
constructor(baseUrl, config) {
|
|
800
|
+
this.baseUrl = normalizedBaseUrl(baseUrl);
|
|
801
|
+
this.apiKey = config.apiKey;
|
|
802
|
+
this.fetchImpl = config.fetchImpl;
|
|
803
|
+
this.defaultHeaders = config.headers ?? {};
|
|
804
|
+
this.sdkVersion = config.sdkVersion;
|
|
805
|
+
}
|
|
806
|
+
async request(request) {
|
|
807
|
+
const body = request.body === void 0 ? void 0 : JSON.stringify(request.body);
|
|
808
|
+
const headers = {
|
|
809
|
+
...this.defaultHeaders,
|
|
810
|
+
accept: request.response === "data" ? CANONICAL_MEDIA_TYPE : request.response === "ndjson" ? "application/x-ndjson" : "application/json",
|
|
811
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
812
|
+
"x-usage-sdk": `js/${this.sdkVersion}`,
|
|
813
|
+
...body ? { "content-type": "application/json" } : {},
|
|
814
|
+
...request.options?.idempotencyKey ? { "idempotency-key": request.options.idempotencyKey } : {},
|
|
815
|
+
...request.options?.headers
|
|
816
|
+
};
|
|
817
|
+
let response;
|
|
818
|
+
try {
|
|
819
|
+
response = await this.fetchImpl(
|
|
820
|
+
new URL(request.path.replace(/^\/+/, ""), this.baseUrl),
|
|
821
|
+
{
|
|
822
|
+
method: request.method,
|
|
823
|
+
headers,
|
|
824
|
+
body,
|
|
825
|
+
signal: request.options?.signal
|
|
826
|
+
}
|
|
827
|
+
);
|
|
828
|
+
} catch (error) {
|
|
829
|
+
throw new UsageTapError(
|
|
830
|
+
"USAGETAP_NETWORK_ERROR",
|
|
831
|
+
"Failed to reach UsageTap",
|
|
832
|
+
{ retryable: true, cause: error }
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
const requestId = response.headers.get("x-request-id") ?? response.headers.get("x-usage-correlation-id") ?? void 0;
|
|
836
|
+
const text = await response.text();
|
|
837
|
+
let payload;
|
|
838
|
+
if (text && request.response !== "ndjson") {
|
|
839
|
+
try {
|
|
840
|
+
payload = JSON.parse(text);
|
|
841
|
+
} catch (error) {
|
|
842
|
+
throw new UsageTapError(
|
|
843
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
844
|
+
"UsageTap returned invalid JSON",
|
|
845
|
+
{ status: response.status, correlationId: requestId, cause: error }
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (!response.ok) {
|
|
850
|
+
throw new UsageTapError(
|
|
851
|
+
errorCode(response.status),
|
|
852
|
+
errorMessage(payload, response.status),
|
|
853
|
+
{
|
|
854
|
+
status: response.status,
|
|
855
|
+
retryable: response.status === 429 || response.status >= 500,
|
|
856
|
+
correlationId: requestId,
|
|
857
|
+
details: payload && typeof payload === "object" ? payload : void 0
|
|
858
|
+
}
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
if (request.response === "ndjson") {
|
|
862
|
+
if (!text.trim()) return [];
|
|
863
|
+
try {
|
|
864
|
+
return text.trim().split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
865
|
+
} catch (error) {
|
|
866
|
+
throw new UsageTapError(
|
|
867
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
868
|
+
"UsageTap returned invalid NDJSON",
|
|
869
|
+
{ status: response.status, correlationId: requestId, cause: error }
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (request.response === "data") {
|
|
874
|
+
if (!payload || typeof payload !== "object" || !("data" in payload)) {
|
|
875
|
+
throw new UsageTapError(
|
|
876
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
877
|
+
"UsageTap response missing data",
|
|
878
|
+
{ status: response.status, correlationId: requestId }
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
return payload.data;
|
|
882
|
+
}
|
|
883
|
+
if (payload === void 0) {
|
|
884
|
+
throw new UsageTapError(
|
|
885
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
886
|
+
"UsageTap response was empty",
|
|
887
|
+
{ status: response.status, correlationId: requestId }
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
return payload;
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
function resourceId(value, keys, label) {
|
|
894
|
+
const id = typeof value === "string" ? value : keys.map((key) => value[key]).find((candidate) => Boolean(candidate?.trim()));
|
|
895
|
+
if (!id?.trim()) {
|
|
896
|
+
throw new UsageTapError(
|
|
897
|
+
"USAGETAP_BAD_REQUEST",
|
|
898
|
+
`${label} requires a non-empty ID`
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
return id.trim();
|
|
902
|
+
}
|
|
903
|
+
function terminalSummary(status) {
|
|
904
|
+
return status === "COMPLETE" || status === "FAILED";
|
|
905
|
+
}
|
|
906
|
+
function terminalGatewayBatch(status) {
|
|
907
|
+
return ["completed", "failed", "expired", "cancelled"].includes(status);
|
|
908
|
+
}
|
|
909
|
+
function validateWaitOptions(options) {
|
|
910
|
+
const pollIntervalMs = options.pollIntervalMs ?? 1500;
|
|
911
|
+
const timeoutMs = options.timeoutMs ?? 3 * 6e4;
|
|
912
|
+
if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 0) {
|
|
913
|
+
throw new UsageTapError(
|
|
914
|
+
"USAGETAP_BAD_REQUEST",
|
|
915
|
+
"pollIntervalMs must be a non-negative number"
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
|
|
919
|
+
throw new UsageTapError(
|
|
920
|
+
"USAGETAP_BAD_REQUEST",
|
|
921
|
+
"timeoutMs must be a positive number"
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
return { pollIntervalMs, timeoutMs };
|
|
925
|
+
}
|
|
926
|
+
var SummarizationResource = class {
|
|
927
|
+
summaries;
|
|
928
|
+
batches;
|
|
929
|
+
profiles;
|
|
930
|
+
measurements;
|
|
931
|
+
transport;
|
|
932
|
+
constructor(config) {
|
|
933
|
+
this.transport = new ResourceTransport(config.apiBaseUrl, config);
|
|
934
|
+
this.summaries = {
|
|
935
|
+
create: (params, options) => this.transport.request({
|
|
936
|
+
method: "POST",
|
|
937
|
+
path: "/v1/compression/summaries",
|
|
938
|
+
body: params,
|
|
939
|
+
options,
|
|
940
|
+
response: "data"
|
|
941
|
+
}),
|
|
942
|
+
retrieve: (jobId, options) => this.transport.request({
|
|
943
|
+
method: "GET",
|
|
944
|
+
path: `/v1/compression/jobs/${encodeURIComponent(
|
|
945
|
+
resourceId(jobId, ["jobId"], "summaries.retrieve")
|
|
946
|
+
)}`,
|
|
947
|
+
options,
|
|
948
|
+
response: "data"
|
|
949
|
+
}),
|
|
950
|
+
wait: (job, options) => this.waitForSummary(job, options)
|
|
951
|
+
};
|
|
952
|
+
this.batches = {
|
|
953
|
+
create: (params, options) => this.transport.request({
|
|
954
|
+
method: "POST",
|
|
955
|
+
path: "/v1/compression/batches",
|
|
956
|
+
body: params,
|
|
957
|
+
options,
|
|
958
|
+
response: "data"
|
|
959
|
+
}),
|
|
960
|
+
retrieve: (batchId, options) => this.transport.request({
|
|
961
|
+
method: "GET",
|
|
962
|
+
path: `/v1/compression/batches/${encodeURIComponent(
|
|
963
|
+
resourceId(batchId, ["batchId"], "summarization.batches.retrieve")
|
|
964
|
+
)}`,
|
|
965
|
+
options,
|
|
966
|
+
response: "data"
|
|
967
|
+
}),
|
|
968
|
+
wait: (batch, options) => this.waitForBatch(batch, options)
|
|
969
|
+
};
|
|
970
|
+
this.profiles = {
|
|
971
|
+
retrieve: (profile, options) => this.transport.request({
|
|
972
|
+
method: "GET",
|
|
973
|
+
path: `/v1/compression/profiles/${encodeURIComponent(
|
|
974
|
+
resourceId(profile, [], "summarization.profiles.retrieve")
|
|
975
|
+
)}`,
|
|
976
|
+
options,
|
|
977
|
+
response: "data"
|
|
978
|
+
})
|
|
979
|
+
};
|
|
980
|
+
this.measurements = {
|
|
981
|
+
create: (params, options) => this.transport.request({
|
|
982
|
+
method: "POST",
|
|
983
|
+
path: "/v1/compression/measurements",
|
|
984
|
+
body: params,
|
|
985
|
+
options,
|
|
986
|
+
response: "data"
|
|
987
|
+
})
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
async waitForSummary(value, options = {}) {
|
|
991
|
+
const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
|
|
992
|
+
const deadline = Date.now() + timeoutMs;
|
|
993
|
+
let job = typeof value === "string" ? await this.summaries.retrieve(value, options) : value;
|
|
994
|
+
while (!terminalSummary(job.status)) {
|
|
995
|
+
if (Date.now() >= deadline) {
|
|
996
|
+
throw new UsageTapError(
|
|
997
|
+
"USAGETAP_RETRY_EXHAUSTED",
|
|
998
|
+
`Summarization job ${job.jobId} did not finish before timeout`,
|
|
999
|
+
{ retryable: true }
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
await sleep(pollIntervalMs, options.signal);
|
|
1003
|
+
job = await this.summaries.retrieve(job.jobId, options);
|
|
1004
|
+
}
|
|
1005
|
+
return job;
|
|
1006
|
+
}
|
|
1007
|
+
async waitForBatch(value, options = {}) {
|
|
1008
|
+
const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
|
|
1009
|
+
const deadline = Date.now() + timeoutMs;
|
|
1010
|
+
let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
|
|
1011
|
+
while (!terminalSummary(batch.status)) {
|
|
1012
|
+
if (Date.now() >= deadline) {
|
|
1013
|
+
throw new UsageTapError(
|
|
1014
|
+
"USAGETAP_RETRY_EXHAUSTED",
|
|
1015
|
+
`Summarization batch ${batch.batchId} did not finish before timeout`,
|
|
1016
|
+
{ retryable: true }
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
await sleep(pollIntervalMs, options.signal);
|
|
1020
|
+
batch = await this.batches.retrieve(batch.batchId, options);
|
|
1021
|
+
}
|
|
1022
|
+
return batch;
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
var GatewayResource = class {
|
|
1026
|
+
chat;
|
|
1027
|
+
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.models = {
|
|
1049
|
+
list: (options) => this.transport.request({
|
|
1050
|
+
method: "GET",
|
|
1051
|
+
path: "/v1/models",
|
|
1052
|
+
options,
|
|
1053
|
+
response: "json"
|
|
1054
|
+
})
|
|
1055
|
+
};
|
|
1056
|
+
this.batches = {
|
|
1057
|
+
create: (params, options = {}) => this.transport.request({
|
|
1058
|
+
method: "POST",
|
|
1059
|
+
path: "/v1/batches",
|
|
1060
|
+
body: params,
|
|
1061
|
+
options: {
|
|
1062
|
+
...options,
|
|
1063
|
+
idempotencyKey: options.idempotencyKey ?? this.idempotencyGenerator()
|
|
1064
|
+
},
|
|
1065
|
+
response: "json"
|
|
1066
|
+
}),
|
|
1067
|
+
retrieve: (batchId, options) => this.transport.request({
|
|
1068
|
+
method: "GET",
|
|
1069
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1070
|
+
resourceId(batchId, ["id"], "gateway.batches.retrieve")
|
|
1071
|
+
)}`,
|
|
1072
|
+
options,
|
|
1073
|
+
response: "json"
|
|
1074
|
+
}),
|
|
1075
|
+
wait: (batch, options) => this.waitForBatch(batch, options),
|
|
1076
|
+
cancel: (batchId, options) => this.transport.request({
|
|
1077
|
+
method: "POST",
|
|
1078
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1079
|
+
resourceId(batchId, ["id"], "gateway.batches.cancel")
|
|
1080
|
+
)}/cancel`,
|
|
1081
|
+
options,
|
|
1082
|
+
response: "json"
|
|
1083
|
+
}),
|
|
1084
|
+
results: (batchId, options) => this.transport.request({
|
|
1085
|
+
method: "GET",
|
|
1086
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1087
|
+
resourceId(batchId, ["id"], "gateway.batches.results")
|
|
1088
|
+
)}/results`,
|
|
1089
|
+
options,
|
|
1090
|
+
response: "ndjson"
|
|
1091
|
+
})
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
async waitForBatch(value, options = {}) {
|
|
1095
|
+
const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
|
|
1096
|
+
const deadline = Date.now() + timeoutMs;
|
|
1097
|
+
let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
|
|
1098
|
+
while (!terminalGatewayBatch(batch.status)) {
|
|
1099
|
+
if (Date.now() >= deadline) {
|
|
1100
|
+
throw new UsageTapError(
|
|
1101
|
+
"USAGETAP_RETRY_EXHAUSTED",
|
|
1102
|
+
`Gateway batch ${batch.id} did not finish before timeout`,
|
|
1103
|
+
{ retryable: true }
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
await sleep(pollIntervalMs, options.signal);
|
|
1107
|
+
batch = await this.batches.retrieve(batch.id, options);
|
|
1108
|
+
}
|
|
1109
|
+
return batch;
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
|
|
755
1113
|
// src/client.ts
|
|
756
1114
|
var CALL_BEGIN_PATH = "call_begin";
|
|
757
1115
|
var CALL_END_PATH = "call_end";
|
|
758
1116
|
var COMPRESS_PROMPT_PATH = "compress_prompt";
|
|
1117
|
+
var SAMPLES_PATH = "samples";
|
|
1118
|
+
var SAMPLING_SETTINGS_PATH = "sampling/settings";
|
|
1119
|
+
var SAMPLING_DECIDE_PATH = "sampling/decide";
|
|
759
1120
|
var CHECK_USAGE_PATH = "customers/{customerId}/usage";
|
|
760
1121
|
var CREATE_CUSTOMER_PATH = "customers";
|
|
761
1122
|
var CHANGE_PLAN_PATH = "customers/{customerId}/change_plan";
|
|
@@ -766,11 +1127,16 @@ var CORRELATION_HEADER = "x-usage-correlation-id";
|
|
|
766
1127
|
var IDEMPOTENCY_HEADER = "idempotency-key";
|
|
767
1128
|
var SDK_HEADER = "x-usage-sdk";
|
|
768
1129
|
var USER_AGENT = "UsageTapClient";
|
|
769
|
-
var
|
|
1130
|
+
var CANONICAL_MEDIA_TYPE2 = "application/vnd.usagetap.v1+json";
|
|
770
1131
|
var DEFAULT_BASE_URL = "https://api.usagetap.com";
|
|
771
|
-
var
|
|
1132
|
+
var DEFAULT_RUN_INACTIVITY_MS = 60 * 60 * 1e3;
|
|
1133
|
+
var SDK_VERSION = "1.4.0" ;
|
|
772
1134
|
var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
773
1135
|
var UsageTapClient = class {
|
|
1136
|
+
/** OpenAI-compatible chat, model, and native batch operations. */
|
|
1137
|
+
gateway;
|
|
1138
|
+
/** Published-profile context summarization operations. */
|
|
1139
|
+
summarization;
|
|
774
1140
|
apiKey;
|
|
775
1141
|
baseUrl;
|
|
776
1142
|
fetchImpl;
|
|
@@ -795,6 +1161,11 @@ var UsageTapClient = class {
|
|
|
795
1161
|
usageTapCompressionMessagesEndpoint;
|
|
796
1162
|
usageTapCompressionModel;
|
|
797
1163
|
usageTapCompressionAggressiveness;
|
|
1164
|
+
sampling;
|
|
1165
|
+
samplingSettingsCacheMs;
|
|
1166
|
+
circuitBreaker;
|
|
1167
|
+
circuitBreakerRuns = /* @__PURE__ */ new Map();
|
|
1168
|
+
samplingSettingsCache;
|
|
798
1169
|
constructor(options = {}) {
|
|
799
1170
|
const apiKey = options.apiKey?.trim() || readEnvironmentVariable("USAGETAP_API_KEY");
|
|
800
1171
|
const baseUrl = options.baseUrl?.trim() || readEnvironmentVariable("USAGETAP_BASE_URL") || DEFAULT_BASE_URL;
|
|
@@ -817,10 +1188,21 @@ var UsageTapClient = class {
|
|
|
817
1188
|
"A global fetch implementation was not found. Pass fetchImpl in UsageTapClientOptions."
|
|
818
1189
|
);
|
|
819
1190
|
}
|
|
820
|
-
const
|
|
821
|
-
this.baseUrl = new URL(
|
|
1191
|
+
const normalizedBaseUrl2 = normalizeBaseUrl(baseUrl);
|
|
1192
|
+
this.baseUrl = new URL(normalizedBaseUrl2);
|
|
822
1193
|
this.apiKey = apiKey;
|
|
823
1194
|
this.fetchImpl = wrapFetchImplementation(fetchCandidate, !options.fetchImpl);
|
|
1195
|
+
const resourceConfig = {
|
|
1196
|
+
apiKey,
|
|
1197
|
+
apiBaseUrl: normalizedBaseUrl2,
|
|
1198
|
+
gatewayBaseUrl: options.gatewayBaseUrl?.trim() || readEnvironmentVariable("USAGETAP_GATEWAY_URL"),
|
|
1199
|
+
fetchImpl: this.fetchImpl,
|
|
1200
|
+
headers: options.headers,
|
|
1201
|
+
sdkVersion: SDK_VERSION,
|
|
1202
|
+
idempotencyGenerator: options.idempotencyGenerator
|
|
1203
|
+
};
|
|
1204
|
+
this.gateway = new GatewayResource(resourceConfig);
|
|
1205
|
+
this.summarization = new SummarizationResource(resourceConfig);
|
|
824
1206
|
this.defaultFeature = options.defaultFeature;
|
|
825
1207
|
this.defaultTags = options.defaultTags?.length ? dedupeStrings(options.defaultTags) : void 0;
|
|
826
1208
|
this.defaultHeaders = options.headers ? normalizeHeaderDictionary(options.headers) : {};
|
|
@@ -842,11 +1224,122 @@ var UsageTapClient = class {
|
|
|
842
1224
|
this.usageTapCompressionMessagesEndpoint = options.usageTapCompressionMessagesEndpoint;
|
|
843
1225
|
this.usageTapCompressionModel = options.usageTapCompressionModel;
|
|
844
1226
|
this.usageTapCompressionAggressiveness = options.usageTapCompressionAggressiveness;
|
|
1227
|
+
this.sampling = options.sampling;
|
|
1228
|
+
this.samplingSettingsCacheMs = Number.isFinite(options.samplingSettingsCacheMs) ? Math.max(0, Number(options.samplingSettingsCacheMs)) : 5 * 60 * 1e3;
|
|
1229
|
+
if (options.circuitBreaker) {
|
|
1230
|
+
const maxCallsPerRun = options.circuitBreaker.maxCallsPerRun;
|
|
1231
|
+
if (!Number.isInteger(maxCallsPerRun) || maxCallsPerRun < 1) {
|
|
1232
|
+
throw new UsageTapError(
|
|
1233
|
+
"USAGETAP_BAD_REQUEST",
|
|
1234
|
+
"circuitBreaker.maxCallsPerRun must be a positive integer"
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
const runInactivityMs = options.circuitBreaker.runInactivityMs ?? DEFAULT_RUN_INACTIVITY_MS;
|
|
1238
|
+
if (!Number.isFinite(runInactivityMs) || runInactivityMs < 1) {
|
|
1239
|
+
throw new UsageTapError(
|
|
1240
|
+
"USAGETAP_BAD_REQUEST",
|
|
1241
|
+
"circuitBreaker.runInactivityMs must be a positive number"
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
this.circuitBreaker = {
|
|
1245
|
+
maxCallsPerRun,
|
|
1246
|
+
runInactivityMs
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
shouldSample(request, policy = this.sampling || void 0) {
|
|
1251
|
+
if (!policy) return false;
|
|
1252
|
+
const rate = Math.min(1, Math.max(0, Number(policy.rate) || 0));
|
|
1253
|
+
if (rate <= 0) return false;
|
|
1254
|
+
const customerId = request.customerId?.trim();
|
|
1255
|
+
if (customerId && policy.customers?.exclude?.includes(customerId)) return false;
|
|
1256
|
+
const feature = request.feature?.trim();
|
|
1257
|
+
if (feature && policy.features?.exclude?.includes(feature)) return false;
|
|
1258
|
+
const included = policy.features?.include?.filter(Boolean) ?? [];
|
|
1259
|
+
if (included.length > 0 && (!feature || !included.includes(feature))) return false;
|
|
1260
|
+
const minimum = Math.max(0, Math.round(policy.minInputTokens ?? 0));
|
|
1261
|
+
if (minimum > 0 && estimatePromptTokens(request.input) < minimum) return false;
|
|
1262
|
+
return (policy.random ?? Math.random)() < rate;
|
|
1263
|
+
}
|
|
1264
|
+
async getSamplingSettings(options = {}) {
|
|
1265
|
+
const now = Date.now();
|
|
1266
|
+
if (!options.forceRefresh && this.samplingSettingsCache && this.samplingSettingsCache.expiresAtMs > now) {
|
|
1267
|
+
return {
|
|
1268
|
+
result: { status: "ACCEPTED", code: "SAMPLING_SETTINGS_CACHED" },
|
|
1269
|
+
data: this.samplingSettingsCache.settings,
|
|
1270
|
+
correlationId: options.correlationId ?? "local-cache"
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
const response = await this.requestGet(
|
|
1274
|
+
SAMPLING_SETTINGS_PATH,
|
|
1275
|
+
{
|
|
1276
|
+
signal: options.signal,
|
|
1277
|
+
headers: options.headers,
|
|
1278
|
+
retries: options.retries,
|
|
1279
|
+
correlationId: options.correlationId
|
|
1280
|
+
}
|
|
1281
|
+
);
|
|
1282
|
+
const serverCacheMs = Math.max(0, Number(response.data.cacheSeconds) || 0) * 1e3;
|
|
1283
|
+
const cacheMs = Math.min(this.samplingSettingsCacheMs, serverCacheMs);
|
|
1284
|
+
this.samplingSettingsCache = {
|
|
1285
|
+
settings: response.data,
|
|
1286
|
+
expiresAtMs: now + cacheMs
|
|
1287
|
+
};
|
|
1288
|
+
return response;
|
|
1289
|
+
}
|
|
1290
|
+
async shouldSampleAsync(request, policy) {
|
|
1291
|
+
if (policy) return this.shouldSample(request, policy);
|
|
1292
|
+
if (this.sampling === false) return false;
|
|
1293
|
+
if (this.sampling) return this.shouldSample(request, this.sampling);
|
|
1294
|
+
try {
|
|
1295
|
+
const settings = await this.getSamplingSettings();
|
|
1296
|
+
return this.shouldSample(request, settings.data);
|
|
1297
|
+
} catch {
|
|
1298
|
+
return false;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
async decideSample(request, options = {}) {
|
|
1302
|
+
const hasTokens = Number.isFinite(request.inputTokens) && Number(request.inputTokens) >= 0;
|
|
1303
|
+
const hasCharacters = Number.isFinite(request.inputCharacters) && Number(request.inputCharacters) >= 0;
|
|
1304
|
+
if (!hasTokens && !hasCharacters) {
|
|
1305
|
+
throw new UsageTapError(
|
|
1306
|
+
"USAGETAP_BAD_REQUEST",
|
|
1307
|
+
"decideSample requires inputTokens or inputCharacters"
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
return this.request(
|
|
1311
|
+
SAMPLING_DECIDE_PATH,
|
|
1312
|
+
request,
|
|
1313
|
+
options
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
async captureSample(request, options = {}) {
|
|
1317
|
+
if (!request || request.input === void 0) {
|
|
1318
|
+
throw new UsageTapError(
|
|
1319
|
+
"USAGETAP_BAD_REQUEST",
|
|
1320
|
+
"captureSample requires input"
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
if (!request.provider?.trim()) {
|
|
1324
|
+
throw new UsageTapError(
|
|
1325
|
+
"USAGETAP_BAD_REQUEST",
|
|
1326
|
+
"captureSample requires provider"
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
const sampleId = request.sampleId?.trim() || this.idempotencyGenerator();
|
|
1330
|
+
return this.request(
|
|
1331
|
+
SAMPLES_PATH,
|
|
1332
|
+
{ ...request, sampleId },
|
|
1333
|
+
{ ...options, idempotencyKey: sampleId }
|
|
1334
|
+
);
|
|
845
1335
|
}
|
|
846
1336
|
async beginCall(request, options = {}) {
|
|
847
1337
|
const idempotencyKey = request.idempotencyKey ?? request.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
|
|
1338
|
+
this.reserveRunCall(request, idempotencyKey);
|
|
1339
|
+
const apiRequest = { ...request };
|
|
1340
|
+
delete apiRequest.runId;
|
|
848
1341
|
const payload = {
|
|
849
|
-
...
|
|
1342
|
+
...apiRequest,
|
|
850
1343
|
feature: request.feature ?? this.defaultFeature,
|
|
851
1344
|
tags: this.mergeTags(request.tags)
|
|
852
1345
|
};
|
|
@@ -864,6 +1357,28 @@ var UsageTapClient = class {
|
|
|
864
1357
|
);
|
|
865
1358
|
return response;
|
|
866
1359
|
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Inspect a configured run circuit breaker without consuming another call.
|
|
1362
|
+
*/
|
|
1363
|
+
canRunContinue(request) {
|
|
1364
|
+
const identity = this.resolveRunIdentity(request);
|
|
1365
|
+
if (!identity || !this.circuitBreaker) {
|
|
1366
|
+
throw new UsageTapError(
|
|
1367
|
+
"USAGETAP_BAD_REQUEST",
|
|
1368
|
+
"canRunContinue requires circuitBreaker configuration and a non-empty runId"
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
this.expireInactiveRuns();
|
|
1372
|
+
const calls = this.circuitBreakerRuns.get(identity.key)?.calls ?? 0;
|
|
1373
|
+
return this.createCircuitBreakerDecision(identity.customerId, identity.runId, calls);
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Release local state after a workflow finishes. Returns true when state existed.
|
|
1377
|
+
*/
|
|
1378
|
+
resetRun(request) {
|
|
1379
|
+
const identity = this.resolveRunIdentity(request);
|
|
1380
|
+
return identity ? this.circuitBreakerRuns.delete(identity.key) : false;
|
|
1381
|
+
}
|
|
867
1382
|
async promptCompress(request, options = {}) {
|
|
868
1383
|
if (!request?.callId) {
|
|
869
1384
|
throw new UsageTapError(
|
|
@@ -961,6 +1476,10 @@ var UsageTapClient = class {
|
|
|
961
1476
|
usageTapCompressionMessagesEndpoint: this.usageTapCompressionMessagesEndpoint,
|
|
962
1477
|
aggressiveness: options.aggressiveness ?? this.aggressiveness,
|
|
963
1478
|
usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
|
|
1479
|
+
mode: options.mode,
|
|
1480
|
+
latencyBudgetMs: options.latencyBudgetMs,
|
|
1481
|
+
compactEmptyUserMessages: options.compactEmptyUserMessages,
|
|
1482
|
+
compactDuplicateUserTextParts: options.compactDuplicateUserTextParts,
|
|
964
1483
|
fetchImpl: this.fetchImpl,
|
|
965
1484
|
signal: options.signal,
|
|
966
1485
|
failOpen: options.failOpen
|
|
@@ -1002,11 +1521,17 @@ var UsageTapClient = class {
|
|
|
1002
1521
|
callId: request.callId,
|
|
1003
1522
|
feature: feature ?? this.defaultFeature,
|
|
1004
1523
|
tags: tags ?? this.defaultTags,
|
|
1524
|
+
providerUsed: request.providerUsed,
|
|
1005
1525
|
modelUsed: request.modelUsed,
|
|
1526
|
+
reasoningEffort: request.reasoningEffort,
|
|
1527
|
+
reasoningEffortSource: request.reasoningEffortSource,
|
|
1528
|
+
reasoningMode: request.reasoningMode,
|
|
1529
|
+
reasoningBudgetTokens: request.reasoningBudgetTokens,
|
|
1006
1530
|
metrics: {
|
|
1007
1531
|
inputTokens: request.inputTokens,
|
|
1008
1532
|
responseTokens: request.responseTokens,
|
|
1009
1533
|
cachedInputTokens: request.cachedInputTokens,
|
|
1534
|
+
cacheWriteInputTokens: request.cacheWriteInputTokens,
|
|
1010
1535
|
reasoningTokens: request.reasoningTokens,
|
|
1011
1536
|
searches: request.searches,
|
|
1012
1537
|
audioSeconds: request.audioSeconds,
|
|
@@ -1114,6 +1639,15 @@ var UsageTapClient = class {
|
|
|
1114
1639
|
meterSlot: request.meterSlot,
|
|
1115
1640
|
amount: request.amount
|
|
1116
1641
|
};
|
|
1642
|
+
if (request.customerUserId) {
|
|
1643
|
+
payload.customerUserId = request.customerUserId;
|
|
1644
|
+
}
|
|
1645
|
+
if (request.customerUserName) {
|
|
1646
|
+
payload.customerUserName = request.customerUserName;
|
|
1647
|
+
}
|
|
1648
|
+
if (request.customerUserEmail) {
|
|
1649
|
+
payload.customerUserEmail = request.customerUserEmail;
|
|
1650
|
+
}
|
|
1117
1651
|
if (request.feature) {
|
|
1118
1652
|
payload.feature = request.feature;
|
|
1119
1653
|
}
|
|
@@ -1150,6 +1684,11 @@ var UsageTapClient = class {
|
|
|
1150
1684
|
const beginPayload = idempotencyKey ? { ...beginRequest, idempotencyKey, idempotency: idempotencyKey } : { ...beginRequest };
|
|
1151
1685
|
const beginResponse = await this.beginCall(beginPayload, options);
|
|
1152
1686
|
let usage = {};
|
|
1687
|
+
const pricingMode = beginResponse.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
|
|
1688
|
+
if (pricingMode) {
|
|
1689
|
+
usage.pricingMode = pricingMode;
|
|
1690
|
+
usage.batch = pricingMode === "batch";
|
|
1691
|
+
}
|
|
1153
1692
|
const initialStripeCustomerId = typeof beginResponse.data.stripeCustomerId === "string" ? beginResponse.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
|
|
1154
1693
|
if (initialStripeCustomerId) {
|
|
1155
1694
|
usage = { ...usage, stripeCustomerId: initialStripeCustomerId };
|
|
@@ -1217,17 +1756,86 @@ var UsageTapClient = class {
|
|
|
1217
1756
|
toPromptCompressionTelemetry(result) {
|
|
1218
1757
|
return {
|
|
1219
1758
|
provider: result.provider,
|
|
1220
|
-
originalCharacters: result.originalCharacters,
|
|
1221
|
-
compressedCharacters: result.compressedCharacters,
|
|
1222
|
-
savedCharacters: result.savedCharacters,
|
|
1223
1759
|
originalTokens: result.originalTokens,
|
|
1224
1760
|
compressedTokens: result.compressedTokens,
|
|
1225
1761
|
savedTokens: result.savedTokens,
|
|
1226
1762
|
tokenSavingsRatio: result.tokenSavingsRatio,
|
|
1227
|
-
savingsRatio: result.savingsRatio,
|
|
1228
1763
|
techniques: result.techniques
|
|
1229
1764
|
};
|
|
1230
1765
|
}
|
|
1766
|
+
reserveRunCall(request, idempotencyKey) {
|
|
1767
|
+
const identity = this.resolveRunIdentity(request);
|
|
1768
|
+
if (!identity || !this.circuitBreaker) return;
|
|
1769
|
+
this.expireInactiveRuns();
|
|
1770
|
+
const now = Date.now();
|
|
1771
|
+
const state = this.circuitBreakerRuns.get(identity.key) ?? {
|
|
1772
|
+
calls: 0,
|
|
1773
|
+
reservationKeys: /* @__PURE__ */ new Set(),
|
|
1774
|
+
lastSeenAtMs: now
|
|
1775
|
+
};
|
|
1776
|
+
const reservationKey = idempotencyKey ?? this.idempotencyGenerator();
|
|
1777
|
+
state.lastSeenAtMs = now;
|
|
1778
|
+
if (state.reservationKeys.has(reservationKey)) {
|
|
1779
|
+
this.circuitBreakerRuns.set(identity.key, state);
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
const decision = this.createCircuitBreakerDecision(
|
|
1783
|
+
identity.customerId,
|
|
1784
|
+
identity.runId,
|
|
1785
|
+
state.calls
|
|
1786
|
+
);
|
|
1787
|
+
if (!decision.allowed) {
|
|
1788
|
+
throw new UsageTapError(
|
|
1789
|
+
"USAGETAP_CIRCUIT_OPEN",
|
|
1790
|
+
`Run ${identity.runId} reached its ${decision.limit}-call circuit-breaker limit`,
|
|
1791
|
+
{
|
|
1792
|
+
details: {
|
|
1793
|
+
reason: decision.reason,
|
|
1794
|
+
customerId: identity.customerId,
|
|
1795
|
+
runId: identity.runId,
|
|
1796
|
+
calls: decision.calls,
|
|
1797
|
+
limit: decision.limit,
|
|
1798
|
+
remaining: decision.remaining
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
state.calls += 1;
|
|
1804
|
+
state.reservationKeys.add(reservationKey);
|
|
1805
|
+
this.circuitBreakerRuns.set(identity.key, state);
|
|
1806
|
+
}
|
|
1807
|
+
resolveRunIdentity(request) {
|
|
1808
|
+
const customerId = request.customerId?.trim();
|
|
1809
|
+
const runId = request.runId?.trim();
|
|
1810
|
+
if (!customerId || !runId) return void 0;
|
|
1811
|
+
return {
|
|
1812
|
+
key: `${customerId}\0${runId}`,
|
|
1813
|
+
customerId,
|
|
1814
|
+
runId
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
createCircuitBreakerDecision(customerId, runId, calls) {
|
|
1818
|
+
const limit = this.circuitBreaker?.maxCallsPerRun ?? 0;
|
|
1819
|
+
const allowed = calls < limit;
|
|
1820
|
+
return {
|
|
1821
|
+
allowed,
|
|
1822
|
+
...allowed ? {} : { reason: "max_calls_per_run" },
|
|
1823
|
+
customerId,
|
|
1824
|
+
runId,
|
|
1825
|
+
calls,
|
|
1826
|
+
limit,
|
|
1827
|
+
remaining: Math.max(0, limit - calls)
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
expireInactiveRuns() {
|
|
1831
|
+
if (!this.circuitBreaker || this.circuitBreakerRuns.size === 0) return;
|
|
1832
|
+
const expiredBefore = Date.now() - this.circuitBreaker.runInactivityMs;
|
|
1833
|
+
for (const [key, state] of this.circuitBreakerRuns) {
|
|
1834
|
+
if (state.lastSeenAtMs < expiredBefore) {
|
|
1835
|
+
this.circuitBreakerRuns.delete(key);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1231
1839
|
async request(path, payload, options) {
|
|
1232
1840
|
const url = new URL(path, this.baseUrl).toString();
|
|
1233
1841
|
const body = payload !== void 0 ? JSON.stringify(payload) : void 0;
|
|
@@ -1415,7 +2023,7 @@ var UsageTapClient = class {
|
|
|
1415
2023
|
...this.defaultHeaders,
|
|
1416
2024
|
[SDK_HEADER]: `js/${SDK_VERSION}`,
|
|
1417
2025
|
"content-type": "application/json",
|
|
1418
|
-
accept:
|
|
2026
|
+
accept: CANONICAL_MEDIA_TYPE2
|
|
1419
2027
|
};
|
|
1420
2028
|
if (!HAS_WINDOW) {
|
|
1421
2029
|
headers["user-agent"] = `${USER_AGENT}/${SDK_VERSION}`;
|
|
@@ -1616,17 +2224,18 @@ var OpenAIPromptCompressionStats = class {
|
|
|
1616
2224
|
}
|
|
1617
2225
|
};
|
|
1618
2226
|
function createOpenAIAdapter(init) {
|
|
1619
|
-
const { client, usageTap } = init;
|
|
2227
|
+
const { client, usageTap, provider = "openai" } = init;
|
|
1620
2228
|
return {
|
|
1621
2229
|
async invoke(params) {
|
|
1622
2230
|
const result = await usageTap.withUsage(
|
|
1623
2231
|
params.begin,
|
|
1624
2232
|
async (ctx) => {
|
|
2233
|
+
ctx.setUsage({ providerUsed: provider });
|
|
1625
2234
|
const response = await params.call(client, {
|
|
1626
2235
|
hints: ctx.begin.data.vendorHints,
|
|
1627
2236
|
begin: ctx.begin
|
|
1628
2237
|
});
|
|
1629
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx);
|
|
2238
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx, provider);
|
|
1630
2239
|
return {
|
|
1631
2240
|
data: response,
|
|
1632
2241
|
begin: ctx.begin
|
|
@@ -1640,6 +2249,7 @@ function createOpenAIAdapter(init) {
|
|
|
1640
2249
|
const result = await usageTap.withUsage(
|
|
1641
2250
|
params.begin,
|
|
1642
2251
|
async (ctx) => {
|
|
2252
|
+
ctx.setUsage({ providerUsed: provider });
|
|
1643
2253
|
const { stream, onComplete } = await params.call(client, {
|
|
1644
2254
|
hints: ctx.begin.data.vendorHints,
|
|
1645
2255
|
begin: ctx.begin
|
|
@@ -1765,6 +2375,181 @@ async function pipeToResponse(stream, res, options = {}) {
|
|
|
1765
2375
|
}
|
|
1766
2376
|
}
|
|
1767
2377
|
var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
|
|
2378
|
+
function withSampling(client, options = {}) {
|
|
2379
|
+
if (!client || !options) {
|
|
2380
|
+
throw new UsageTapError(
|
|
2381
|
+
"USAGETAP_BAD_REQUEST",
|
|
2382
|
+
"withSampling requires an OpenAI-compatible client and sampling options"
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
const { apiKey, usageTapClient, provider = "openai", ...policy } = options;
|
|
2386
|
+
const localPolicy = typeof policy.rate === "number" ? policy : void 0;
|
|
2387
|
+
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey, sampling: localPolicy });
|
|
2388
|
+
const wrapCreate = (create) => async (params, requestOptions) => {
|
|
2389
|
+
const { usageTap: callContextRaw, ...providerOptions } = requestOptions ?? {};
|
|
2390
|
+
const callContext = isObjectRecord(callContextRaw) ? callContextRaw : {};
|
|
2391
|
+
const streaming = params.stream === true;
|
|
2392
|
+
const decision = streaming ? Promise.resolve(false) : usageTap.shouldSampleAsync({
|
|
2393
|
+
customerId: readString(callContext.customerId),
|
|
2394
|
+
feature: readString(callContext.feature),
|
|
2395
|
+
input: params
|
|
2396
|
+
}, localPolicy);
|
|
2397
|
+
const startedAt = Date.now();
|
|
2398
|
+
try {
|
|
2399
|
+
const response = await create(
|
|
2400
|
+
params,
|
|
2401
|
+
Object.keys(providerOptions).length ? providerOptions : void 0
|
|
2402
|
+
);
|
|
2403
|
+
const selected = await decision;
|
|
2404
|
+
if (selected) {
|
|
2405
|
+
const record = isObjectRecord(response) ? response : {};
|
|
2406
|
+
await usageTap.captureSample({
|
|
2407
|
+
customerId: readString(callContext.customerId),
|
|
2408
|
+
feature: readString(callContext.feature),
|
|
2409
|
+
environment: readString(callContext.environment),
|
|
2410
|
+
tags: readStringArray(callContext.tags),
|
|
2411
|
+
provider,
|
|
2412
|
+
model: readString(record.model) ?? readString(params.model),
|
|
2413
|
+
input: params,
|
|
2414
|
+
output: response,
|
|
2415
|
+
usage: record.usage,
|
|
2416
|
+
latencyMs: Date.now() - startedAt
|
|
2417
|
+
}).catch(() => void 0);
|
|
2418
|
+
}
|
|
2419
|
+
return response;
|
|
2420
|
+
} catch (error) {
|
|
2421
|
+
const selected = await decision;
|
|
2422
|
+
if (selected) {
|
|
2423
|
+
await usageTap.captureSample({
|
|
2424
|
+
customerId: readString(callContext.customerId),
|
|
2425
|
+
feature: readString(callContext.feature),
|
|
2426
|
+
environment: readString(callContext.environment),
|
|
2427
|
+
tags: readStringArray(callContext.tags),
|
|
2428
|
+
provider,
|
|
2429
|
+
model: readString(params.model),
|
|
2430
|
+
input: params,
|
|
2431
|
+
latencyMs: Date.now() - startedAt,
|
|
2432
|
+
error: serializeSamplingError(error)
|
|
2433
|
+
}).catch(() => void 0);
|
|
2434
|
+
}
|
|
2435
|
+
throw error;
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
const chat = client.chat?.completions ? new Proxy(client.chat, {
|
|
2439
|
+
get(target, prop, receiver) {
|
|
2440
|
+
if (prop !== "completions") return safeReflectGet(target, prop, receiver);
|
|
2441
|
+
const completions = target.completions;
|
|
2442
|
+
return new Proxy(completions, {
|
|
2443
|
+
get(completionTarget, completionProp, completionReceiver) {
|
|
2444
|
+
if (completionProp === "create") {
|
|
2445
|
+
return wrapCreate(
|
|
2446
|
+
completionTarget.create.bind(completionTarget)
|
|
2447
|
+
);
|
|
2448
|
+
}
|
|
2449
|
+
return safeReflectGet(
|
|
2450
|
+
completionTarget,
|
|
2451
|
+
completionProp,
|
|
2452
|
+
completionReceiver
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
}) : void 0;
|
|
2458
|
+
const responses = typeof client.responses !== "undefined" && client.responses ? new Proxy(client.responses, {
|
|
2459
|
+
get(target, prop, receiver) {
|
|
2460
|
+
if (prop === "create") {
|
|
2461
|
+
const create = Reflect.get(target, prop, receiver);
|
|
2462
|
+
return wrapCreate(create.bind(target));
|
|
2463
|
+
}
|
|
2464
|
+
return safeReflectGet(target, prop, receiver);
|
|
2465
|
+
}
|
|
2466
|
+
}) : void 0;
|
|
2467
|
+
return new Proxy(client, {
|
|
2468
|
+
get(target, prop, receiver) {
|
|
2469
|
+
if (prop === "chat" && chat) return chat;
|
|
2470
|
+
if (prop === "responses" && responses) return responses;
|
|
2471
|
+
if (prop === "unwrap") return () => target;
|
|
2472
|
+
return safeReflectGet(target, prop, receiver);
|
|
2473
|
+
}
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
function safeReflectGet(target, prop, receiver) {
|
|
2477
|
+
return Reflect.get(target, prop, receiver);
|
|
2478
|
+
}
|
|
2479
|
+
function readStringArray(value) {
|
|
2480
|
+
if (!Array.isArray(value)) return void 0;
|
|
2481
|
+
const strings = value.filter((item) => typeof item === "string");
|
|
2482
|
+
return strings.length ? strings : void 0;
|
|
2483
|
+
}
|
|
2484
|
+
function readString(value) {
|
|
2485
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2486
|
+
}
|
|
2487
|
+
function serializeSamplingError(error) {
|
|
2488
|
+
if (error instanceof Error) {
|
|
2489
|
+
return { name: error.name, message: error.message };
|
|
2490
|
+
}
|
|
2491
|
+
return { message: String(error) };
|
|
2492
|
+
}
|
|
2493
|
+
function normalizeMeteredOpenAISampling(options) {
|
|
2494
|
+
if (!options) return void 0;
|
|
2495
|
+
if (options === true) return { provider: "openai" };
|
|
2496
|
+
const { provider = "openai", ...policyFields } = options;
|
|
2497
|
+
return {
|
|
2498
|
+
provider,
|
|
2499
|
+
policy: typeof policyFields.rate === "number" ? policyFields : void 0
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
function startMeteredOpenAISampleDecision({
|
|
2503
|
+
usageTap,
|
|
2504
|
+
sampling,
|
|
2505
|
+
beginRequest,
|
|
2506
|
+
input
|
|
2507
|
+
}) {
|
|
2508
|
+
if (!sampling) return Promise.resolve(false);
|
|
2509
|
+
return usageTap.shouldSampleAsync(
|
|
2510
|
+
{
|
|
2511
|
+
customerId: beginRequest.customerId,
|
|
2512
|
+
feature: beginRequest.feature,
|
|
2513
|
+
input
|
|
2514
|
+
},
|
|
2515
|
+
sampling.policy
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2518
|
+
async function captureMeteredOpenAISample({
|
|
2519
|
+
usageTap,
|
|
2520
|
+
sampling,
|
|
2521
|
+
decision,
|
|
2522
|
+
ctx,
|
|
2523
|
+
beginRequest,
|
|
2524
|
+
input,
|
|
2525
|
+
response,
|
|
2526
|
+
error,
|
|
2527
|
+
startedAt
|
|
2528
|
+
}) {
|
|
2529
|
+
if (!sampling) return;
|
|
2530
|
+
let selected = false;
|
|
2531
|
+
try {
|
|
2532
|
+
selected = await decision;
|
|
2533
|
+
} catch {
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
if (!selected) return;
|
|
2537
|
+
const record = isObjectRecord(response) ? response : {};
|
|
2538
|
+
await usageTap.captureSample({
|
|
2539
|
+
sampleId: ctx.begin.data.callId,
|
|
2540
|
+
callId: ctx.begin.data.callId,
|
|
2541
|
+
customerId: beginRequest.customerId,
|
|
2542
|
+
feature: beginRequest.feature,
|
|
2543
|
+
tags: beginRequest.tags,
|
|
2544
|
+
provider: sampling.provider,
|
|
2545
|
+
model: readString(record.model) ?? readString(input.model),
|
|
2546
|
+
input,
|
|
2547
|
+
...response === void 0 ? {} : { output: response },
|
|
2548
|
+
usage: record.usage,
|
|
2549
|
+
latencyMs: Date.now() - startedAt,
|
|
2550
|
+
...error === void 0 ? {} : { error: serializeSamplingError(error) }
|
|
2551
|
+
}).catch(() => void 0);
|
|
2552
|
+
}
|
|
1768
2553
|
function withCompression(client, options = {}) {
|
|
1769
2554
|
if (!client) {
|
|
1770
2555
|
throw new UsageTapError(
|
|
@@ -1775,11 +2560,13 @@ function withCompression(client, options = {}) {
|
|
|
1775
2560
|
const {
|
|
1776
2561
|
apiKey,
|
|
1777
2562
|
usageTapClient,
|
|
2563
|
+
sampling,
|
|
1778
2564
|
...compressionOverrides
|
|
1779
2565
|
} = options;
|
|
1780
2566
|
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
|
|
1781
2567
|
const compression = {
|
|
1782
2568
|
provider: "usagetap",
|
|
2569
|
+
roles: { user: { enabled: true, aggressiveness: 0.2 } },
|
|
1783
2570
|
minContextTokens: DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS,
|
|
1784
2571
|
...compressionOverrides
|
|
1785
2572
|
};
|
|
@@ -1789,7 +2576,7 @@ function withCompression(client, options = {}) {
|
|
|
1789
2576
|
usageTap,
|
|
1790
2577
|
compression
|
|
1791
2578
|
) : void 0;
|
|
1792
|
-
|
|
2579
|
+
const compressed = new Proxy(client, {
|
|
1793
2580
|
get(target, prop, receiver) {
|
|
1794
2581
|
if (prop === "chat" && proxiedChat) {
|
|
1795
2582
|
return proxiedChat;
|
|
@@ -1803,6 +2590,11 @@ function withCompression(client, options = {}) {
|
|
|
1803
2590
|
return Reflect.get(target, prop, receiver);
|
|
1804
2591
|
}
|
|
1805
2592
|
});
|
|
2593
|
+
return sampling ? withSampling(compressed, {
|
|
2594
|
+
...sampling === true ? {} : sampling,
|
|
2595
|
+
apiKey,
|
|
2596
|
+
usageTapClient
|
|
2597
|
+
}) : compressed;
|
|
1806
2598
|
}
|
|
1807
2599
|
function withMetering(client, customer) {
|
|
1808
2600
|
const config = typeof customer === "string" ? { customerId: customer } : customer;
|
|
@@ -1817,6 +2609,8 @@ function withMetering(client, customer) {
|
|
|
1817
2609
|
usageTapClient,
|
|
1818
2610
|
applyVendorHints,
|
|
1819
2611
|
promptCompression,
|
|
2612
|
+
sampling,
|
|
2613
|
+
provider,
|
|
1820
2614
|
...defaultContext
|
|
1821
2615
|
} = config;
|
|
1822
2616
|
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
|
|
@@ -1824,7 +2618,9 @@ function withMetering(client, customer) {
|
|
|
1824
2618
|
return wrapOpenAI(client, usageTap, {
|
|
1825
2619
|
defaultContext,
|
|
1826
2620
|
applyVendorHints,
|
|
1827
|
-
promptCompression: normalizedCompression
|
|
2621
|
+
promptCompression: normalizedCompression,
|
|
2622
|
+
sampling,
|
|
2623
|
+
provider
|
|
1828
2624
|
});
|
|
1829
2625
|
}
|
|
1830
2626
|
function wrapOpenAI(client, usageTap, options = {}) {
|
|
@@ -1834,6 +2630,8 @@ function wrapOpenAI(client, usageTap, options = {}) {
|
|
|
1834
2630
|
const defaultContext = options.defaultContext;
|
|
1835
2631
|
const applyVendorHints = options.applyVendorHints !== false;
|
|
1836
2632
|
const defaultPromptCompression = normalizePromptCompressionOptions(options.promptCompression);
|
|
2633
|
+
const defaultSampling = normalizeMeteredOpenAISampling(options.sampling);
|
|
2634
|
+
const provider = options.provider ?? "openai";
|
|
1837
2635
|
const promptCompressionStats = new OpenAIPromptCompressionStats();
|
|
1838
2636
|
const proxiedChat = client.chat ? createChatProxy(
|
|
1839
2637
|
client.chat,
|
|
@@ -1841,7 +2639,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
|
|
|
1841
2639
|
defaultContext,
|
|
1842
2640
|
applyVendorHints,
|
|
1843
2641
|
defaultPromptCompression,
|
|
1844
|
-
promptCompressionStats
|
|
2642
|
+
promptCompressionStats,
|
|
2643
|
+
defaultSampling,
|
|
2644
|
+
provider
|
|
1845
2645
|
) : void 0;
|
|
1846
2646
|
const proxiedResponses = typeof client.responses !== "undefined" ? createResponsesProxy(
|
|
1847
2647
|
client.responses,
|
|
@@ -1849,7 +2649,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
|
|
|
1849
2649
|
defaultContext,
|
|
1850
2650
|
applyVendorHints,
|
|
1851
2651
|
defaultPromptCompression,
|
|
1852
|
-
promptCompressionStats
|
|
2652
|
+
promptCompressionStats,
|
|
2653
|
+
defaultSampling,
|
|
2654
|
+
provider
|
|
1853
2655
|
) : void 0;
|
|
1854
2656
|
const handler = {
|
|
1855
2657
|
get(target, prop, receiver) {
|
|
@@ -1968,14 +2770,16 @@ function streamOpenAIRoute(usageTap, openai, options) {
|
|
|
1968
2770
|
});
|
|
1969
2771
|
};
|
|
1970
2772
|
}
|
|
1971
|
-
function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2773
|
+
function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
1972
2774
|
const completions = createChatCompletionsProxy(
|
|
1973
2775
|
resource.completions,
|
|
1974
2776
|
usageTap,
|
|
1975
2777
|
defaultContext,
|
|
1976
2778
|
applyVendorHints,
|
|
1977
2779
|
defaultPromptCompression,
|
|
1978
|
-
promptCompressionStats
|
|
2780
|
+
promptCompressionStats,
|
|
2781
|
+
defaultSampling,
|
|
2782
|
+
provider
|
|
1979
2783
|
);
|
|
1980
2784
|
const handler = {
|
|
1981
2785
|
get(target, prop, receiver) {
|
|
@@ -1987,7 +2791,7 @@ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, d
|
|
|
1987
2791
|
};
|
|
1988
2792
|
return new Proxy(resource, handler);
|
|
1989
2793
|
}
|
|
1990
|
-
function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2794
|
+
function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
1991
2795
|
if (!resource || typeof resource !== "object") {
|
|
1992
2796
|
return void 0;
|
|
1993
2797
|
}
|
|
@@ -2002,9 +2806,20 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
2002
2806
|
withUsage,
|
|
2003
2807
|
promptCompression
|
|
2004
2808
|
} = splitUsageOptions(options);
|
|
2005
|
-
const beginRequest =
|
|
2809
|
+
const beginRequest = withRuntimeCompressionContext(
|
|
2810
|
+
resolveBeginRequest(defaultContext, usageContext),
|
|
2811
|
+
"openai",
|
|
2812
|
+
String(params.model)
|
|
2813
|
+
);
|
|
2006
2814
|
const wantsStream = isStreamingRequest(params);
|
|
2007
2815
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2816
|
+
const sampleStartedAt = Date.now();
|
|
2817
|
+
const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
|
|
2818
|
+
usageTap,
|
|
2819
|
+
sampling: defaultSampling,
|
|
2820
|
+
beginRequest,
|
|
2821
|
+
input: params
|
|
2822
|
+
});
|
|
2008
2823
|
const hintedParams = applyVendorHints ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
2009
2824
|
const finalParams = await compressResponsesParamsForCall({
|
|
2010
2825
|
params: hintedParams,
|
|
@@ -2016,13 +2831,14 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
2016
2831
|
withUsage,
|
|
2017
2832
|
operation: "responses.create"
|
|
2018
2833
|
});
|
|
2834
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
2019
2835
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
2020
2836
|
if (wantsStream) {
|
|
2021
2837
|
const apiPromise2 = originalCreate(finalParams, request);
|
|
2022
2838
|
const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
|
|
2023
2839
|
ensureAsyncIterable(rawStream, "responses.create");
|
|
2024
2840
|
const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
|
|
2025
|
-
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
|
|
2841
|
+
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
|
|
2026
2842
|
if (usage) {
|
|
2027
2843
|
ctx.setUsage(usage);
|
|
2028
2844
|
}
|
|
@@ -2032,9 +2848,31 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
2032
2848
|
return wrappedPromise2;
|
|
2033
2849
|
}
|
|
2034
2850
|
const apiPromise = originalCreate(finalParams, request);
|
|
2035
|
-
const wrappedPromise = transformApiPromise(apiPromise, (response) => {
|
|
2036
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
|
|
2851
|
+
const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
|
|
2852
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
|
|
2853
|
+
await captureMeteredOpenAISample({
|
|
2854
|
+
usageTap,
|
|
2855
|
+
sampling: defaultSampling,
|
|
2856
|
+
decision: sampleDecision,
|
|
2857
|
+
ctx,
|
|
2858
|
+
beginRequest,
|
|
2859
|
+
input: params,
|
|
2860
|
+
response,
|
|
2861
|
+
startedAt: sampleStartedAt
|
|
2862
|
+
});
|
|
2037
2863
|
return response;
|
|
2864
|
+
}, async (error) => {
|
|
2865
|
+
await captureMeteredOpenAISample({
|
|
2866
|
+
usageTap,
|
|
2867
|
+
sampling: defaultSampling,
|
|
2868
|
+
decision: sampleDecision,
|
|
2869
|
+
ctx,
|
|
2870
|
+
beginRequest,
|
|
2871
|
+
input: params,
|
|
2872
|
+
error,
|
|
2873
|
+
startedAt: sampleStartedAt
|
|
2874
|
+
});
|
|
2875
|
+
throw error;
|
|
2038
2876
|
});
|
|
2039
2877
|
return wrappedPromise;
|
|
2040
2878
|
}, withUsage);
|
|
@@ -2049,7 +2887,7 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
2049
2887
|
};
|
|
2050
2888
|
return new Proxy(resource, handler);
|
|
2051
2889
|
}
|
|
2052
|
-
function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2890
|
+
function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
2053
2891
|
const originalCreate = resource.create.bind(resource);
|
|
2054
2892
|
const streamCandidate = resource.stream;
|
|
2055
2893
|
const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(resource) : void 0;
|
|
@@ -2060,9 +2898,20 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2060
2898
|
withUsage,
|
|
2061
2899
|
promptCompression
|
|
2062
2900
|
} = splitUsageOptions(options);
|
|
2063
|
-
const beginRequest =
|
|
2901
|
+
const beginRequest = withRuntimeCompressionContext(
|
|
2902
|
+
resolveBeginRequest(defaultContext, usageContext),
|
|
2903
|
+
"openai",
|
|
2904
|
+
String(params.model)
|
|
2905
|
+
);
|
|
2064
2906
|
const wantsStream = isStreamingRequest(params);
|
|
2065
2907
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2908
|
+
const sampleStartedAt = Date.now();
|
|
2909
|
+
const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
|
|
2910
|
+
usageTap,
|
|
2911
|
+
sampling: defaultSampling,
|
|
2912
|
+
beginRequest,
|
|
2913
|
+
input: params
|
|
2914
|
+
});
|
|
2066
2915
|
const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
2067
2916
|
const finalParams = await compressChatParamsForCall({
|
|
2068
2917
|
params: hintedParams,
|
|
@@ -2074,13 +2923,14 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2074
2923
|
withUsage,
|
|
2075
2924
|
operation: "chat.completions.create"
|
|
2076
2925
|
});
|
|
2926
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
2077
2927
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
2078
2928
|
if (wantsStream) {
|
|
2079
2929
|
const apiPromise2 = originalCreate(finalParams, request);
|
|
2080
2930
|
const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
|
|
2081
2931
|
ensureAsyncIterable(rawStream, "chat.completions.create");
|
|
2082
2932
|
const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
|
|
2083
|
-
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
|
|
2933
|
+
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
|
|
2084
2934
|
if (usage) {
|
|
2085
2935
|
ctx.setUsage(usage);
|
|
2086
2936
|
}
|
|
@@ -2090,9 +2940,31 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2090
2940
|
return wrappedPromise2;
|
|
2091
2941
|
}
|
|
2092
2942
|
const apiPromise = originalCreate(finalParams, request);
|
|
2093
|
-
const wrappedPromise = transformApiPromise(apiPromise, (response) => {
|
|
2094
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
|
|
2943
|
+
const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
|
|
2944
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
|
|
2945
|
+
await captureMeteredOpenAISample({
|
|
2946
|
+
usageTap,
|
|
2947
|
+
sampling: defaultSampling,
|
|
2948
|
+
decision: sampleDecision,
|
|
2949
|
+
ctx,
|
|
2950
|
+
beginRequest,
|
|
2951
|
+
input: params,
|
|
2952
|
+
response,
|
|
2953
|
+
startedAt: sampleStartedAt
|
|
2954
|
+
});
|
|
2095
2955
|
return response;
|
|
2956
|
+
}, async (error) => {
|
|
2957
|
+
await captureMeteredOpenAISample({
|
|
2958
|
+
usageTap,
|
|
2959
|
+
sampling: defaultSampling,
|
|
2960
|
+
decision: sampleDecision,
|
|
2961
|
+
ctx,
|
|
2962
|
+
beginRequest,
|
|
2963
|
+
input: params,
|
|
2964
|
+
error,
|
|
2965
|
+
startedAt: sampleStartedAt
|
|
2966
|
+
});
|
|
2967
|
+
throw error;
|
|
2096
2968
|
});
|
|
2097
2969
|
return wrappedPromise;
|
|
2098
2970
|
}, withUsage);
|
|
@@ -2104,7 +2976,11 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2104
2976
|
withUsage,
|
|
2105
2977
|
promptCompression
|
|
2106
2978
|
} = splitUsageOptions(options);
|
|
2107
|
-
const beginRequest =
|
|
2979
|
+
const beginRequest = withRuntimeCompressionContext(
|
|
2980
|
+
resolveBeginRequest(defaultContext, usageContext),
|
|
2981
|
+
"openai",
|
|
2982
|
+
String(params.model)
|
|
2983
|
+
);
|
|
2108
2984
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2109
2985
|
const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
2110
2986
|
const finalParams = await compressChatParamsForCall({
|
|
@@ -2117,12 +2993,17 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2117
2993
|
withUsage,
|
|
2118
2994
|
operation: "chat.completions.stream"
|
|
2119
2995
|
});
|
|
2996
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
2120
2997
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
2121
2998
|
const apiPromise = originalStream(finalParams, request);
|
|
2122
2999
|
const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
|
|
2123
3000
|
ensureAsyncIterable(rawStream, "chat.completions.stream");
|
|
2124
3001
|
const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
|
|
2125
|
-
const usage = await extractUsageFromStream(
|
|
3002
|
+
const usage = await extractUsageFromStream(
|
|
3003
|
+
rawStream,
|
|
3004
|
+
ctx.begin.data.vendorHints,
|
|
3005
|
+
provider
|
|
3006
|
+
);
|
|
2126
3007
|
if (usage) {
|
|
2127
3008
|
ctx.setUsage(usage);
|
|
2128
3009
|
}
|
|
@@ -2146,19 +3027,70 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2146
3027
|
return new Proxy(resource, handler);
|
|
2147
3028
|
}
|
|
2148
3029
|
async function compressChatParamsForCall(args) {
|
|
2149
|
-
|
|
3030
|
+
let compression = resolveEffectivePromptCompressionOptions(
|
|
2150
3031
|
args.defaultPromptCompression,
|
|
2151
3032
|
args.callPromptCompression
|
|
2152
3033
|
);
|
|
3034
|
+
const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
|
|
3035
|
+
const policyDriven = !compression && Boolean(runtimePolicy);
|
|
3036
|
+
if (policyDriven && runtimePolicy) {
|
|
3037
|
+
if (!runtimePolicy.selected) {
|
|
3038
|
+
args.ctx.setUsage({
|
|
3039
|
+
runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
|
|
3040
|
+
});
|
|
3041
|
+
return args.params;
|
|
3042
|
+
}
|
|
3043
|
+
compression = openAIPolicyCompressionOptions(runtimePolicy);
|
|
3044
|
+
if (runtimePolicy.mode === "SHADOW") {
|
|
3045
|
+
const startedAt2 = Date.now();
|
|
3046
|
+
void compressChatParams(
|
|
3047
|
+
args.params,
|
|
3048
|
+
args.usageTap,
|
|
3049
|
+
compression,
|
|
3050
|
+
args.withUsage?.signal
|
|
3051
|
+
).then((outcome2) => {
|
|
3052
|
+
args.ctx.setUsage({
|
|
3053
|
+
runtimeCompression: openAIRuntimeMeasurement({
|
|
3054
|
+
policy: runtimePolicy,
|
|
3055
|
+
telemetry: buildPromptCompressionTelemetry(outcome2.segments),
|
|
3056
|
+
latencyMs: Date.now() - startedAt2,
|
|
3057
|
+
shadow: true,
|
|
3058
|
+
originalPayload: args.params,
|
|
3059
|
+
resultPayload: outcome2.params
|
|
3060
|
+
})
|
|
3061
|
+
});
|
|
3062
|
+
}).catch(() => {
|
|
3063
|
+
args.ctx.setUsage({
|
|
3064
|
+
runtimeCompression: failedRuntimeMeasurement(
|
|
3065
|
+
runtimePolicy,
|
|
3066
|
+
Date.now() - startedAt2
|
|
3067
|
+
)
|
|
3068
|
+
});
|
|
3069
|
+
});
|
|
3070
|
+
return args.params;
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
2153
3073
|
if (!compression) {
|
|
2154
3074
|
return args.params;
|
|
2155
3075
|
}
|
|
3076
|
+
const startedAt = Date.now();
|
|
2156
3077
|
const outcome = await compressChatParams(
|
|
2157
3078
|
args.params,
|
|
2158
3079
|
args.usageTap,
|
|
2159
3080
|
compression,
|
|
2160
3081
|
args.withUsage?.signal
|
|
2161
3082
|
);
|
|
3083
|
+
if (policyDriven && runtimePolicy) {
|
|
3084
|
+
const runtimeMeasurement = openAIRuntimeMeasurement({
|
|
3085
|
+
policy: runtimePolicy,
|
|
3086
|
+
telemetry: buildPromptCompressionTelemetry(outcome.segments),
|
|
3087
|
+
latencyMs: Date.now() - startedAt,
|
|
3088
|
+
originalPayload: args.params,
|
|
3089
|
+
resultPayload: outcome.params
|
|
3090
|
+
});
|
|
3091
|
+
args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
|
|
3092
|
+
if (runtimeMeasurement.decision !== "compressed") return args.params;
|
|
3093
|
+
}
|
|
2162
3094
|
await recordCompressionOutcome({
|
|
2163
3095
|
outcome,
|
|
2164
3096
|
compression,
|
|
@@ -2171,19 +3103,70 @@ async function compressChatParamsForCall(args) {
|
|
|
2171
3103
|
return outcome.params;
|
|
2172
3104
|
}
|
|
2173
3105
|
async function compressResponsesParamsForCall(args) {
|
|
2174
|
-
|
|
3106
|
+
let compression = resolveEffectivePromptCompressionOptions(
|
|
2175
3107
|
args.defaultPromptCompression,
|
|
2176
3108
|
args.callPromptCompression
|
|
2177
3109
|
);
|
|
3110
|
+
const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
|
|
3111
|
+
const policyDriven = !compression && Boolean(runtimePolicy);
|
|
3112
|
+
if (policyDriven && runtimePolicy) {
|
|
3113
|
+
if (!runtimePolicy.selected) {
|
|
3114
|
+
args.ctx.setUsage({
|
|
3115
|
+
runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
|
|
3116
|
+
});
|
|
3117
|
+
return args.params;
|
|
3118
|
+
}
|
|
3119
|
+
compression = openAIPolicyCompressionOptions(runtimePolicy);
|
|
3120
|
+
if (runtimePolicy.mode === "SHADOW") {
|
|
3121
|
+
const startedAt2 = Date.now();
|
|
3122
|
+
void compressResponsesParams(
|
|
3123
|
+
args.params,
|
|
3124
|
+
args.usageTap,
|
|
3125
|
+
compression,
|
|
3126
|
+
args.withUsage?.signal
|
|
3127
|
+
).then((outcome2) => {
|
|
3128
|
+
args.ctx.setUsage({
|
|
3129
|
+
runtimeCompression: openAIRuntimeMeasurement({
|
|
3130
|
+
policy: runtimePolicy,
|
|
3131
|
+
telemetry: buildPromptCompressionTelemetry(outcome2.segments),
|
|
3132
|
+
latencyMs: Date.now() - startedAt2,
|
|
3133
|
+
shadow: true,
|
|
3134
|
+
originalPayload: args.params,
|
|
3135
|
+
resultPayload: outcome2.params
|
|
3136
|
+
})
|
|
3137
|
+
});
|
|
3138
|
+
}).catch(() => {
|
|
3139
|
+
args.ctx.setUsage({
|
|
3140
|
+
runtimeCompression: failedRuntimeMeasurement(
|
|
3141
|
+
runtimePolicy,
|
|
3142
|
+
Date.now() - startedAt2
|
|
3143
|
+
)
|
|
3144
|
+
});
|
|
3145
|
+
});
|
|
3146
|
+
return args.params;
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
2178
3149
|
if (!compression) {
|
|
2179
3150
|
return args.params;
|
|
2180
3151
|
}
|
|
3152
|
+
const startedAt = Date.now();
|
|
2181
3153
|
const outcome = await compressResponsesParams(
|
|
2182
3154
|
args.params,
|
|
2183
3155
|
args.usageTap,
|
|
2184
3156
|
compression,
|
|
2185
3157
|
args.withUsage?.signal
|
|
2186
3158
|
);
|
|
3159
|
+
if (policyDriven && runtimePolicy) {
|
|
3160
|
+
const runtimeMeasurement = openAIRuntimeMeasurement({
|
|
3161
|
+
policy: runtimePolicy,
|
|
3162
|
+
telemetry: buildPromptCompressionTelemetry(outcome.segments),
|
|
3163
|
+
latencyMs: Date.now() - startedAt,
|
|
3164
|
+
originalPayload: args.params,
|
|
3165
|
+
resultPayload: outcome.params
|
|
3166
|
+
});
|
|
3167
|
+
args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
|
|
3168
|
+
if (runtimeMeasurement.decision !== "compressed") return args.params;
|
|
3169
|
+
}
|
|
2187
3170
|
await recordCompressionOutcome({
|
|
2188
3171
|
outcome,
|
|
2189
3172
|
compression,
|
|
@@ -2195,6 +3178,79 @@ async function compressResponsesParamsForCall(args) {
|
|
|
2195
3178
|
});
|
|
2196
3179
|
return outcome.params;
|
|
2197
3180
|
}
|
|
3181
|
+
function openAIPolicyCompressionOptions(policy) {
|
|
3182
|
+
const roles = Object.fromEntries(
|
|
3183
|
+
["system", "user", "tool", "assistant"].filter((role) => policy.snapshot.scope.messageRoles.includes(role)).map((role) => [role, true])
|
|
3184
|
+
);
|
|
3185
|
+
return {
|
|
3186
|
+
provider: "usagetap",
|
|
3187
|
+
roles,
|
|
3188
|
+
minContextTokens: policy.snapshot.scope.minimumTokens,
|
|
3189
|
+
failOpen: true
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
function bypassedRuntimeMeasurement(policy) {
|
|
3193
|
+
return {
|
|
3194
|
+
policyId: policy.policyId,
|
|
3195
|
+
policyVersionId: policy.policyVersionId,
|
|
3196
|
+
policyVersion: policy.version,
|
|
3197
|
+
rolloutMode: policy.mode,
|
|
3198
|
+
decision: "bypassed",
|
|
3199
|
+
reasonCode: "not_in_rollout_sample",
|
|
3200
|
+
methodsAttempted: [],
|
|
3201
|
+
methodsApplied: [],
|
|
3202
|
+
originalTokens: 0,
|
|
3203
|
+
resultTokens: 0,
|
|
3204
|
+
savedTokens: 0,
|
|
3205
|
+
reductionPercentage: 0,
|
|
3206
|
+
compressionLatencyMs: 0,
|
|
3207
|
+
compressionComputeCostUsd: 0,
|
|
3208
|
+
financialsEstimated: true
|
|
3209
|
+
};
|
|
3210
|
+
}
|
|
3211
|
+
function failedRuntimeMeasurement(policy, latencyMs) {
|
|
3212
|
+
return {
|
|
3213
|
+
...bypassedRuntimeMeasurement(policy),
|
|
3214
|
+
decision: "fallback",
|
|
3215
|
+
reasonCode: "transformation_failed",
|
|
3216
|
+
methodsAttempted: ["deterministic"],
|
|
3217
|
+
compressionLatencyMs: latencyMs
|
|
3218
|
+
};
|
|
3219
|
+
}
|
|
3220
|
+
function openAIRuntimeMeasurement(input) {
|
|
3221
|
+
const telemetry = input.telemetry;
|
|
3222
|
+
const originalTokens = telemetry?.originalTokens ?? estimatePromptTokens(JSON.stringify(input.originalPayload));
|
|
3223
|
+
const resultTokens = telemetry?.compressedTokens ?? originalTokens;
|
|
3224
|
+
const savedTokens = Math.max(0, telemetry?.savedTokens ?? 0);
|
|
3225
|
+
const reductionPercentage = originalTokens > 0 ? savedTokens / originalTokens * 100 : 0;
|
|
3226
|
+
const belowMinimumSize = originalTokens < input.policy.snapshot.scope.minimumTokens;
|
|
3227
|
+
const latencyExceeded = input.latencyMs > input.policy.snapshot.gates.maximumLatencyMs;
|
|
3228
|
+
const belowSavings = savedTokens < input.policy.snapshot.gates.minimumTokensRemoved || reductionPercentage < input.policy.snapshot.gates.minimumReductionPercentage;
|
|
3229
|
+
const accepted = !belowMinimumSize && !latencyExceeded && !belowSavings;
|
|
3230
|
+
const deepText = input.policy.snapshot.methods.deepText.enabled && !input.policy.snapshot.rollout.deterministicOnly && (input.policy.mode !== "CANARY" || input.policy.snapshot.rollout.deepTextCanaryAllowed);
|
|
3231
|
+
const reasonCode = belowMinimumSize ? "below_minimum_size" : latencyExceeded ? "latency_budget_exceeded" : belowSavings ? "below_minimum_savings" : void 0;
|
|
3232
|
+
const measurement = {
|
|
3233
|
+
policyId: input.policy.policyId,
|
|
3234
|
+
policyVersionId: input.policy.policyVersionId,
|
|
3235
|
+
policyVersion: input.policy.version,
|
|
3236
|
+
rolloutMode: input.policy.mode,
|
|
3237
|
+
decision: accepted ? input.shadow ? "bypassed" : "compressed" : latencyExceeded ? "fallback" : "skipped",
|
|
3238
|
+
...reasonCode ? { reasonCode } : {},
|
|
3239
|
+
methodsAttempted: [
|
|
3240
|
+
"deterministic",
|
|
3241
|
+
...deepText ? ["deep_text"] : []
|
|
3242
|
+
],
|
|
3243
|
+
methodsApplied: accepted ? ["deterministic"] : [],
|
|
3244
|
+
originalTokens,
|
|
3245
|
+
resultTokens: accepted ? resultTokens : originalTokens,
|
|
3246
|
+
savedTokens: accepted ? savedTokens : 0,
|
|
3247
|
+
reductionPercentage: accepted ? reductionPercentage : 0,
|
|
3248
|
+
compressionLatencyMs: input.latencyMs,
|
|
3249
|
+
compressionComputeCostUsd: 0,
|
|
3250
|
+
financialsEstimated: true
|
|
3251
|
+
};
|
|
3252
|
+
return measurement;
|
|
3253
|
+
}
|
|
2198
3254
|
async function recordCompressionOutcome(args) {
|
|
2199
3255
|
const telemetry = buildPromptCompressionTelemetry(args.outcome.segments);
|
|
2200
3256
|
if (!telemetry) {
|
|
@@ -2248,6 +3304,10 @@ async function compressChatParams(params, usageTap, compression, signal) {
|
|
|
2248
3304
|
const result = await usageTap.compressPromptMessages(source, {
|
|
2249
3305
|
provider: "usagetap",
|
|
2250
3306
|
failOpen: compression.failOpen,
|
|
3307
|
+
mode: compression.mode,
|
|
3308
|
+
latencyBudgetMs: compression.latencyBudgetMs,
|
|
3309
|
+
compactEmptyUserMessages: compression.compactEmptyUserMessages,
|
|
3310
|
+
compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
|
|
2251
3311
|
aggressiveness: resolveMessageEndpointAggressiveness(compression),
|
|
2252
3312
|
signal
|
|
2253
3313
|
});
|
|
@@ -2713,23 +3773,53 @@ function resolveBeginRequest(defaults, override) {
|
|
|
2713
3773
|
}
|
|
2714
3774
|
const tags = mergeTags(base.tags, current.tags);
|
|
2715
3775
|
const begin = { customerId };
|
|
3776
|
+
const runtimeCompressionContext = current.runtimeCompressionContext ?? base.runtimeCompressionContext;
|
|
3777
|
+
if (runtimeCompressionContext) {
|
|
3778
|
+
begin.runtimeCompressionContext = runtimeCompressionContext;
|
|
3779
|
+
}
|
|
2716
3780
|
const requested = current.requested ?? base.requested;
|
|
2717
3781
|
if (requested) begin.requested = requested;
|
|
2718
3782
|
const feature = current.feature ?? base.feature;
|
|
2719
3783
|
if (feature) begin.feature = feature;
|
|
3784
|
+
const runId = current.runId ?? base.runId;
|
|
3785
|
+
if (runId) begin.runId = runId;
|
|
2720
3786
|
const idempotency = current.idempotency ?? base.idempotency;
|
|
2721
3787
|
if (idempotency) begin.idempotency = idempotency;
|
|
2722
3788
|
const customerName = current.customerName ?? base.customerName;
|
|
2723
3789
|
if (customerName) begin.customerName = customerName;
|
|
2724
3790
|
const customerEmail = current.customerEmail ?? base.customerEmail;
|
|
2725
3791
|
if (customerEmail) begin.customerEmail = customerEmail;
|
|
3792
|
+
const customerUserId = current.customerUserId ?? base.customerUserId;
|
|
3793
|
+
if (customerUserId) begin.customerUserId = customerUserId;
|
|
3794
|
+
const customerUserName = current.customerUserName ?? base.customerUserName;
|
|
3795
|
+
if (customerUserName) begin.customerUserName = customerUserName;
|
|
3796
|
+
const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
|
|
3797
|
+
if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
|
|
3798
|
+
const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
|
|
3799
|
+
if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
|
|
3800
|
+
const holdUsd = current.holdUsd ?? base.holdUsd;
|
|
3801
|
+
if (typeof holdUsd === "number") begin.holdUsd = holdUsd;
|
|
3802
|
+
const batch = current.batch ?? base.batch;
|
|
3803
|
+
if (typeof batch === "boolean") begin.batch = batch;
|
|
3804
|
+
const pricingMode = current.pricingMode ?? base.pricingMode;
|
|
3805
|
+
if (pricingMode) begin.pricingMode = pricingMode;
|
|
2726
3806
|
if (tags?.length) {
|
|
2727
3807
|
begin.tags = tags;
|
|
2728
3808
|
}
|
|
2729
3809
|
return begin;
|
|
2730
3810
|
}
|
|
2731
|
-
function
|
|
2732
|
-
|
|
3811
|
+
function withRuntimeCompressionContext(begin, provider, model) {
|
|
3812
|
+
return {
|
|
3813
|
+
...begin,
|
|
3814
|
+
runtimeCompressionContext: {
|
|
3815
|
+
...begin.runtimeCompressionContext ?? {},
|
|
3816
|
+
provider,
|
|
3817
|
+
model
|
|
3818
|
+
}
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3821
|
+
function transformApiPromise(apiPromise, onResolve, onReject) {
|
|
3822
|
+
const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
|
|
2733
3823
|
if (isObjectRecord(apiPromise)) {
|
|
2734
3824
|
const proto = Object.getPrototypeOf(apiPromise);
|
|
2735
3825
|
if (proto) {
|
|
@@ -2864,12 +3954,12 @@ function applyResponsesVendorHints(params, hints) {
|
|
|
2864
3954
|
}
|
|
2865
3955
|
return next;
|
|
2866
3956
|
}
|
|
2867
|
-
async function extractUsageFromStream(stream, hints) {
|
|
3957
|
+
async function extractUsageFromStream(stream, hints, provider = "openai") {
|
|
2868
3958
|
const finalPayload = await resolveStreamFinalPayload(stream);
|
|
2869
3959
|
if (!finalPayload) {
|
|
2870
3960
|
return void 0;
|
|
2871
3961
|
}
|
|
2872
|
-
return inferUsageFromResponse(finalPayload, hints);
|
|
3962
|
+
return inferUsageFromResponse(finalPayload, hints, provider);
|
|
2873
3963
|
}
|
|
2874
3964
|
async function resolveStreamFinalPayload(stream) {
|
|
2875
3965
|
if (!stream || typeof stream !== "object") {
|
|
@@ -2942,14 +4032,14 @@ function setHeaderIfPossible(res, key, value) {
|
|
|
2942
4032
|
res.setHeader(key, value);
|
|
2943
4033
|
}
|
|
2944
4034
|
}
|
|
2945
|
-
function tryInferUsage(response, hints, extractor, ctx) {
|
|
4035
|
+
function tryInferUsage(response, hints, extractor, ctx, provider = "openai") {
|
|
2946
4036
|
const explicit = extractor?.(response);
|
|
2947
|
-
const inferred = explicit ?? inferUsageFromResponse(response, hints);
|
|
4037
|
+
const inferred = explicit ?? inferUsageFromResponse(response, hints, provider);
|
|
2948
4038
|
if (inferred) {
|
|
2949
4039
|
ctx.setUsage(inferred);
|
|
2950
4040
|
}
|
|
2951
4041
|
}
|
|
2952
|
-
function inferUsageFromResponse(response, hints) {
|
|
4042
|
+
function inferUsageFromResponse(response, hints, provider = "openai") {
|
|
2953
4043
|
if (!response || typeof response !== "object") {
|
|
2954
4044
|
return void 0;
|
|
2955
4045
|
}
|
|
@@ -2957,12 +4047,41 @@ function inferUsageFromResponse(response, hints) {
|
|
|
2957
4047
|
if (!candidate.usage) {
|
|
2958
4048
|
return void 0;
|
|
2959
4049
|
}
|
|
2960
|
-
const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
|
|
4050
|
+
const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
|
|
4051
|
+
const responseEffort = normalizeExecutionReasoningEffort(
|
|
4052
|
+
candidate.reasoning?.effort
|
|
4053
|
+
);
|
|
2961
4054
|
return {
|
|
4055
|
+
providerUsed: provider,
|
|
2962
4056
|
modelUsed: candidate.model ?? hints?.preferredModel,
|
|
2963
|
-
inputTokens: candidate.usage.prompt_tokens,
|
|
2964
|
-
responseTokens: candidate.usage.completion_tokens,
|
|
2965
|
-
cachedInputTokens
|
|
4057
|
+
inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
|
|
4058
|
+
responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
|
|
4059
|
+
cachedInputTokens,
|
|
4060
|
+
reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
|
|
4061
|
+
...responseEffort ? {
|
|
4062
|
+
reasoningEffort: responseEffort,
|
|
4063
|
+
reasoningEffortSource: "provider_response"
|
|
4064
|
+
} : {},
|
|
4065
|
+
...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {}
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
4068
|
+
function normalizeExecutionReasoningEffort(value) {
|
|
4069
|
+
return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
|
|
4070
|
+
}
|
|
4071
|
+
function openAIRequestExecutionMetadata(params, provider) {
|
|
4072
|
+
const record = params && typeof params === "object" ? params : {};
|
|
4073
|
+
const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
|
|
4074
|
+
const effort = normalizeExecutionReasoningEffort(
|
|
4075
|
+
record.reasoning_effort ?? reasoning?.effort ?? record.thinking_level
|
|
4076
|
+
);
|
|
4077
|
+
const mode = typeof reasoning?.type === "string" ? reasoning.type : typeof reasoning?.mode === "string" ? reasoning.mode : void 0;
|
|
4078
|
+
const rawBudget = reasoning?.budget_tokens ?? record.thinking_budget ?? record.thinking_budget_tokens;
|
|
4079
|
+
const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
|
|
4080
|
+
return {
|
|
4081
|
+
providerUsed: provider,
|
|
4082
|
+
...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
|
|
4083
|
+
...mode ? { reasoningMode: mode } : {},
|
|
4084
|
+
...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
|
|
2966
4085
|
};
|
|
2967
4086
|
}
|
|
2968
4087
|
function wrapStreamForUsageTap(source, finalize, ctx) {
|
|
@@ -3069,6 +4188,7 @@ exports.streamOpenAIRoute = streamOpenAIRoute;
|
|
|
3069
4188
|
exports.toNextResponse = toNextResponse;
|
|
3070
4189
|
exports.withCompression = withCompression;
|
|
3071
4190
|
exports.withMetering = withMetering;
|
|
4191
|
+
exports.withSampling = withSampling;
|
|
3072
4192
|
exports.wrapOpenAI = wrapOpenAI;
|
|
3073
4193
|
//# sourceMappingURL=openai.cjs.map
|
|
3074
4194
|
//# sourceMappingURL=openai.cjs.map
|