@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
|
@@ -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) {
|
|
@@ -1917,14 +2719,16 @@ function createCompressionOnlyResponsesProxy(resource, usageTap, compression) {
|
|
|
1917
2719
|
}
|
|
1918
2720
|
});
|
|
1919
2721
|
}
|
|
1920
|
-
function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2722
|
+
function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
1921
2723
|
const completions = createChatCompletionsProxy(
|
|
1922
2724
|
resource.completions,
|
|
1923
2725
|
usageTap,
|
|
1924
2726
|
defaultContext,
|
|
1925
2727
|
applyVendorHints,
|
|
1926
2728
|
defaultPromptCompression,
|
|
1927
|
-
promptCompressionStats
|
|
2729
|
+
promptCompressionStats,
|
|
2730
|
+
defaultSampling,
|
|
2731
|
+
provider
|
|
1928
2732
|
);
|
|
1929
2733
|
const handler = {
|
|
1930
2734
|
get(target, prop, receiver) {
|
|
@@ -1936,7 +2740,7 @@ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, d
|
|
|
1936
2740
|
};
|
|
1937
2741
|
return new Proxy(resource, handler);
|
|
1938
2742
|
}
|
|
1939
|
-
function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2743
|
+
function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
1940
2744
|
if (!resource || typeof resource !== "object") {
|
|
1941
2745
|
return void 0;
|
|
1942
2746
|
}
|
|
@@ -1951,9 +2755,20 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
1951
2755
|
withUsage,
|
|
1952
2756
|
promptCompression
|
|
1953
2757
|
} = splitUsageOptions(options);
|
|
1954
|
-
const beginRequest =
|
|
2758
|
+
const beginRequest = withRuntimeCompressionContext(
|
|
2759
|
+
resolveBeginRequest(defaultContext, usageContext),
|
|
2760
|
+
"openai",
|
|
2761
|
+
String(params.model)
|
|
2762
|
+
);
|
|
1955
2763
|
const wantsStream = isStreamingRequest(params);
|
|
1956
2764
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2765
|
+
const sampleStartedAt = Date.now();
|
|
2766
|
+
const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
|
|
2767
|
+
usageTap,
|
|
2768
|
+
sampling: defaultSampling,
|
|
2769
|
+
beginRequest,
|
|
2770
|
+
input: params
|
|
2771
|
+
});
|
|
1957
2772
|
const hintedParams = applyVendorHints ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
1958
2773
|
const finalParams = await compressResponsesParamsForCall({
|
|
1959
2774
|
params: hintedParams,
|
|
@@ -1965,13 +2780,14 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
1965
2780
|
withUsage,
|
|
1966
2781
|
operation: "responses.create"
|
|
1967
2782
|
});
|
|
2783
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
1968
2784
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
1969
2785
|
if (wantsStream) {
|
|
1970
2786
|
const apiPromise2 = originalCreate(finalParams, request);
|
|
1971
2787
|
const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
|
|
1972
2788
|
ensureAsyncIterable(rawStream, "responses.create");
|
|
1973
2789
|
const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
|
|
1974
|
-
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
|
|
2790
|
+
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
|
|
1975
2791
|
if (usage) {
|
|
1976
2792
|
ctx.setUsage(usage);
|
|
1977
2793
|
}
|
|
@@ -1981,9 +2797,31 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
1981
2797
|
return wrappedPromise2;
|
|
1982
2798
|
}
|
|
1983
2799
|
const apiPromise = originalCreate(finalParams, request);
|
|
1984
|
-
const wrappedPromise = transformApiPromise(apiPromise, (response) => {
|
|
1985
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
|
|
2800
|
+
const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
|
|
2801
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
|
|
2802
|
+
await captureMeteredOpenAISample({
|
|
2803
|
+
usageTap,
|
|
2804
|
+
sampling: defaultSampling,
|
|
2805
|
+
decision: sampleDecision,
|
|
2806
|
+
ctx,
|
|
2807
|
+
beginRequest,
|
|
2808
|
+
input: params,
|
|
2809
|
+
response,
|
|
2810
|
+
startedAt: sampleStartedAt
|
|
2811
|
+
});
|
|
1986
2812
|
return response;
|
|
2813
|
+
}, async (error) => {
|
|
2814
|
+
await captureMeteredOpenAISample({
|
|
2815
|
+
usageTap,
|
|
2816
|
+
sampling: defaultSampling,
|
|
2817
|
+
decision: sampleDecision,
|
|
2818
|
+
ctx,
|
|
2819
|
+
beginRequest,
|
|
2820
|
+
input: params,
|
|
2821
|
+
error,
|
|
2822
|
+
startedAt: sampleStartedAt
|
|
2823
|
+
});
|
|
2824
|
+
throw error;
|
|
1987
2825
|
});
|
|
1988
2826
|
return wrappedPromise;
|
|
1989
2827
|
}, withUsage);
|
|
@@ -1998,7 +2836,7 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
1998
2836
|
};
|
|
1999
2837
|
return new Proxy(resource, handler);
|
|
2000
2838
|
}
|
|
2001
|
-
function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2839
|
+
function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
2002
2840
|
const originalCreate = resource.create.bind(resource);
|
|
2003
2841
|
const streamCandidate = resource.stream;
|
|
2004
2842
|
const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(resource) : void 0;
|
|
@@ -2009,9 +2847,20 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2009
2847
|
withUsage,
|
|
2010
2848
|
promptCompression
|
|
2011
2849
|
} = splitUsageOptions(options);
|
|
2012
|
-
const beginRequest =
|
|
2850
|
+
const beginRequest = withRuntimeCompressionContext(
|
|
2851
|
+
resolveBeginRequest(defaultContext, usageContext),
|
|
2852
|
+
"openai",
|
|
2853
|
+
String(params.model)
|
|
2854
|
+
);
|
|
2013
2855
|
const wantsStream = isStreamingRequest(params);
|
|
2014
2856
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2857
|
+
const sampleStartedAt = Date.now();
|
|
2858
|
+
const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
|
|
2859
|
+
usageTap,
|
|
2860
|
+
sampling: defaultSampling,
|
|
2861
|
+
beginRequest,
|
|
2862
|
+
input: params
|
|
2863
|
+
});
|
|
2015
2864
|
const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
2016
2865
|
const finalParams = await compressChatParamsForCall({
|
|
2017
2866
|
params: hintedParams,
|
|
@@ -2023,13 +2872,14 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2023
2872
|
withUsage,
|
|
2024
2873
|
operation: "chat.completions.create"
|
|
2025
2874
|
});
|
|
2875
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
2026
2876
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
2027
2877
|
if (wantsStream) {
|
|
2028
2878
|
const apiPromise2 = originalCreate(finalParams, request);
|
|
2029
2879
|
const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
|
|
2030
2880
|
ensureAsyncIterable(rawStream, "chat.completions.create");
|
|
2031
2881
|
const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
|
|
2032
|
-
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
|
|
2882
|
+
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
|
|
2033
2883
|
if (usage) {
|
|
2034
2884
|
ctx.setUsage(usage);
|
|
2035
2885
|
}
|
|
@@ -2039,9 +2889,31 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2039
2889
|
return wrappedPromise2;
|
|
2040
2890
|
}
|
|
2041
2891
|
const apiPromise = originalCreate(finalParams, request);
|
|
2042
|
-
const wrappedPromise = transformApiPromise(apiPromise, (response) => {
|
|
2043
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
|
|
2892
|
+
const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
|
|
2893
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
|
|
2894
|
+
await captureMeteredOpenAISample({
|
|
2895
|
+
usageTap,
|
|
2896
|
+
sampling: defaultSampling,
|
|
2897
|
+
decision: sampleDecision,
|
|
2898
|
+
ctx,
|
|
2899
|
+
beginRequest,
|
|
2900
|
+
input: params,
|
|
2901
|
+
response,
|
|
2902
|
+
startedAt: sampleStartedAt
|
|
2903
|
+
});
|
|
2044
2904
|
return response;
|
|
2905
|
+
}, async (error) => {
|
|
2906
|
+
await captureMeteredOpenAISample({
|
|
2907
|
+
usageTap,
|
|
2908
|
+
sampling: defaultSampling,
|
|
2909
|
+
decision: sampleDecision,
|
|
2910
|
+
ctx,
|
|
2911
|
+
beginRequest,
|
|
2912
|
+
input: params,
|
|
2913
|
+
error,
|
|
2914
|
+
startedAt: sampleStartedAt
|
|
2915
|
+
});
|
|
2916
|
+
throw error;
|
|
2045
2917
|
});
|
|
2046
2918
|
return wrappedPromise;
|
|
2047
2919
|
}, withUsage);
|
|
@@ -2053,7 +2925,11 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2053
2925
|
withUsage,
|
|
2054
2926
|
promptCompression
|
|
2055
2927
|
} = splitUsageOptions(options);
|
|
2056
|
-
const beginRequest =
|
|
2928
|
+
const beginRequest = withRuntimeCompressionContext(
|
|
2929
|
+
resolveBeginRequest(defaultContext, usageContext),
|
|
2930
|
+
"openai",
|
|
2931
|
+
String(params.model)
|
|
2932
|
+
);
|
|
2057
2933
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2058
2934
|
const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
2059
2935
|
const finalParams = await compressChatParamsForCall({
|
|
@@ -2066,12 +2942,17 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2066
2942
|
withUsage,
|
|
2067
2943
|
operation: "chat.completions.stream"
|
|
2068
2944
|
});
|
|
2945
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
2069
2946
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
2070
2947
|
const apiPromise = originalStream(finalParams, request);
|
|
2071
2948
|
const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
|
|
2072
2949
|
ensureAsyncIterable(rawStream, "chat.completions.stream");
|
|
2073
2950
|
const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
|
|
2074
|
-
const usage = await extractUsageFromStream(
|
|
2951
|
+
const usage = await extractUsageFromStream(
|
|
2952
|
+
rawStream,
|
|
2953
|
+
ctx.begin.data.vendorHints,
|
|
2954
|
+
provider
|
|
2955
|
+
);
|
|
2075
2956
|
if (usage) {
|
|
2076
2957
|
ctx.setUsage(usage);
|
|
2077
2958
|
}
|
|
@@ -2095,19 +2976,70 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2095
2976
|
return new Proxy(resource, handler);
|
|
2096
2977
|
}
|
|
2097
2978
|
async function compressChatParamsForCall(args) {
|
|
2098
|
-
|
|
2979
|
+
let compression = resolveEffectivePromptCompressionOptions(
|
|
2099
2980
|
args.defaultPromptCompression,
|
|
2100
2981
|
args.callPromptCompression
|
|
2101
2982
|
);
|
|
2983
|
+
const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
|
|
2984
|
+
const policyDriven = !compression && Boolean(runtimePolicy);
|
|
2985
|
+
if (policyDriven && runtimePolicy) {
|
|
2986
|
+
if (!runtimePolicy.selected) {
|
|
2987
|
+
args.ctx.setUsage({
|
|
2988
|
+
runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
|
|
2989
|
+
});
|
|
2990
|
+
return args.params;
|
|
2991
|
+
}
|
|
2992
|
+
compression = openAIPolicyCompressionOptions(runtimePolicy);
|
|
2993
|
+
if (runtimePolicy.mode === "SHADOW") {
|
|
2994
|
+
const startedAt2 = Date.now();
|
|
2995
|
+
void compressChatParams(
|
|
2996
|
+
args.params,
|
|
2997
|
+
args.usageTap,
|
|
2998
|
+
compression,
|
|
2999
|
+
args.withUsage?.signal
|
|
3000
|
+
).then((outcome2) => {
|
|
3001
|
+
args.ctx.setUsage({
|
|
3002
|
+
runtimeCompression: openAIRuntimeMeasurement({
|
|
3003
|
+
policy: runtimePolicy,
|
|
3004
|
+
telemetry: buildPromptCompressionTelemetry(outcome2.segments),
|
|
3005
|
+
latencyMs: Date.now() - startedAt2,
|
|
3006
|
+
shadow: true,
|
|
3007
|
+
originalPayload: args.params,
|
|
3008
|
+
resultPayload: outcome2.params
|
|
3009
|
+
})
|
|
3010
|
+
});
|
|
3011
|
+
}).catch(() => {
|
|
3012
|
+
args.ctx.setUsage({
|
|
3013
|
+
runtimeCompression: failedRuntimeMeasurement(
|
|
3014
|
+
runtimePolicy,
|
|
3015
|
+
Date.now() - startedAt2
|
|
3016
|
+
)
|
|
3017
|
+
});
|
|
3018
|
+
});
|
|
3019
|
+
return args.params;
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
2102
3022
|
if (!compression) {
|
|
2103
3023
|
return args.params;
|
|
2104
3024
|
}
|
|
3025
|
+
const startedAt = Date.now();
|
|
2105
3026
|
const outcome = await compressChatParams(
|
|
2106
3027
|
args.params,
|
|
2107
3028
|
args.usageTap,
|
|
2108
3029
|
compression,
|
|
2109
3030
|
args.withUsage?.signal
|
|
2110
3031
|
);
|
|
3032
|
+
if (policyDriven && runtimePolicy) {
|
|
3033
|
+
const runtimeMeasurement = openAIRuntimeMeasurement({
|
|
3034
|
+
policy: runtimePolicy,
|
|
3035
|
+
telemetry: buildPromptCompressionTelemetry(outcome.segments),
|
|
3036
|
+
latencyMs: Date.now() - startedAt,
|
|
3037
|
+
originalPayload: args.params,
|
|
3038
|
+
resultPayload: outcome.params
|
|
3039
|
+
});
|
|
3040
|
+
args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
|
|
3041
|
+
if (runtimeMeasurement.decision !== "compressed") return args.params;
|
|
3042
|
+
}
|
|
2111
3043
|
await recordCompressionOutcome({
|
|
2112
3044
|
outcome,
|
|
2113
3045
|
compression,
|
|
@@ -2120,19 +3052,70 @@ async function compressChatParamsForCall(args) {
|
|
|
2120
3052
|
return outcome.params;
|
|
2121
3053
|
}
|
|
2122
3054
|
async function compressResponsesParamsForCall(args) {
|
|
2123
|
-
|
|
3055
|
+
let compression = resolveEffectivePromptCompressionOptions(
|
|
2124
3056
|
args.defaultPromptCompression,
|
|
2125
3057
|
args.callPromptCompression
|
|
2126
3058
|
);
|
|
3059
|
+
const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
|
|
3060
|
+
const policyDriven = !compression && Boolean(runtimePolicy);
|
|
3061
|
+
if (policyDriven && runtimePolicy) {
|
|
3062
|
+
if (!runtimePolicy.selected) {
|
|
3063
|
+
args.ctx.setUsage({
|
|
3064
|
+
runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
|
|
3065
|
+
});
|
|
3066
|
+
return args.params;
|
|
3067
|
+
}
|
|
3068
|
+
compression = openAIPolicyCompressionOptions(runtimePolicy);
|
|
3069
|
+
if (runtimePolicy.mode === "SHADOW") {
|
|
3070
|
+
const startedAt2 = Date.now();
|
|
3071
|
+
void compressResponsesParams(
|
|
3072
|
+
args.params,
|
|
3073
|
+
args.usageTap,
|
|
3074
|
+
compression,
|
|
3075
|
+
args.withUsage?.signal
|
|
3076
|
+
).then((outcome2) => {
|
|
3077
|
+
args.ctx.setUsage({
|
|
3078
|
+
runtimeCompression: openAIRuntimeMeasurement({
|
|
3079
|
+
policy: runtimePolicy,
|
|
3080
|
+
telemetry: buildPromptCompressionTelemetry(outcome2.segments),
|
|
3081
|
+
latencyMs: Date.now() - startedAt2,
|
|
3082
|
+
shadow: true,
|
|
3083
|
+
originalPayload: args.params,
|
|
3084
|
+
resultPayload: outcome2.params
|
|
3085
|
+
})
|
|
3086
|
+
});
|
|
3087
|
+
}).catch(() => {
|
|
3088
|
+
args.ctx.setUsage({
|
|
3089
|
+
runtimeCompression: failedRuntimeMeasurement(
|
|
3090
|
+
runtimePolicy,
|
|
3091
|
+
Date.now() - startedAt2
|
|
3092
|
+
)
|
|
3093
|
+
});
|
|
3094
|
+
});
|
|
3095
|
+
return args.params;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
2127
3098
|
if (!compression) {
|
|
2128
3099
|
return args.params;
|
|
2129
3100
|
}
|
|
3101
|
+
const startedAt = Date.now();
|
|
2130
3102
|
const outcome = await compressResponsesParams(
|
|
2131
3103
|
args.params,
|
|
2132
3104
|
args.usageTap,
|
|
2133
3105
|
compression,
|
|
2134
3106
|
args.withUsage?.signal
|
|
2135
3107
|
);
|
|
3108
|
+
if (policyDriven && runtimePolicy) {
|
|
3109
|
+
const runtimeMeasurement = openAIRuntimeMeasurement({
|
|
3110
|
+
policy: runtimePolicy,
|
|
3111
|
+
telemetry: buildPromptCompressionTelemetry(outcome.segments),
|
|
3112
|
+
latencyMs: Date.now() - startedAt,
|
|
3113
|
+
originalPayload: args.params,
|
|
3114
|
+
resultPayload: outcome.params
|
|
3115
|
+
});
|
|
3116
|
+
args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
|
|
3117
|
+
if (runtimeMeasurement.decision !== "compressed") return args.params;
|
|
3118
|
+
}
|
|
2136
3119
|
await recordCompressionOutcome({
|
|
2137
3120
|
outcome,
|
|
2138
3121
|
compression,
|
|
@@ -2144,6 +3127,79 @@ async function compressResponsesParamsForCall(args) {
|
|
|
2144
3127
|
});
|
|
2145
3128
|
return outcome.params;
|
|
2146
3129
|
}
|
|
3130
|
+
function openAIPolicyCompressionOptions(policy) {
|
|
3131
|
+
const roles = Object.fromEntries(
|
|
3132
|
+
["system", "user", "tool", "assistant"].filter((role) => policy.snapshot.scope.messageRoles.includes(role)).map((role) => [role, true])
|
|
3133
|
+
);
|
|
3134
|
+
return {
|
|
3135
|
+
provider: "usagetap",
|
|
3136
|
+
roles,
|
|
3137
|
+
minContextTokens: policy.snapshot.scope.minimumTokens,
|
|
3138
|
+
failOpen: true
|
|
3139
|
+
};
|
|
3140
|
+
}
|
|
3141
|
+
function bypassedRuntimeMeasurement(policy) {
|
|
3142
|
+
return {
|
|
3143
|
+
policyId: policy.policyId,
|
|
3144
|
+
policyVersionId: policy.policyVersionId,
|
|
3145
|
+
policyVersion: policy.version,
|
|
3146
|
+
rolloutMode: policy.mode,
|
|
3147
|
+
decision: "bypassed",
|
|
3148
|
+
reasonCode: "not_in_rollout_sample",
|
|
3149
|
+
methodsAttempted: [],
|
|
3150
|
+
methodsApplied: [],
|
|
3151
|
+
originalTokens: 0,
|
|
3152
|
+
resultTokens: 0,
|
|
3153
|
+
savedTokens: 0,
|
|
3154
|
+
reductionPercentage: 0,
|
|
3155
|
+
compressionLatencyMs: 0,
|
|
3156
|
+
compressionComputeCostUsd: 0,
|
|
3157
|
+
financialsEstimated: true
|
|
3158
|
+
};
|
|
3159
|
+
}
|
|
3160
|
+
function failedRuntimeMeasurement(policy, latencyMs) {
|
|
3161
|
+
return {
|
|
3162
|
+
...bypassedRuntimeMeasurement(policy),
|
|
3163
|
+
decision: "fallback",
|
|
3164
|
+
reasonCode: "transformation_failed",
|
|
3165
|
+
methodsAttempted: ["deterministic"],
|
|
3166
|
+
compressionLatencyMs: latencyMs
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
function openAIRuntimeMeasurement(input) {
|
|
3170
|
+
const telemetry = input.telemetry;
|
|
3171
|
+
const originalTokens = telemetry?.originalTokens ?? estimatePromptTokens(JSON.stringify(input.originalPayload));
|
|
3172
|
+
const resultTokens = telemetry?.compressedTokens ?? originalTokens;
|
|
3173
|
+
const savedTokens = Math.max(0, telemetry?.savedTokens ?? 0);
|
|
3174
|
+
const reductionPercentage = originalTokens > 0 ? savedTokens / originalTokens * 100 : 0;
|
|
3175
|
+
const belowMinimumSize = originalTokens < input.policy.snapshot.scope.minimumTokens;
|
|
3176
|
+
const latencyExceeded = input.latencyMs > input.policy.snapshot.gates.maximumLatencyMs;
|
|
3177
|
+
const belowSavings = savedTokens < input.policy.snapshot.gates.minimumTokensRemoved || reductionPercentage < input.policy.snapshot.gates.minimumReductionPercentage;
|
|
3178
|
+
const accepted = !belowMinimumSize && !latencyExceeded && !belowSavings;
|
|
3179
|
+
const deepText = input.policy.snapshot.methods.deepText.enabled && !input.policy.snapshot.rollout.deterministicOnly && (input.policy.mode !== "CANARY" || input.policy.snapshot.rollout.deepTextCanaryAllowed);
|
|
3180
|
+
const reasonCode = belowMinimumSize ? "below_minimum_size" : latencyExceeded ? "latency_budget_exceeded" : belowSavings ? "below_minimum_savings" : void 0;
|
|
3181
|
+
const measurement = {
|
|
3182
|
+
policyId: input.policy.policyId,
|
|
3183
|
+
policyVersionId: input.policy.policyVersionId,
|
|
3184
|
+
policyVersion: input.policy.version,
|
|
3185
|
+
rolloutMode: input.policy.mode,
|
|
3186
|
+
decision: accepted ? input.shadow ? "bypassed" : "compressed" : latencyExceeded ? "fallback" : "skipped",
|
|
3187
|
+
...reasonCode ? { reasonCode } : {},
|
|
3188
|
+
methodsAttempted: [
|
|
3189
|
+
"deterministic",
|
|
3190
|
+
...deepText ? ["deep_text"] : []
|
|
3191
|
+
],
|
|
3192
|
+
methodsApplied: accepted ? ["deterministic"] : [],
|
|
3193
|
+
originalTokens,
|
|
3194
|
+
resultTokens: accepted ? resultTokens : originalTokens,
|
|
3195
|
+
savedTokens: accepted ? savedTokens : 0,
|
|
3196
|
+
reductionPercentage: accepted ? reductionPercentage : 0,
|
|
3197
|
+
compressionLatencyMs: input.latencyMs,
|
|
3198
|
+
compressionComputeCostUsd: 0,
|
|
3199
|
+
financialsEstimated: true
|
|
3200
|
+
};
|
|
3201
|
+
return measurement;
|
|
3202
|
+
}
|
|
2147
3203
|
async function recordCompressionOutcome(args) {
|
|
2148
3204
|
const telemetry = buildPromptCompressionTelemetry(args.outcome.segments);
|
|
2149
3205
|
if (!telemetry) {
|
|
@@ -2197,6 +3253,10 @@ async function compressChatParams(params, usageTap, compression, signal) {
|
|
|
2197
3253
|
const result = await usageTap.compressPromptMessages(source, {
|
|
2198
3254
|
provider: "usagetap",
|
|
2199
3255
|
failOpen: compression.failOpen,
|
|
3256
|
+
mode: compression.mode,
|
|
3257
|
+
latencyBudgetMs: compression.latencyBudgetMs,
|
|
3258
|
+
compactEmptyUserMessages: compression.compactEmptyUserMessages,
|
|
3259
|
+
compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
|
|
2200
3260
|
aggressiveness: resolveMessageEndpointAggressiveness(compression),
|
|
2201
3261
|
signal
|
|
2202
3262
|
});
|
|
@@ -2662,23 +3722,53 @@ function resolveBeginRequest(defaults, override) {
|
|
|
2662
3722
|
}
|
|
2663
3723
|
const tags = mergeTags(base.tags, current.tags);
|
|
2664
3724
|
const begin = { customerId };
|
|
3725
|
+
const runtimeCompressionContext = current.runtimeCompressionContext ?? base.runtimeCompressionContext;
|
|
3726
|
+
if (runtimeCompressionContext) {
|
|
3727
|
+
begin.runtimeCompressionContext = runtimeCompressionContext;
|
|
3728
|
+
}
|
|
2665
3729
|
const requested = current.requested ?? base.requested;
|
|
2666
3730
|
if (requested) begin.requested = requested;
|
|
2667
3731
|
const feature = current.feature ?? base.feature;
|
|
2668
3732
|
if (feature) begin.feature = feature;
|
|
3733
|
+
const runId = current.runId ?? base.runId;
|
|
3734
|
+
if (runId) begin.runId = runId;
|
|
2669
3735
|
const idempotency = current.idempotency ?? base.idempotency;
|
|
2670
3736
|
if (idempotency) begin.idempotency = idempotency;
|
|
2671
3737
|
const customerName = current.customerName ?? base.customerName;
|
|
2672
3738
|
if (customerName) begin.customerName = customerName;
|
|
2673
3739
|
const customerEmail = current.customerEmail ?? base.customerEmail;
|
|
2674
3740
|
if (customerEmail) begin.customerEmail = customerEmail;
|
|
3741
|
+
const customerUserId = current.customerUserId ?? base.customerUserId;
|
|
3742
|
+
if (customerUserId) begin.customerUserId = customerUserId;
|
|
3743
|
+
const customerUserName = current.customerUserName ?? base.customerUserName;
|
|
3744
|
+
if (customerUserName) begin.customerUserName = customerUserName;
|
|
3745
|
+
const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
|
|
3746
|
+
if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
|
|
3747
|
+
const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
|
|
3748
|
+
if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
|
|
3749
|
+
const holdUsd = current.holdUsd ?? base.holdUsd;
|
|
3750
|
+
if (typeof holdUsd === "number") begin.holdUsd = holdUsd;
|
|
3751
|
+
const batch = current.batch ?? base.batch;
|
|
3752
|
+
if (typeof batch === "boolean") begin.batch = batch;
|
|
3753
|
+
const pricingMode = current.pricingMode ?? base.pricingMode;
|
|
3754
|
+
if (pricingMode) begin.pricingMode = pricingMode;
|
|
2675
3755
|
if (tags?.length) {
|
|
2676
3756
|
begin.tags = tags;
|
|
2677
3757
|
}
|
|
2678
3758
|
return begin;
|
|
2679
3759
|
}
|
|
2680
|
-
function
|
|
2681
|
-
|
|
3760
|
+
function withRuntimeCompressionContext(begin, provider, model) {
|
|
3761
|
+
return {
|
|
3762
|
+
...begin,
|
|
3763
|
+
runtimeCompressionContext: {
|
|
3764
|
+
...begin.runtimeCompressionContext ?? {},
|
|
3765
|
+
provider,
|
|
3766
|
+
model
|
|
3767
|
+
}
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
function transformApiPromise(apiPromise, onResolve, onReject) {
|
|
3771
|
+
const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
|
|
2682
3772
|
if (isObjectRecord(apiPromise)) {
|
|
2683
3773
|
const proto = Object.getPrototypeOf(apiPromise);
|
|
2684
3774
|
if (proto) {
|
|
@@ -2813,12 +3903,12 @@ function applyResponsesVendorHints(params, hints) {
|
|
|
2813
3903
|
}
|
|
2814
3904
|
return next;
|
|
2815
3905
|
}
|
|
2816
|
-
async function extractUsageFromStream(stream, hints) {
|
|
3906
|
+
async function extractUsageFromStream(stream, hints, provider = "openai") {
|
|
2817
3907
|
const finalPayload = await resolveStreamFinalPayload(stream);
|
|
2818
3908
|
if (!finalPayload) {
|
|
2819
3909
|
return void 0;
|
|
2820
3910
|
}
|
|
2821
|
-
return inferUsageFromResponse(finalPayload, hints);
|
|
3911
|
+
return inferUsageFromResponse(finalPayload, hints, provider);
|
|
2822
3912
|
}
|
|
2823
3913
|
async function resolveStreamFinalPayload(stream) {
|
|
2824
3914
|
if (!stream || typeof stream !== "object") {
|
|
@@ -2891,14 +3981,14 @@ function setHeaderIfPossible(res, key, value) {
|
|
|
2891
3981
|
res.setHeader(key, value);
|
|
2892
3982
|
}
|
|
2893
3983
|
}
|
|
2894
|
-
function tryInferUsage(response, hints, extractor, ctx) {
|
|
3984
|
+
function tryInferUsage(response, hints, extractor, ctx, provider = "openai") {
|
|
2895
3985
|
const explicit = extractor?.(response);
|
|
2896
|
-
const inferred = explicit ?? inferUsageFromResponse(response, hints);
|
|
3986
|
+
const inferred = explicit ?? inferUsageFromResponse(response, hints, provider);
|
|
2897
3987
|
if (inferred) {
|
|
2898
3988
|
ctx.setUsage(inferred);
|
|
2899
3989
|
}
|
|
2900
3990
|
}
|
|
2901
|
-
function inferUsageFromResponse(response, hints) {
|
|
3991
|
+
function inferUsageFromResponse(response, hints, provider = "openai") {
|
|
2902
3992
|
if (!response || typeof response !== "object") {
|
|
2903
3993
|
return void 0;
|
|
2904
3994
|
}
|
|
@@ -2906,12 +3996,41 @@ function inferUsageFromResponse(response, hints) {
|
|
|
2906
3996
|
if (!candidate.usage) {
|
|
2907
3997
|
return void 0;
|
|
2908
3998
|
}
|
|
2909
|
-
const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
|
|
3999
|
+
const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
|
|
4000
|
+
const responseEffort = normalizeExecutionReasoningEffort(
|
|
4001
|
+
candidate.reasoning?.effort
|
|
4002
|
+
);
|
|
2910
4003
|
return {
|
|
4004
|
+
providerUsed: provider,
|
|
2911
4005
|
modelUsed: candidate.model ?? hints?.preferredModel,
|
|
2912
|
-
inputTokens: candidate.usage.prompt_tokens,
|
|
2913
|
-
responseTokens: candidate.usage.completion_tokens,
|
|
2914
|
-
cachedInputTokens
|
|
4006
|
+
inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
|
|
4007
|
+
responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
|
|
4008
|
+
cachedInputTokens,
|
|
4009
|
+
reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
|
|
4010
|
+
...responseEffort ? {
|
|
4011
|
+
reasoningEffort: responseEffort,
|
|
4012
|
+
reasoningEffortSource: "provider_response"
|
|
4013
|
+
} : {},
|
|
4014
|
+
...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {}
|
|
4015
|
+
};
|
|
4016
|
+
}
|
|
4017
|
+
function normalizeExecutionReasoningEffort(value) {
|
|
4018
|
+
return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
|
|
4019
|
+
}
|
|
4020
|
+
function openAIRequestExecutionMetadata(params, provider) {
|
|
4021
|
+
const record = params && typeof params === "object" ? params : {};
|
|
4022
|
+
const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
|
|
4023
|
+
const effort = normalizeExecutionReasoningEffort(
|
|
4024
|
+
record.reasoning_effort ?? reasoning?.effort ?? record.thinking_level
|
|
4025
|
+
);
|
|
4026
|
+
const mode = typeof reasoning?.type === "string" ? reasoning.type : typeof reasoning?.mode === "string" ? reasoning.mode : void 0;
|
|
4027
|
+
const rawBudget = reasoning?.budget_tokens ?? record.thinking_budget ?? record.thinking_budget_tokens;
|
|
4028
|
+
const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
|
|
4029
|
+
return {
|
|
4030
|
+
providerUsed: provider,
|
|
4031
|
+
...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
|
|
4032
|
+
...mode ? { reasoningMode: mode } : {},
|
|
4033
|
+
...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
|
|
2915
4034
|
};
|
|
2916
4035
|
}
|
|
2917
4036
|
function wrapStreamForUsageTap(source, finalize, ctx) {
|
|
@@ -3013,12 +4132,28 @@ function isIteratorResult(value) {
|
|
|
3013
4132
|
|
|
3014
4133
|
// src/adapters/openrouter.ts
|
|
3015
4134
|
function createOpenRouterAdapter(init) {
|
|
3016
|
-
return createOpenAIAdapter(init);
|
|
4135
|
+
return createOpenAIAdapter({ ...init, provider: "openrouter" });
|
|
4136
|
+
}
|
|
4137
|
+
function withMetering2(client, customer) {
|
|
4138
|
+
return withMetering(
|
|
4139
|
+
client,
|
|
4140
|
+
typeof customer === "string" ? { customerId: customer, provider: "openrouter" } : { ...customer, provider: "openrouter" }
|
|
4141
|
+
);
|
|
4142
|
+
}
|
|
4143
|
+
function wrapOpenAI2(client, usageTap, options = {}) {
|
|
4144
|
+
return wrapOpenAI(client, usageTap, {
|
|
4145
|
+
...options,
|
|
4146
|
+
provider: "openrouter"
|
|
4147
|
+
});
|
|
4148
|
+
}
|
|
4149
|
+
function withSampling2(client, options = {}) {
|
|
4150
|
+
return withSampling(client, { ...options, provider: "openrouter" });
|
|
3017
4151
|
}
|
|
3018
4152
|
|
|
3019
4153
|
exports.createOpenRouterAdapter = createOpenRouterAdapter;
|
|
3020
4154
|
exports.withCompression = withCompression;
|
|
3021
|
-
exports.withMetering =
|
|
3022
|
-
exports.
|
|
4155
|
+
exports.withMetering = withMetering2;
|
|
4156
|
+
exports.withSampling = withSampling2;
|
|
4157
|
+
exports.wrapOpenAI = wrapOpenAI2;
|
|
3023
4158
|
//# sourceMappingURL=index.cjs.map
|
|
3024
4159
|
//# sourceMappingURL=index.cjs.map
|