@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
|
@@ -240,6 +240,11 @@ async function compressMessagesWithUsageTap(options) {
|
|
|
240
240
|
aggressiveness,
|
|
241
241
|
"UsageTap prompt message compression"
|
|
242
242
|
);
|
|
243
|
+
if (options.latencyBudgetMs !== void 0 && (!Number.isFinite(options.latencyBudgetMs) || options.latencyBudgetMs < 0)) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
"UsageTap prompt message compression latencyBudgetMs must be a non-negative number"
|
|
246
|
+
);
|
|
247
|
+
}
|
|
243
248
|
const original = stableStringifyInput(options.input);
|
|
244
249
|
const headers = {
|
|
245
250
|
"content-type": "application/json"
|
|
@@ -254,7 +259,15 @@ async function compressMessagesWithUsageTap(options) {
|
|
|
254
259
|
headers,
|
|
255
260
|
body: JSON.stringify({
|
|
256
261
|
...cloneInputRecord(options.input),
|
|
257
|
-
compression_settings: {
|
|
262
|
+
compression_settings: {
|
|
263
|
+
aggressiveness,
|
|
264
|
+
...options.mode === void 0 ? {} : { mode: options.mode },
|
|
265
|
+
...options.latencyBudgetMs === void 0 ? {} : { latency_budget_ms: options.latencyBudgetMs },
|
|
266
|
+
...options.compactEmptyUserMessages === void 0 ? {} : { compact_empty_user_messages: options.compactEmptyUserMessages },
|
|
267
|
+
...options.compactDuplicateUserTextParts === void 0 ? {} : {
|
|
268
|
+
compact_duplicate_user_text_parts: options.compactDuplicateUserTextParts
|
|
269
|
+
}
|
|
270
|
+
}
|
|
258
271
|
}),
|
|
259
272
|
signal: options.signal
|
|
260
273
|
}
|
|
@@ -750,10 +763,358 @@ function scalarToToon(value) {
|
|
|
750
763
|
return JSON.stringify(text);
|
|
751
764
|
}
|
|
752
765
|
|
|
766
|
+
// src/resources.ts
|
|
767
|
+
var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
|
|
768
|
+
var DEFAULT_GATEWAY_BASE_URL = "https://gateway.usagetap.com";
|
|
769
|
+
function normalizedBaseUrl(value) {
|
|
770
|
+
return `${value.replace(/\/+$/, "")}/`;
|
|
771
|
+
}
|
|
772
|
+
function errorMessage(payload, status) {
|
|
773
|
+
if (payload && typeof payload === "object") {
|
|
774
|
+
const record = payload;
|
|
775
|
+
const error = record.error;
|
|
776
|
+
if (error && typeof error === "object") {
|
|
777
|
+
const message2 = error.message;
|
|
778
|
+
if (typeof message2 === "string" && message2) return message2;
|
|
779
|
+
}
|
|
780
|
+
const message = record.message;
|
|
781
|
+
if (typeof message === "string" && message) return message;
|
|
782
|
+
}
|
|
783
|
+
return `UsageTap request failed with HTTP ${status}`;
|
|
784
|
+
}
|
|
785
|
+
function errorCode(status) {
|
|
786
|
+
if (status === 401 || status === 403) return "USAGETAP_AUTH_ERROR";
|
|
787
|
+
if (status === 429) return "USAGETAP_RATE_LIMITED";
|
|
788
|
+
if (status >= 500) return "USAGETAP_SERVER_ERROR";
|
|
789
|
+
return "USAGETAP_BAD_REQUEST";
|
|
790
|
+
}
|
|
791
|
+
var ResourceTransport = class {
|
|
792
|
+
baseUrl;
|
|
793
|
+
apiKey;
|
|
794
|
+
fetchImpl;
|
|
795
|
+
defaultHeaders;
|
|
796
|
+
sdkVersion;
|
|
797
|
+
constructor(baseUrl, config) {
|
|
798
|
+
this.baseUrl = normalizedBaseUrl(baseUrl);
|
|
799
|
+
this.apiKey = config.apiKey;
|
|
800
|
+
this.fetchImpl = config.fetchImpl;
|
|
801
|
+
this.defaultHeaders = config.headers ?? {};
|
|
802
|
+
this.sdkVersion = config.sdkVersion;
|
|
803
|
+
}
|
|
804
|
+
async request(request) {
|
|
805
|
+
const body = request.body === void 0 ? void 0 : JSON.stringify(request.body);
|
|
806
|
+
const headers = {
|
|
807
|
+
...this.defaultHeaders,
|
|
808
|
+
accept: request.response === "data" ? CANONICAL_MEDIA_TYPE : request.response === "ndjson" ? "application/x-ndjson" : "application/json",
|
|
809
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
810
|
+
"x-usage-sdk": `js/${this.sdkVersion}`,
|
|
811
|
+
...body ? { "content-type": "application/json" } : {},
|
|
812
|
+
...request.options?.idempotencyKey ? { "idempotency-key": request.options.idempotencyKey } : {},
|
|
813
|
+
...request.options?.headers
|
|
814
|
+
};
|
|
815
|
+
let response;
|
|
816
|
+
try {
|
|
817
|
+
response = await this.fetchImpl(
|
|
818
|
+
new URL(request.path.replace(/^\/+/, ""), this.baseUrl),
|
|
819
|
+
{
|
|
820
|
+
method: request.method,
|
|
821
|
+
headers,
|
|
822
|
+
body,
|
|
823
|
+
signal: request.options?.signal
|
|
824
|
+
}
|
|
825
|
+
);
|
|
826
|
+
} catch (error) {
|
|
827
|
+
throw new UsageTapError(
|
|
828
|
+
"USAGETAP_NETWORK_ERROR",
|
|
829
|
+
"Failed to reach UsageTap",
|
|
830
|
+
{ retryable: true, cause: error }
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
const requestId = response.headers.get("x-request-id") ?? response.headers.get("x-usage-correlation-id") ?? void 0;
|
|
834
|
+
const text = await response.text();
|
|
835
|
+
let payload;
|
|
836
|
+
if (text && request.response !== "ndjson") {
|
|
837
|
+
try {
|
|
838
|
+
payload = JSON.parse(text);
|
|
839
|
+
} catch (error) {
|
|
840
|
+
throw new UsageTapError(
|
|
841
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
842
|
+
"UsageTap returned invalid JSON",
|
|
843
|
+
{ status: response.status, correlationId: requestId, cause: error }
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (!response.ok) {
|
|
848
|
+
throw new UsageTapError(
|
|
849
|
+
errorCode(response.status),
|
|
850
|
+
errorMessage(payload, response.status),
|
|
851
|
+
{
|
|
852
|
+
status: response.status,
|
|
853
|
+
retryable: response.status === 429 || response.status >= 500,
|
|
854
|
+
correlationId: requestId,
|
|
855
|
+
details: payload && typeof payload === "object" ? payload : void 0
|
|
856
|
+
}
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
if (request.response === "ndjson") {
|
|
860
|
+
if (!text.trim()) return [];
|
|
861
|
+
try {
|
|
862
|
+
return text.trim().split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
863
|
+
} catch (error) {
|
|
864
|
+
throw new UsageTapError(
|
|
865
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
866
|
+
"UsageTap returned invalid NDJSON",
|
|
867
|
+
{ status: response.status, correlationId: requestId, cause: error }
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (request.response === "data") {
|
|
872
|
+
if (!payload || typeof payload !== "object" || !("data" in payload)) {
|
|
873
|
+
throw new UsageTapError(
|
|
874
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
875
|
+
"UsageTap response missing data",
|
|
876
|
+
{ status: response.status, correlationId: requestId }
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
return payload.data;
|
|
880
|
+
}
|
|
881
|
+
if (payload === void 0) {
|
|
882
|
+
throw new UsageTapError(
|
|
883
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
884
|
+
"UsageTap response was empty",
|
|
885
|
+
{ status: response.status, correlationId: requestId }
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
return payload;
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
function resourceId(value, keys, label) {
|
|
892
|
+
const id = typeof value === "string" ? value : keys.map((key) => value[key]).find((candidate) => Boolean(candidate?.trim()));
|
|
893
|
+
if (!id?.trim()) {
|
|
894
|
+
throw new UsageTapError(
|
|
895
|
+
"USAGETAP_BAD_REQUEST",
|
|
896
|
+
`${label} requires a non-empty ID`
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
return id.trim();
|
|
900
|
+
}
|
|
901
|
+
function terminalSummary(status) {
|
|
902
|
+
return status === "COMPLETE" || status === "FAILED";
|
|
903
|
+
}
|
|
904
|
+
function terminalGatewayBatch(status) {
|
|
905
|
+
return ["completed", "failed", "expired", "cancelled"].includes(status);
|
|
906
|
+
}
|
|
907
|
+
function validateWaitOptions(options) {
|
|
908
|
+
const pollIntervalMs = options.pollIntervalMs ?? 1500;
|
|
909
|
+
const timeoutMs = options.timeoutMs ?? 3 * 6e4;
|
|
910
|
+
if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 0) {
|
|
911
|
+
throw new UsageTapError(
|
|
912
|
+
"USAGETAP_BAD_REQUEST",
|
|
913
|
+
"pollIntervalMs must be a non-negative number"
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
|
|
917
|
+
throw new UsageTapError(
|
|
918
|
+
"USAGETAP_BAD_REQUEST",
|
|
919
|
+
"timeoutMs must be a positive number"
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
return { pollIntervalMs, timeoutMs };
|
|
923
|
+
}
|
|
924
|
+
var SummarizationResource = class {
|
|
925
|
+
summaries;
|
|
926
|
+
batches;
|
|
927
|
+
profiles;
|
|
928
|
+
measurements;
|
|
929
|
+
transport;
|
|
930
|
+
constructor(config) {
|
|
931
|
+
this.transport = new ResourceTransport(config.apiBaseUrl, config);
|
|
932
|
+
this.summaries = {
|
|
933
|
+
create: (params, options) => this.transport.request({
|
|
934
|
+
method: "POST",
|
|
935
|
+
path: "/v1/compression/summaries",
|
|
936
|
+
body: params,
|
|
937
|
+
options,
|
|
938
|
+
response: "data"
|
|
939
|
+
}),
|
|
940
|
+
retrieve: (jobId, options) => this.transport.request({
|
|
941
|
+
method: "GET",
|
|
942
|
+
path: `/v1/compression/jobs/${encodeURIComponent(
|
|
943
|
+
resourceId(jobId, ["jobId"], "summaries.retrieve")
|
|
944
|
+
)}`,
|
|
945
|
+
options,
|
|
946
|
+
response: "data"
|
|
947
|
+
}),
|
|
948
|
+
wait: (job, options) => this.waitForSummary(job, options)
|
|
949
|
+
};
|
|
950
|
+
this.batches = {
|
|
951
|
+
create: (params, options) => this.transport.request({
|
|
952
|
+
method: "POST",
|
|
953
|
+
path: "/v1/compression/batches",
|
|
954
|
+
body: params,
|
|
955
|
+
options,
|
|
956
|
+
response: "data"
|
|
957
|
+
}),
|
|
958
|
+
retrieve: (batchId, options) => this.transport.request({
|
|
959
|
+
method: "GET",
|
|
960
|
+
path: `/v1/compression/batches/${encodeURIComponent(
|
|
961
|
+
resourceId(batchId, ["batchId"], "summarization.batches.retrieve")
|
|
962
|
+
)}`,
|
|
963
|
+
options,
|
|
964
|
+
response: "data"
|
|
965
|
+
}),
|
|
966
|
+
wait: (batch, options) => this.waitForBatch(batch, options)
|
|
967
|
+
};
|
|
968
|
+
this.profiles = {
|
|
969
|
+
retrieve: (profile, options) => this.transport.request({
|
|
970
|
+
method: "GET",
|
|
971
|
+
path: `/v1/compression/profiles/${encodeURIComponent(
|
|
972
|
+
resourceId(profile, [], "summarization.profiles.retrieve")
|
|
973
|
+
)}`,
|
|
974
|
+
options,
|
|
975
|
+
response: "data"
|
|
976
|
+
})
|
|
977
|
+
};
|
|
978
|
+
this.measurements = {
|
|
979
|
+
create: (params, options) => this.transport.request({
|
|
980
|
+
method: "POST",
|
|
981
|
+
path: "/v1/compression/measurements",
|
|
982
|
+
body: params,
|
|
983
|
+
options,
|
|
984
|
+
response: "data"
|
|
985
|
+
})
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
async waitForSummary(value, options = {}) {
|
|
989
|
+
const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
|
|
990
|
+
const deadline = Date.now() + timeoutMs;
|
|
991
|
+
let job = typeof value === "string" ? await this.summaries.retrieve(value, options) : value;
|
|
992
|
+
while (!terminalSummary(job.status)) {
|
|
993
|
+
if (Date.now() >= deadline) {
|
|
994
|
+
throw new UsageTapError(
|
|
995
|
+
"USAGETAP_RETRY_EXHAUSTED",
|
|
996
|
+
`Summarization job ${job.jobId} did not finish before timeout`,
|
|
997
|
+
{ retryable: true }
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
await sleep(pollIntervalMs, options.signal);
|
|
1001
|
+
job = await this.summaries.retrieve(job.jobId, options);
|
|
1002
|
+
}
|
|
1003
|
+
return job;
|
|
1004
|
+
}
|
|
1005
|
+
async waitForBatch(value, options = {}) {
|
|
1006
|
+
const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
|
|
1007
|
+
const deadline = Date.now() + timeoutMs;
|
|
1008
|
+
let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
|
|
1009
|
+
while (!terminalSummary(batch.status)) {
|
|
1010
|
+
if (Date.now() >= deadline) {
|
|
1011
|
+
throw new UsageTapError(
|
|
1012
|
+
"USAGETAP_RETRY_EXHAUSTED",
|
|
1013
|
+
`Summarization batch ${batch.batchId} did not finish before timeout`,
|
|
1014
|
+
{ retryable: true }
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
await sleep(pollIntervalMs, options.signal);
|
|
1018
|
+
batch = await this.batches.retrieve(batch.batchId, options);
|
|
1019
|
+
}
|
|
1020
|
+
return batch;
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
var GatewayResource = class {
|
|
1024
|
+
chat;
|
|
1025
|
+
models;
|
|
1026
|
+
batches;
|
|
1027
|
+
transport;
|
|
1028
|
+
idempotencyGenerator;
|
|
1029
|
+
constructor(config) {
|
|
1030
|
+
this.transport = new ResourceTransport(
|
|
1031
|
+
config.gatewayBaseUrl ?? DEFAULT_GATEWAY_BASE_URL,
|
|
1032
|
+
config
|
|
1033
|
+
);
|
|
1034
|
+
this.idempotencyGenerator = config.idempotencyGenerator ?? createIdempotencyKey;
|
|
1035
|
+
this.chat = {
|
|
1036
|
+
completions: {
|
|
1037
|
+
create: (params, options) => this.transport.request({
|
|
1038
|
+
method: "POST",
|
|
1039
|
+
path: "/v1/chat/completions",
|
|
1040
|
+
body: params,
|
|
1041
|
+
options,
|
|
1042
|
+
response: "json"
|
|
1043
|
+
})
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
this.models = {
|
|
1047
|
+
list: (options) => this.transport.request({
|
|
1048
|
+
method: "GET",
|
|
1049
|
+
path: "/v1/models",
|
|
1050
|
+
options,
|
|
1051
|
+
response: "json"
|
|
1052
|
+
})
|
|
1053
|
+
};
|
|
1054
|
+
this.batches = {
|
|
1055
|
+
create: (params, options = {}) => this.transport.request({
|
|
1056
|
+
method: "POST",
|
|
1057
|
+
path: "/v1/batches",
|
|
1058
|
+
body: params,
|
|
1059
|
+
options: {
|
|
1060
|
+
...options,
|
|
1061
|
+
idempotencyKey: options.idempotencyKey ?? this.idempotencyGenerator()
|
|
1062
|
+
},
|
|
1063
|
+
response: "json"
|
|
1064
|
+
}),
|
|
1065
|
+
retrieve: (batchId, options) => this.transport.request({
|
|
1066
|
+
method: "GET",
|
|
1067
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1068
|
+
resourceId(batchId, ["id"], "gateway.batches.retrieve")
|
|
1069
|
+
)}`,
|
|
1070
|
+
options,
|
|
1071
|
+
response: "json"
|
|
1072
|
+
}),
|
|
1073
|
+
wait: (batch, options) => this.waitForBatch(batch, options),
|
|
1074
|
+
cancel: (batchId, options) => this.transport.request({
|
|
1075
|
+
method: "POST",
|
|
1076
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1077
|
+
resourceId(batchId, ["id"], "gateway.batches.cancel")
|
|
1078
|
+
)}/cancel`,
|
|
1079
|
+
options,
|
|
1080
|
+
response: "json"
|
|
1081
|
+
}),
|
|
1082
|
+
results: (batchId, options) => this.transport.request({
|
|
1083
|
+
method: "GET",
|
|
1084
|
+
path: `/v1/batches/${encodeURIComponent(
|
|
1085
|
+
resourceId(batchId, ["id"], "gateway.batches.results")
|
|
1086
|
+
)}/results`,
|
|
1087
|
+
options,
|
|
1088
|
+
response: "ndjson"
|
|
1089
|
+
})
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
async waitForBatch(value, options = {}) {
|
|
1093
|
+
const { pollIntervalMs, timeoutMs } = validateWaitOptions(options);
|
|
1094
|
+
const deadline = Date.now() + timeoutMs;
|
|
1095
|
+
let batch = typeof value === "string" ? await this.batches.retrieve(value, options) : value;
|
|
1096
|
+
while (!terminalGatewayBatch(batch.status)) {
|
|
1097
|
+
if (Date.now() >= deadline) {
|
|
1098
|
+
throw new UsageTapError(
|
|
1099
|
+
"USAGETAP_RETRY_EXHAUSTED",
|
|
1100
|
+
`Gateway batch ${batch.id} did not finish before timeout`,
|
|
1101
|
+
{ retryable: true }
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
await sleep(pollIntervalMs, options.signal);
|
|
1105
|
+
batch = await this.batches.retrieve(batch.id, options);
|
|
1106
|
+
}
|
|
1107
|
+
return batch;
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
|
|
753
1111
|
// src/client.ts
|
|
754
1112
|
var CALL_BEGIN_PATH = "call_begin";
|
|
755
1113
|
var CALL_END_PATH = "call_end";
|
|
756
1114
|
var COMPRESS_PROMPT_PATH = "compress_prompt";
|
|
1115
|
+
var SAMPLES_PATH = "samples";
|
|
1116
|
+
var SAMPLING_SETTINGS_PATH = "sampling/settings";
|
|
1117
|
+
var SAMPLING_DECIDE_PATH = "sampling/decide";
|
|
757
1118
|
var CHECK_USAGE_PATH = "customers/{customerId}/usage";
|
|
758
1119
|
var CREATE_CUSTOMER_PATH = "customers";
|
|
759
1120
|
var CHANGE_PLAN_PATH = "customers/{customerId}/change_plan";
|
|
@@ -764,11 +1125,16 @@ var CORRELATION_HEADER = "x-usage-correlation-id";
|
|
|
764
1125
|
var IDEMPOTENCY_HEADER = "idempotency-key";
|
|
765
1126
|
var SDK_HEADER = "x-usage-sdk";
|
|
766
1127
|
var USER_AGENT = "UsageTapClient";
|
|
767
|
-
var
|
|
1128
|
+
var CANONICAL_MEDIA_TYPE2 = "application/vnd.usagetap.v1+json";
|
|
768
1129
|
var DEFAULT_BASE_URL = "https://api.usagetap.com";
|
|
769
|
-
var
|
|
1130
|
+
var DEFAULT_RUN_INACTIVITY_MS = 60 * 60 * 1e3;
|
|
1131
|
+
var SDK_VERSION = "1.4.0" ;
|
|
770
1132
|
var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
771
1133
|
var UsageTapClient = class {
|
|
1134
|
+
/** OpenAI-compatible chat, model, and native batch operations. */
|
|
1135
|
+
gateway;
|
|
1136
|
+
/** Published-profile context summarization operations. */
|
|
1137
|
+
summarization;
|
|
772
1138
|
apiKey;
|
|
773
1139
|
baseUrl;
|
|
774
1140
|
fetchImpl;
|
|
@@ -793,6 +1159,11 @@ var UsageTapClient = class {
|
|
|
793
1159
|
usageTapCompressionMessagesEndpoint;
|
|
794
1160
|
usageTapCompressionModel;
|
|
795
1161
|
usageTapCompressionAggressiveness;
|
|
1162
|
+
sampling;
|
|
1163
|
+
samplingSettingsCacheMs;
|
|
1164
|
+
circuitBreaker;
|
|
1165
|
+
circuitBreakerRuns = /* @__PURE__ */ new Map();
|
|
1166
|
+
samplingSettingsCache;
|
|
796
1167
|
constructor(options = {}) {
|
|
797
1168
|
const apiKey = options.apiKey?.trim() || readEnvironmentVariable("USAGETAP_API_KEY");
|
|
798
1169
|
const baseUrl = options.baseUrl?.trim() || readEnvironmentVariable("USAGETAP_BASE_URL") || DEFAULT_BASE_URL;
|
|
@@ -815,10 +1186,21 @@ var UsageTapClient = class {
|
|
|
815
1186
|
"A global fetch implementation was not found. Pass fetchImpl in UsageTapClientOptions."
|
|
816
1187
|
);
|
|
817
1188
|
}
|
|
818
|
-
const
|
|
819
|
-
this.baseUrl = new URL(
|
|
1189
|
+
const normalizedBaseUrl2 = normalizeBaseUrl(baseUrl);
|
|
1190
|
+
this.baseUrl = new URL(normalizedBaseUrl2);
|
|
820
1191
|
this.apiKey = apiKey;
|
|
821
1192
|
this.fetchImpl = wrapFetchImplementation(fetchCandidate, !options.fetchImpl);
|
|
1193
|
+
const resourceConfig = {
|
|
1194
|
+
apiKey,
|
|
1195
|
+
apiBaseUrl: normalizedBaseUrl2,
|
|
1196
|
+
gatewayBaseUrl: options.gatewayBaseUrl?.trim() || readEnvironmentVariable("USAGETAP_GATEWAY_URL"),
|
|
1197
|
+
fetchImpl: this.fetchImpl,
|
|
1198
|
+
headers: options.headers,
|
|
1199
|
+
sdkVersion: SDK_VERSION,
|
|
1200
|
+
idempotencyGenerator: options.idempotencyGenerator
|
|
1201
|
+
};
|
|
1202
|
+
this.gateway = new GatewayResource(resourceConfig);
|
|
1203
|
+
this.summarization = new SummarizationResource(resourceConfig);
|
|
822
1204
|
this.defaultFeature = options.defaultFeature;
|
|
823
1205
|
this.defaultTags = options.defaultTags?.length ? dedupeStrings(options.defaultTags) : void 0;
|
|
824
1206
|
this.defaultHeaders = options.headers ? normalizeHeaderDictionary(options.headers) : {};
|
|
@@ -840,11 +1222,122 @@ var UsageTapClient = class {
|
|
|
840
1222
|
this.usageTapCompressionMessagesEndpoint = options.usageTapCompressionMessagesEndpoint;
|
|
841
1223
|
this.usageTapCompressionModel = options.usageTapCompressionModel;
|
|
842
1224
|
this.usageTapCompressionAggressiveness = options.usageTapCompressionAggressiveness;
|
|
1225
|
+
this.sampling = options.sampling;
|
|
1226
|
+
this.samplingSettingsCacheMs = Number.isFinite(options.samplingSettingsCacheMs) ? Math.max(0, Number(options.samplingSettingsCacheMs)) : 5 * 60 * 1e3;
|
|
1227
|
+
if (options.circuitBreaker) {
|
|
1228
|
+
const maxCallsPerRun = options.circuitBreaker.maxCallsPerRun;
|
|
1229
|
+
if (!Number.isInteger(maxCallsPerRun) || maxCallsPerRun < 1) {
|
|
1230
|
+
throw new UsageTapError(
|
|
1231
|
+
"USAGETAP_BAD_REQUEST",
|
|
1232
|
+
"circuitBreaker.maxCallsPerRun must be a positive integer"
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
const runInactivityMs = options.circuitBreaker.runInactivityMs ?? DEFAULT_RUN_INACTIVITY_MS;
|
|
1236
|
+
if (!Number.isFinite(runInactivityMs) || runInactivityMs < 1) {
|
|
1237
|
+
throw new UsageTapError(
|
|
1238
|
+
"USAGETAP_BAD_REQUEST",
|
|
1239
|
+
"circuitBreaker.runInactivityMs must be a positive number"
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
this.circuitBreaker = {
|
|
1243
|
+
maxCallsPerRun,
|
|
1244
|
+
runInactivityMs
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
shouldSample(request, policy = this.sampling || void 0) {
|
|
1249
|
+
if (!policy) return false;
|
|
1250
|
+
const rate = Math.min(1, Math.max(0, Number(policy.rate) || 0));
|
|
1251
|
+
if (rate <= 0) return false;
|
|
1252
|
+
const customerId = request.customerId?.trim();
|
|
1253
|
+
if (customerId && policy.customers?.exclude?.includes(customerId)) return false;
|
|
1254
|
+
const feature = request.feature?.trim();
|
|
1255
|
+
if (feature && policy.features?.exclude?.includes(feature)) return false;
|
|
1256
|
+
const included = policy.features?.include?.filter(Boolean) ?? [];
|
|
1257
|
+
if (included.length > 0 && (!feature || !included.includes(feature))) return false;
|
|
1258
|
+
const minimum = Math.max(0, Math.round(policy.minInputTokens ?? 0));
|
|
1259
|
+
if (minimum > 0 && estimatePromptTokens(request.input) < minimum) return false;
|
|
1260
|
+
return (policy.random ?? Math.random)() < rate;
|
|
1261
|
+
}
|
|
1262
|
+
async getSamplingSettings(options = {}) {
|
|
1263
|
+
const now = Date.now();
|
|
1264
|
+
if (!options.forceRefresh && this.samplingSettingsCache && this.samplingSettingsCache.expiresAtMs > now) {
|
|
1265
|
+
return {
|
|
1266
|
+
result: { status: "ACCEPTED", code: "SAMPLING_SETTINGS_CACHED" },
|
|
1267
|
+
data: this.samplingSettingsCache.settings,
|
|
1268
|
+
correlationId: options.correlationId ?? "local-cache"
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
const response = await this.requestGet(
|
|
1272
|
+
SAMPLING_SETTINGS_PATH,
|
|
1273
|
+
{
|
|
1274
|
+
signal: options.signal,
|
|
1275
|
+
headers: options.headers,
|
|
1276
|
+
retries: options.retries,
|
|
1277
|
+
correlationId: options.correlationId
|
|
1278
|
+
}
|
|
1279
|
+
);
|
|
1280
|
+
const serverCacheMs = Math.max(0, Number(response.data.cacheSeconds) || 0) * 1e3;
|
|
1281
|
+
const cacheMs = Math.min(this.samplingSettingsCacheMs, serverCacheMs);
|
|
1282
|
+
this.samplingSettingsCache = {
|
|
1283
|
+
settings: response.data,
|
|
1284
|
+
expiresAtMs: now + cacheMs
|
|
1285
|
+
};
|
|
1286
|
+
return response;
|
|
1287
|
+
}
|
|
1288
|
+
async shouldSampleAsync(request, policy) {
|
|
1289
|
+
if (policy) return this.shouldSample(request, policy);
|
|
1290
|
+
if (this.sampling === false) return false;
|
|
1291
|
+
if (this.sampling) return this.shouldSample(request, this.sampling);
|
|
1292
|
+
try {
|
|
1293
|
+
const settings = await this.getSamplingSettings();
|
|
1294
|
+
return this.shouldSample(request, settings.data);
|
|
1295
|
+
} catch {
|
|
1296
|
+
return false;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
async decideSample(request, options = {}) {
|
|
1300
|
+
const hasTokens = Number.isFinite(request.inputTokens) && Number(request.inputTokens) >= 0;
|
|
1301
|
+
const hasCharacters = Number.isFinite(request.inputCharacters) && Number(request.inputCharacters) >= 0;
|
|
1302
|
+
if (!hasTokens && !hasCharacters) {
|
|
1303
|
+
throw new UsageTapError(
|
|
1304
|
+
"USAGETAP_BAD_REQUEST",
|
|
1305
|
+
"decideSample requires inputTokens or inputCharacters"
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
return this.request(
|
|
1309
|
+
SAMPLING_DECIDE_PATH,
|
|
1310
|
+
request,
|
|
1311
|
+
options
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
async captureSample(request, options = {}) {
|
|
1315
|
+
if (!request || request.input === void 0) {
|
|
1316
|
+
throw new UsageTapError(
|
|
1317
|
+
"USAGETAP_BAD_REQUEST",
|
|
1318
|
+
"captureSample requires input"
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
if (!request.provider?.trim()) {
|
|
1322
|
+
throw new UsageTapError(
|
|
1323
|
+
"USAGETAP_BAD_REQUEST",
|
|
1324
|
+
"captureSample requires provider"
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
const sampleId = request.sampleId?.trim() || this.idempotencyGenerator();
|
|
1328
|
+
return this.request(
|
|
1329
|
+
SAMPLES_PATH,
|
|
1330
|
+
{ ...request, sampleId },
|
|
1331
|
+
{ ...options, idempotencyKey: sampleId }
|
|
1332
|
+
);
|
|
843
1333
|
}
|
|
844
1334
|
async beginCall(request, options = {}) {
|
|
845
1335
|
const idempotencyKey = request.idempotencyKey ?? request.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
|
|
1336
|
+
this.reserveRunCall(request, idempotencyKey);
|
|
1337
|
+
const apiRequest = { ...request };
|
|
1338
|
+
delete apiRequest.runId;
|
|
846
1339
|
const payload = {
|
|
847
|
-
...
|
|
1340
|
+
...apiRequest,
|
|
848
1341
|
feature: request.feature ?? this.defaultFeature,
|
|
849
1342
|
tags: this.mergeTags(request.tags)
|
|
850
1343
|
};
|
|
@@ -862,6 +1355,28 @@ var UsageTapClient = class {
|
|
|
862
1355
|
);
|
|
863
1356
|
return response;
|
|
864
1357
|
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Inspect a configured run circuit breaker without consuming another call.
|
|
1360
|
+
*/
|
|
1361
|
+
canRunContinue(request) {
|
|
1362
|
+
const identity = this.resolveRunIdentity(request);
|
|
1363
|
+
if (!identity || !this.circuitBreaker) {
|
|
1364
|
+
throw new UsageTapError(
|
|
1365
|
+
"USAGETAP_BAD_REQUEST",
|
|
1366
|
+
"canRunContinue requires circuitBreaker configuration and a non-empty runId"
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
this.expireInactiveRuns();
|
|
1370
|
+
const calls = this.circuitBreakerRuns.get(identity.key)?.calls ?? 0;
|
|
1371
|
+
return this.createCircuitBreakerDecision(identity.customerId, identity.runId, calls);
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Release local state after a workflow finishes. Returns true when state existed.
|
|
1375
|
+
*/
|
|
1376
|
+
resetRun(request) {
|
|
1377
|
+
const identity = this.resolveRunIdentity(request);
|
|
1378
|
+
return identity ? this.circuitBreakerRuns.delete(identity.key) : false;
|
|
1379
|
+
}
|
|
865
1380
|
async promptCompress(request, options = {}) {
|
|
866
1381
|
if (!request?.callId) {
|
|
867
1382
|
throw new UsageTapError(
|
|
@@ -959,6 +1474,10 @@ var UsageTapClient = class {
|
|
|
959
1474
|
usageTapCompressionMessagesEndpoint: this.usageTapCompressionMessagesEndpoint,
|
|
960
1475
|
aggressiveness: options.aggressiveness ?? this.aggressiveness,
|
|
961
1476
|
usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
|
|
1477
|
+
mode: options.mode,
|
|
1478
|
+
latencyBudgetMs: options.latencyBudgetMs,
|
|
1479
|
+
compactEmptyUserMessages: options.compactEmptyUserMessages,
|
|
1480
|
+
compactDuplicateUserTextParts: options.compactDuplicateUserTextParts,
|
|
962
1481
|
fetchImpl: this.fetchImpl,
|
|
963
1482
|
signal: options.signal,
|
|
964
1483
|
failOpen: options.failOpen
|
|
@@ -1000,11 +1519,17 @@ var UsageTapClient = class {
|
|
|
1000
1519
|
callId: request.callId,
|
|
1001
1520
|
feature: feature ?? this.defaultFeature,
|
|
1002
1521
|
tags: tags ?? this.defaultTags,
|
|
1522
|
+
providerUsed: request.providerUsed,
|
|
1003
1523
|
modelUsed: request.modelUsed,
|
|
1524
|
+
reasoningEffort: request.reasoningEffort,
|
|
1525
|
+
reasoningEffortSource: request.reasoningEffortSource,
|
|
1526
|
+
reasoningMode: request.reasoningMode,
|
|
1527
|
+
reasoningBudgetTokens: request.reasoningBudgetTokens,
|
|
1004
1528
|
metrics: {
|
|
1005
1529
|
inputTokens: request.inputTokens,
|
|
1006
1530
|
responseTokens: request.responseTokens,
|
|
1007
1531
|
cachedInputTokens: request.cachedInputTokens,
|
|
1532
|
+
cacheWriteInputTokens: request.cacheWriteInputTokens,
|
|
1008
1533
|
reasoningTokens: request.reasoningTokens,
|
|
1009
1534
|
searches: request.searches,
|
|
1010
1535
|
audioSeconds: request.audioSeconds,
|
|
@@ -1112,6 +1637,15 @@ var UsageTapClient = class {
|
|
|
1112
1637
|
meterSlot: request.meterSlot,
|
|
1113
1638
|
amount: request.amount
|
|
1114
1639
|
};
|
|
1640
|
+
if (request.customerUserId) {
|
|
1641
|
+
payload.customerUserId = request.customerUserId;
|
|
1642
|
+
}
|
|
1643
|
+
if (request.customerUserName) {
|
|
1644
|
+
payload.customerUserName = request.customerUserName;
|
|
1645
|
+
}
|
|
1646
|
+
if (request.customerUserEmail) {
|
|
1647
|
+
payload.customerUserEmail = request.customerUserEmail;
|
|
1648
|
+
}
|
|
1115
1649
|
if (request.feature) {
|
|
1116
1650
|
payload.feature = request.feature;
|
|
1117
1651
|
}
|
|
@@ -1148,6 +1682,11 @@ var UsageTapClient = class {
|
|
|
1148
1682
|
const beginPayload = idempotencyKey ? { ...beginRequest, idempotencyKey, idempotency: idempotencyKey } : { ...beginRequest };
|
|
1149
1683
|
const beginResponse = await this.beginCall(beginPayload, options);
|
|
1150
1684
|
let usage = {};
|
|
1685
|
+
const pricingMode = beginResponse.data.pricingMode ?? beginRequest.pricingMode ?? (beginRequest.batch === true ? "batch" : beginRequest.batch === false ? "standard" : void 0);
|
|
1686
|
+
if (pricingMode) {
|
|
1687
|
+
usage.pricingMode = pricingMode;
|
|
1688
|
+
usage.batch = pricingMode === "batch";
|
|
1689
|
+
}
|
|
1151
1690
|
const initialStripeCustomerId = typeof beginResponse.data.stripeCustomerId === "string" ? beginResponse.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
|
|
1152
1691
|
if (initialStripeCustomerId) {
|
|
1153
1692
|
usage = { ...usage, stripeCustomerId: initialStripeCustomerId };
|
|
@@ -1215,17 +1754,86 @@ var UsageTapClient = class {
|
|
|
1215
1754
|
toPromptCompressionTelemetry(result) {
|
|
1216
1755
|
return {
|
|
1217
1756
|
provider: result.provider,
|
|
1218
|
-
originalCharacters: result.originalCharacters,
|
|
1219
|
-
compressedCharacters: result.compressedCharacters,
|
|
1220
|
-
savedCharacters: result.savedCharacters,
|
|
1221
1757
|
originalTokens: result.originalTokens,
|
|
1222
1758
|
compressedTokens: result.compressedTokens,
|
|
1223
1759
|
savedTokens: result.savedTokens,
|
|
1224
1760
|
tokenSavingsRatio: result.tokenSavingsRatio,
|
|
1225
|
-
savingsRatio: result.savingsRatio,
|
|
1226
1761
|
techniques: result.techniques
|
|
1227
1762
|
};
|
|
1228
1763
|
}
|
|
1764
|
+
reserveRunCall(request, idempotencyKey) {
|
|
1765
|
+
const identity = this.resolveRunIdentity(request);
|
|
1766
|
+
if (!identity || !this.circuitBreaker) return;
|
|
1767
|
+
this.expireInactiveRuns();
|
|
1768
|
+
const now = Date.now();
|
|
1769
|
+
const state = this.circuitBreakerRuns.get(identity.key) ?? {
|
|
1770
|
+
calls: 0,
|
|
1771
|
+
reservationKeys: /* @__PURE__ */ new Set(),
|
|
1772
|
+
lastSeenAtMs: now
|
|
1773
|
+
};
|
|
1774
|
+
const reservationKey = idempotencyKey ?? this.idempotencyGenerator();
|
|
1775
|
+
state.lastSeenAtMs = now;
|
|
1776
|
+
if (state.reservationKeys.has(reservationKey)) {
|
|
1777
|
+
this.circuitBreakerRuns.set(identity.key, state);
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
const decision = this.createCircuitBreakerDecision(
|
|
1781
|
+
identity.customerId,
|
|
1782
|
+
identity.runId,
|
|
1783
|
+
state.calls
|
|
1784
|
+
);
|
|
1785
|
+
if (!decision.allowed) {
|
|
1786
|
+
throw new UsageTapError(
|
|
1787
|
+
"USAGETAP_CIRCUIT_OPEN",
|
|
1788
|
+
`Run ${identity.runId} reached its ${decision.limit}-call circuit-breaker limit`,
|
|
1789
|
+
{
|
|
1790
|
+
details: {
|
|
1791
|
+
reason: decision.reason,
|
|
1792
|
+
customerId: identity.customerId,
|
|
1793
|
+
runId: identity.runId,
|
|
1794
|
+
calls: decision.calls,
|
|
1795
|
+
limit: decision.limit,
|
|
1796
|
+
remaining: decision.remaining
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
);
|
|
1800
|
+
}
|
|
1801
|
+
state.calls += 1;
|
|
1802
|
+
state.reservationKeys.add(reservationKey);
|
|
1803
|
+
this.circuitBreakerRuns.set(identity.key, state);
|
|
1804
|
+
}
|
|
1805
|
+
resolveRunIdentity(request) {
|
|
1806
|
+
const customerId = request.customerId?.trim();
|
|
1807
|
+
const runId = request.runId?.trim();
|
|
1808
|
+
if (!customerId || !runId) return void 0;
|
|
1809
|
+
return {
|
|
1810
|
+
key: `${customerId}\0${runId}`,
|
|
1811
|
+
customerId,
|
|
1812
|
+
runId
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
createCircuitBreakerDecision(customerId, runId, calls) {
|
|
1816
|
+
const limit = this.circuitBreaker?.maxCallsPerRun ?? 0;
|
|
1817
|
+
const allowed = calls < limit;
|
|
1818
|
+
return {
|
|
1819
|
+
allowed,
|
|
1820
|
+
...allowed ? {} : { reason: "max_calls_per_run" },
|
|
1821
|
+
customerId,
|
|
1822
|
+
runId,
|
|
1823
|
+
calls,
|
|
1824
|
+
limit,
|
|
1825
|
+
remaining: Math.max(0, limit - calls)
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
expireInactiveRuns() {
|
|
1829
|
+
if (!this.circuitBreaker || this.circuitBreakerRuns.size === 0) return;
|
|
1830
|
+
const expiredBefore = Date.now() - this.circuitBreaker.runInactivityMs;
|
|
1831
|
+
for (const [key, state] of this.circuitBreakerRuns) {
|
|
1832
|
+
if (state.lastSeenAtMs < expiredBefore) {
|
|
1833
|
+
this.circuitBreakerRuns.delete(key);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1229
1837
|
async request(path, payload, options) {
|
|
1230
1838
|
const url = new URL(path, this.baseUrl).toString();
|
|
1231
1839
|
const body = payload !== void 0 ? JSON.stringify(payload) : void 0;
|
|
@@ -1413,7 +2021,7 @@ var UsageTapClient = class {
|
|
|
1413
2021
|
...this.defaultHeaders,
|
|
1414
2022
|
[SDK_HEADER]: `js/${SDK_VERSION}`,
|
|
1415
2023
|
"content-type": "application/json",
|
|
1416
|
-
accept:
|
|
2024
|
+
accept: CANONICAL_MEDIA_TYPE2
|
|
1417
2025
|
};
|
|
1418
2026
|
if (!HAS_WINDOW) {
|
|
1419
2027
|
headers["user-agent"] = `${USER_AGENT}/${SDK_VERSION}`;
|
|
@@ -1614,17 +2222,18 @@ var OpenAIPromptCompressionStats = class {
|
|
|
1614
2222
|
}
|
|
1615
2223
|
};
|
|
1616
2224
|
function createOpenAIAdapter(init) {
|
|
1617
|
-
const { client, usageTap } = init;
|
|
2225
|
+
const { client, usageTap, provider = "openai" } = init;
|
|
1618
2226
|
return {
|
|
1619
2227
|
async invoke(params) {
|
|
1620
2228
|
const result = await usageTap.withUsage(
|
|
1621
2229
|
params.begin,
|
|
1622
2230
|
async (ctx) => {
|
|
2231
|
+
ctx.setUsage({ providerUsed: provider });
|
|
1623
2232
|
const response = await params.call(client, {
|
|
1624
2233
|
hints: ctx.begin.data.vendorHints,
|
|
1625
2234
|
begin: ctx.begin
|
|
1626
2235
|
});
|
|
1627
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx);
|
|
2236
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx, provider);
|
|
1628
2237
|
return {
|
|
1629
2238
|
data: response,
|
|
1630
2239
|
begin: ctx.begin
|
|
@@ -1638,6 +2247,7 @@ function createOpenAIAdapter(init) {
|
|
|
1638
2247
|
const result = await usageTap.withUsage(
|
|
1639
2248
|
params.begin,
|
|
1640
2249
|
async (ctx) => {
|
|
2250
|
+
ctx.setUsage({ providerUsed: provider });
|
|
1641
2251
|
const { stream, onComplete } = await params.call(client, {
|
|
1642
2252
|
hints: ctx.begin.data.vendorHints,
|
|
1643
2253
|
begin: ctx.begin
|
|
@@ -1763,6 +2373,181 @@ async function pipeToResponse(stream, res, options = {}) {
|
|
|
1763
2373
|
}
|
|
1764
2374
|
}
|
|
1765
2375
|
var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
|
|
2376
|
+
function withSampling(client, options = {}) {
|
|
2377
|
+
if (!client || !options) {
|
|
2378
|
+
throw new UsageTapError(
|
|
2379
|
+
"USAGETAP_BAD_REQUEST",
|
|
2380
|
+
"withSampling requires an OpenAI-compatible client and sampling options"
|
|
2381
|
+
);
|
|
2382
|
+
}
|
|
2383
|
+
const { apiKey, usageTapClient, provider = "openai", ...policy } = options;
|
|
2384
|
+
const localPolicy = typeof policy.rate === "number" ? policy : void 0;
|
|
2385
|
+
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey, sampling: localPolicy });
|
|
2386
|
+
const wrapCreate = (create) => async (params, requestOptions) => {
|
|
2387
|
+
const { usageTap: callContextRaw, ...providerOptions } = requestOptions ?? {};
|
|
2388
|
+
const callContext = isObjectRecord(callContextRaw) ? callContextRaw : {};
|
|
2389
|
+
const streaming = params.stream === true;
|
|
2390
|
+
const decision = streaming ? Promise.resolve(false) : usageTap.shouldSampleAsync({
|
|
2391
|
+
customerId: readString(callContext.customerId),
|
|
2392
|
+
feature: readString(callContext.feature),
|
|
2393
|
+
input: params
|
|
2394
|
+
}, localPolicy);
|
|
2395
|
+
const startedAt = Date.now();
|
|
2396
|
+
try {
|
|
2397
|
+
const response = await create(
|
|
2398
|
+
params,
|
|
2399
|
+
Object.keys(providerOptions).length ? providerOptions : void 0
|
|
2400
|
+
);
|
|
2401
|
+
const selected = await decision;
|
|
2402
|
+
if (selected) {
|
|
2403
|
+
const record = isObjectRecord(response) ? response : {};
|
|
2404
|
+
await usageTap.captureSample({
|
|
2405
|
+
customerId: readString(callContext.customerId),
|
|
2406
|
+
feature: readString(callContext.feature),
|
|
2407
|
+
environment: readString(callContext.environment),
|
|
2408
|
+
tags: readStringArray(callContext.tags),
|
|
2409
|
+
provider,
|
|
2410
|
+
model: readString(record.model) ?? readString(params.model),
|
|
2411
|
+
input: params,
|
|
2412
|
+
output: response,
|
|
2413
|
+
usage: record.usage,
|
|
2414
|
+
latencyMs: Date.now() - startedAt
|
|
2415
|
+
}).catch(() => void 0);
|
|
2416
|
+
}
|
|
2417
|
+
return response;
|
|
2418
|
+
} catch (error) {
|
|
2419
|
+
const selected = await decision;
|
|
2420
|
+
if (selected) {
|
|
2421
|
+
await usageTap.captureSample({
|
|
2422
|
+
customerId: readString(callContext.customerId),
|
|
2423
|
+
feature: readString(callContext.feature),
|
|
2424
|
+
environment: readString(callContext.environment),
|
|
2425
|
+
tags: readStringArray(callContext.tags),
|
|
2426
|
+
provider,
|
|
2427
|
+
model: readString(params.model),
|
|
2428
|
+
input: params,
|
|
2429
|
+
latencyMs: Date.now() - startedAt,
|
|
2430
|
+
error: serializeSamplingError(error)
|
|
2431
|
+
}).catch(() => void 0);
|
|
2432
|
+
}
|
|
2433
|
+
throw error;
|
|
2434
|
+
}
|
|
2435
|
+
};
|
|
2436
|
+
const chat = client.chat?.completions ? new Proxy(client.chat, {
|
|
2437
|
+
get(target, prop, receiver) {
|
|
2438
|
+
if (prop !== "completions") return safeReflectGet(target, prop, receiver);
|
|
2439
|
+
const completions = target.completions;
|
|
2440
|
+
return new Proxy(completions, {
|
|
2441
|
+
get(completionTarget, completionProp, completionReceiver) {
|
|
2442
|
+
if (completionProp === "create") {
|
|
2443
|
+
return wrapCreate(
|
|
2444
|
+
completionTarget.create.bind(completionTarget)
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
return safeReflectGet(
|
|
2448
|
+
completionTarget,
|
|
2449
|
+
completionProp,
|
|
2450
|
+
completionReceiver
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
}) : void 0;
|
|
2456
|
+
const responses = typeof client.responses !== "undefined" && client.responses ? new Proxy(client.responses, {
|
|
2457
|
+
get(target, prop, receiver) {
|
|
2458
|
+
if (prop === "create") {
|
|
2459
|
+
const create = Reflect.get(target, prop, receiver);
|
|
2460
|
+
return wrapCreate(create.bind(target));
|
|
2461
|
+
}
|
|
2462
|
+
return safeReflectGet(target, prop, receiver);
|
|
2463
|
+
}
|
|
2464
|
+
}) : void 0;
|
|
2465
|
+
return new Proxy(client, {
|
|
2466
|
+
get(target, prop, receiver) {
|
|
2467
|
+
if (prop === "chat" && chat) return chat;
|
|
2468
|
+
if (prop === "responses" && responses) return responses;
|
|
2469
|
+
if (prop === "unwrap") return () => target;
|
|
2470
|
+
return safeReflectGet(target, prop, receiver);
|
|
2471
|
+
}
|
|
2472
|
+
});
|
|
2473
|
+
}
|
|
2474
|
+
function safeReflectGet(target, prop, receiver) {
|
|
2475
|
+
return Reflect.get(target, prop, receiver);
|
|
2476
|
+
}
|
|
2477
|
+
function readStringArray(value) {
|
|
2478
|
+
if (!Array.isArray(value)) return void 0;
|
|
2479
|
+
const strings = value.filter((item) => typeof item === "string");
|
|
2480
|
+
return strings.length ? strings : void 0;
|
|
2481
|
+
}
|
|
2482
|
+
function readString(value) {
|
|
2483
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2484
|
+
}
|
|
2485
|
+
function serializeSamplingError(error) {
|
|
2486
|
+
if (error instanceof Error) {
|
|
2487
|
+
return { name: error.name, message: error.message };
|
|
2488
|
+
}
|
|
2489
|
+
return { message: String(error) };
|
|
2490
|
+
}
|
|
2491
|
+
function normalizeMeteredOpenAISampling(options) {
|
|
2492
|
+
if (!options) return void 0;
|
|
2493
|
+
if (options === true) return { provider: "openai" };
|
|
2494
|
+
const { provider = "openai", ...policyFields } = options;
|
|
2495
|
+
return {
|
|
2496
|
+
provider,
|
|
2497
|
+
policy: typeof policyFields.rate === "number" ? policyFields : void 0
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
function startMeteredOpenAISampleDecision({
|
|
2501
|
+
usageTap,
|
|
2502
|
+
sampling,
|
|
2503
|
+
beginRequest,
|
|
2504
|
+
input
|
|
2505
|
+
}) {
|
|
2506
|
+
if (!sampling) return Promise.resolve(false);
|
|
2507
|
+
return usageTap.shouldSampleAsync(
|
|
2508
|
+
{
|
|
2509
|
+
customerId: beginRequest.customerId,
|
|
2510
|
+
feature: beginRequest.feature,
|
|
2511
|
+
input
|
|
2512
|
+
},
|
|
2513
|
+
sampling.policy
|
|
2514
|
+
);
|
|
2515
|
+
}
|
|
2516
|
+
async function captureMeteredOpenAISample({
|
|
2517
|
+
usageTap,
|
|
2518
|
+
sampling,
|
|
2519
|
+
decision,
|
|
2520
|
+
ctx,
|
|
2521
|
+
beginRequest,
|
|
2522
|
+
input,
|
|
2523
|
+
response,
|
|
2524
|
+
error,
|
|
2525
|
+
startedAt
|
|
2526
|
+
}) {
|
|
2527
|
+
if (!sampling) return;
|
|
2528
|
+
let selected = false;
|
|
2529
|
+
try {
|
|
2530
|
+
selected = await decision;
|
|
2531
|
+
} catch {
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
if (!selected) return;
|
|
2535
|
+
const record = isObjectRecord(response) ? response : {};
|
|
2536
|
+
await usageTap.captureSample({
|
|
2537
|
+
sampleId: ctx.begin.data.callId,
|
|
2538
|
+
callId: ctx.begin.data.callId,
|
|
2539
|
+
customerId: beginRequest.customerId,
|
|
2540
|
+
feature: beginRequest.feature,
|
|
2541
|
+
tags: beginRequest.tags,
|
|
2542
|
+
provider: sampling.provider,
|
|
2543
|
+
model: readString(record.model) ?? readString(input.model),
|
|
2544
|
+
input,
|
|
2545
|
+
...response === void 0 ? {} : { output: response },
|
|
2546
|
+
usage: record.usage,
|
|
2547
|
+
latencyMs: Date.now() - startedAt,
|
|
2548
|
+
...error === void 0 ? {} : { error: serializeSamplingError(error) }
|
|
2549
|
+
}).catch(() => void 0);
|
|
2550
|
+
}
|
|
1766
2551
|
function withCompression(client, options = {}) {
|
|
1767
2552
|
if (!client) {
|
|
1768
2553
|
throw new UsageTapError(
|
|
@@ -1773,11 +2558,13 @@ function withCompression(client, options = {}) {
|
|
|
1773
2558
|
const {
|
|
1774
2559
|
apiKey,
|
|
1775
2560
|
usageTapClient,
|
|
2561
|
+
sampling,
|
|
1776
2562
|
...compressionOverrides
|
|
1777
2563
|
} = options;
|
|
1778
2564
|
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
|
|
1779
2565
|
const compression = {
|
|
1780
2566
|
provider: "usagetap",
|
|
2567
|
+
roles: { user: { enabled: true, aggressiveness: 0.2 } },
|
|
1781
2568
|
minContextTokens: DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS,
|
|
1782
2569
|
...compressionOverrides
|
|
1783
2570
|
};
|
|
@@ -1787,7 +2574,7 @@ function withCompression(client, options = {}) {
|
|
|
1787
2574
|
usageTap,
|
|
1788
2575
|
compression
|
|
1789
2576
|
) : void 0;
|
|
1790
|
-
|
|
2577
|
+
const compressed = new Proxy(client, {
|
|
1791
2578
|
get(target, prop, receiver) {
|
|
1792
2579
|
if (prop === "chat" && proxiedChat) {
|
|
1793
2580
|
return proxiedChat;
|
|
@@ -1801,6 +2588,11 @@ function withCompression(client, options = {}) {
|
|
|
1801
2588
|
return Reflect.get(target, prop, receiver);
|
|
1802
2589
|
}
|
|
1803
2590
|
});
|
|
2591
|
+
return sampling ? withSampling(compressed, {
|
|
2592
|
+
...sampling === true ? {} : sampling,
|
|
2593
|
+
apiKey,
|
|
2594
|
+
usageTapClient
|
|
2595
|
+
}) : compressed;
|
|
1804
2596
|
}
|
|
1805
2597
|
function withMetering(client, customer) {
|
|
1806
2598
|
const config = typeof customer === "string" ? { customerId: customer } : customer;
|
|
@@ -1815,6 +2607,8 @@ function withMetering(client, customer) {
|
|
|
1815
2607
|
usageTapClient,
|
|
1816
2608
|
applyVendorHints,
|
|
1817
2609
|
promptCompression,
|
|
2610
|
+
sampling,
|
|
2611
|
+
provider,
|
|
1818
2612
|
...defaultContext
|
|
1819
2613
|
} = config;
|
|
1820
2614
|
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
|
|
@@ -1822,7 +2616,9 @@ function withMetering(client, customer) {
|
|
|
1822
2616
|
return wrapOpenAI(client, usageTap, {
|
|
1823
2617
|
defaultContext,
|
|
1824
2618
|
applyVendorHints,
|
|
1825
|
-
promptCompression: normalizedCompression
|
|
2619
|
+
promptCompression: normalizedCompression,
|
|
2620
|
+
sampling,
|
|
2621
|
+
provider
|
|
1826
2622
|
});
|
|
1827
2623
|
}
|
|
1828
2624
|
function wrapOpenAI(client, usageTap, options = {}) {
|
|
@@ -1832,6 +2628,8 @@ function wrapOpenAI(client, usageTap, options = {}) {
|
|
|
1832
2628
|
const defaultContext = options.defaultContext;
|
|
1833
2629
|
const applyVendorHints = options.applyVendorHints !== false;
|
|
1834
2630
|
const defaultPromptCompression = normalizePromptCompressionOptions(options.promptCompression);
|
|
2631
|
+
const defaultSampling = normalizeMeteredOpenAISampling(options.sampling);
|
|
2632
|
+
const provider = options.provider ?? "openai";
|
|
1835
2633
|
const promptCompressionStats = new OpenAIPromptCompressionStats();
|
|
1836
2634
|
const proxiedChat = client.chat ? createChatProxy(
|
|
1837
2635
|
client.chat,
|
|
@@ -1839,7 +2637,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
|
|
|
1839
2637
|
defaultContext,
|
|
1840
2638
|
applyVendorHints,
|
|
1841
2639
|
defaultPromptCompression,
|
|
1842
|
-
promptCompressionStats
|
|
2640
|
+
promptCompressionStats,
|
|
2641
|
+
defaultSampling,
|
|
2642
|
+
provider
|
|
1843
2643
|
) : void 0;
|
|
1844
2644
|
const proxiedResponses = typeof client.responses !== "undefined" ? createResponsesProxy(
|
|
1845
2645
|
client.responses,
|
|
@@ -1847,7 +2647,9 @@ function wrapOpenAI(client, usageTap, options = {}) {
|
|
|
1847
2647
|
defaultContext,
|
|
1848
2648
|
applyVendorHints,
|
|
1849
2649
|
defaultPromptCompression,
|
|
1850
|
-
promptCompressionStats
|
|
2650
|
+
promptCompressionStats,
|
|
2651
|
+
defaultSampling,
|
|
2652
|
+
provider
|
|
1851
2653
|
) : void 0;
|
|
1852
2654
|
const handler = {
|
|
1853
2655
|
get(target, prop, receiver) {
|
|
@@ -1915,14 +2717,16 @@ function createCompressionOnlyResponsesProxy(resource, usageTap, compression) {
|
|
|
1915
2717
|
}
|
|
1916
2718
|
});
|
|
1917
2719
|
}
|
|
1918
|
-
function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2720
|
+
function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
1919
2721
|
const completions = createChatCompletionsProxy(
|
|
1920
2722
|
resource.completions,
|
|
1921
2723
|
usageTap,
|
|
1922
2724
|
defaultContext,
|
|
1923
2725
|
applyVendorHints,
|
|
1924
2726
|
defaultPromptCompression,
|
|
1925
|
-
promptCompressionStats
|
|
2727
|
+
promptCompressionStats,
|
|
2728
|
+
defaultSampling,
|
|
2729
|
+
provider
|
|
1926
2730
|
);
|
|
1927
2731
|
const handler = {
|
|
1928
2732
|
get(target, prop, receiver) {
|
|
@@ -1934,7 +2738,7 @@ function createChatProxy(resource, usageTap, defaultContext, applyVendorHints, d
|
|
|
1934
2738
|
};
|
|
1935
2739
|
return new Proxy(resource, handler);
|
|
1936
2740
|
}
|
|
1937
|
-
function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2741
|
+
function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
1938
2742
|
if (!resource || typeof resource !== "object") {
|
|
1939
2743
|
return void 0;
|
|
1940
2744
|
}
|
|
@@ -1949,9 +2753,20 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
1949
2753
|
withUsage,
|
|
1950
2754
|
promptCompression
|
|
1951
2755
|
} = splitUsageOptions(options);
|
|
1952
|
-
const beginRequest =
|
|
2756
|
+
const beginRequest = withRuntimeCompressionContext(
|
|
2757
|
+
resolveBeginRequest(defaultContext, usageContext),
|
|
2758
|
+
"openai",
|
|
2759
|
+
String(params.model)
|
|
2760
|
+
);
|
|
1953
2761
|
const wantsStream = isStreamingRequest(params);
|
|
1954
2762
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2763
|
+
const sampleStartedAt = Date.now();
|
|
2764
|
+
const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
|
|
2765
|
+
usageTap,
|
|
2766
|
+
sampling: defaultSampling,
|
|
2767
|
+
beginRequest,
|
|
2768
|
+
input: params
|
|
2769
|
+
});
|
|
1955
2770
|
const hintedParams = applyVendorHints ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
1956
2771
|
const finalParams = await compressResponsesParamsForCall({
|
|
1957
2772
|
params: hintedParams,
|
|
@@ -1963,13 +2778,14 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
1963
2778
|
withUsage,
|
|
1964
2779
|
operation: "responses.create"
|
|
1965
2780
|
});
|
|
2781
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
1966
2782
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
1967
2783
|
if (wantsStream) {
|
|
1968
2784
|
const apiPromise2 = originalCreate(finalParams, request);
|
|
1969
2785
|
const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
|
|
1970
2786
|
ensureAsyncIterable(rawStream, "responses.create");
|
|
1971
2787
|
const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {
|
|
1972
|
-
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
|
|
2788
|
+
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
|
|
1973
2789
|
if (usage) {
|
|
1974
2790
|
ctx.setUsage(usage);
|
|
1975
2791
|
}
|
|
@@ -1979,9 +2795,31 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
1979
2795
|
return wrappedPromise2;
|
|
1980
2796
|
}
|
|
1981
2797
|
const apiPromise = originalCreate(finalParams, request);
|
|
1982
|
-
const wrappedPromise = transformApiPromise(apiPromise, (response) => {
|
|
1983
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
|
|
2798
|
+
const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
|
|
2799
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
|
|
2800
|
+
await captureMeteredOpenAISample({
|
|
2801
|
+
usageTap,
|
|
2802
|
+
sampling: defaultSampling,
|
|
2803
|
+
decision: sampleDecision,
|
|
2804
|
+
ctx,
|
|
2805
|
+
beginRequest,
|
|
2806
|
+
input: params,
|
|
2807
|
+
response,
|
|
2808
|
+
startedAt: sampleStartedAt
|
|
2809
|
+
});
|
|
1984
2810
|
return response;
|
|
2811
|
+
}, async (error) => {
|
|
2812
|
+
await captureMeteredOpenAISample({
|
|
2813
|
+
usageTap,
|
|
2814
|
+
sampling: defaultSampling,
|
|
2815
|
+
decision: sampleDecision,
|
|
2816
|
+
ctx,
|
|
2817
|
+
beginRequest,
|
|
2818
|
+
input: params,
|
|
2819
|
+
error,
|
|
2820
|
+
startedAt: sampleStartedAt
|
|
2821
|
+
});
|
|
2822
|
+
throw error;
|
|
1985
2823
|
});
|
|
1986
2824
|
return wrappedPromise;
|
|
1987
2825
|
}, withUsage);
|
|
@@ -1996,7 +2834,7 @@ function createResponsesProxy(resource, usageTap, defaultContext, applyVendorHin
|
|
|
1996
2834
|
};
|
|
1997
2835
|
return new Proxy(resource, handler);
|
|
1998
2836
|
}
|
|
1999
|
-
function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
|
|
2837
|
+
function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats, defaultSampling, provider) {
|
|
2000
2838
|
const originalCreate = resource.create.bind(resource);
|
|
2001
2839
|
const streamCandidate = resource.stream;
|
|
2002
2840
|
const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(resource) : void 0;
|
|
@@ -2007,9 +2845,20 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2007
2845
|
withUsage,
|
|
2008
2846
|
promptCompression
|
|
2009
2847
|
} = splitUsageOptions(options);
|
|
2010
|
-
const beginRequest =
|
|
2848
|
+
const beginRequest = withRuntimeCompressionContext(
|
|
2849
|
+
resolveBeginRequest(defaultContext, usageContext),
|
|
2850
|
+
"openai",
|
|
2851
|
+
String(params.model)
|
|
2852
|
+
);
|
|
2011
2853
|
const wantsStream = isStreamingRequest(params);
|
|
2012
2854
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2855
|
+
const sampleStartedAt = Date.now();
|
|
2856
|
+
const sampleDecision = wantsStream ? Promise.resolve(false) : startMeteredOpenAISampleDecision({
|
|
2857
|
+
usageTap,
|
|
2858
|
+
sampling: defaultSampling,
|
|
2859
|
+
beginRequest,
|
|
2860
|
+
input: params
|
|
2861
|
+
});
|
|
2013
2862
|
const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
2014
2863
|
const finalParams = await compressChatParamsForCall({
|
|
2015
2864
|
params: hintedParams,
|
|
@@ -2021,13 +2870,14 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2021
2870
|
withUsage,
|
|
2022
2871
|
operation: "chat.completions.create"
|
|
2023
2872
|
});
|
|
2873
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
2024
2874
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
2025
2875
|
if (wantsStream) {
|
|
2026
2876
|
const apiPromise2 = originalCreate(finalParams, request);
|
|
2027
2877
|
const wrappedPromise2 = transformApiPromise(apiPromise2, (rawStream) => {
|
|
2028
2878
|
ensureAsyncIterable(rawStream, "chat.completions.create");
|
|
2029
2879
|
const wrappedStream2 = wrapStreamForUsageTap(rawStream, async () => {
|
|
2030
|
-
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);
|
|
2880
|
+
const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints, provider);
|
|
2031
2881
|
if (usage) {
|
|
2032
2882
|
ctx.setUsage(usage);
|
|
2033
2883
|
}
|
|
@@ -2037,9 +2887,31 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2037
2887
|
return wrappedPromise2;
|
|
2038
2888
|
}
|
|
2039
2889
|
const apiPromise = originalCreate(finalParams, request);
|
|
2040
|
-
const wrappedPromise = transformApiPromise(apiPromise, (response) => {
|
|
2041
|
-
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx);
|
|
2890
|
+
const wrappedPromise = transformApiPromise(apiPromise, async (response) => {
|
|
2891
|
+
tryInferUsage(response, ctx.begin.data.vendorHints, void 0, ctx, provider);
|
|
2892
|
+
await captureMeteredOpenAISample({
|
|
2893
|
+
usageTap,
|
|
2894
|
+
sampling: defaultSampling,
|
|
2895
|
+
decision: sampleDecision,
|
|
2896
|
+
ctx,
|
|
2897
|
+
beginRequest,
|
|
2898
|
+
input: params,
|
|
2899
|
+
response,
|
|
2900
|
+
startedAt: sampleStartedAt
|
|
2901
|
+
});
|
|
2042
2902
|
return response;
|
|
2903
|
+
}, async (error) => {
|
|
2904
|
+
await captureMeteredOpenAISample({
|
|
2905
|
+
usageTap,
|
|
2906
|
+
sampling: defaultSampling,
|
|
2907
|
+
decision: sampleDecision,
|
|
2908
|
+
ctx,
|
|
2909
|
+
beginRequest,
|
|
2910
|
+
input: params,
|
|
2911
|
+
error,
|
|
2912
|
+
startedAt: sampleStartedAt
|
|
2913
|
+
});
|
|
2914
|
+
throw error;
|
|
2043
2915
|
});
|
|
2044
2916
|
return wrappedPromise;
|
|
2045
2917
|
}, withUsage);
|
|
@@ -2051,7 +2923,11 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2051
2923
|
withUsage,
|
|
2052
2924
|
promptCompression
|
|
2053
2925
|
} = splitUsageOptions(options);
|
|
2054
|
-
const beginRequest =
|
|
2926
|
+
const beginRequest = withRuntimeCompressionContext(
|
|
2927
|
+
resolveBeginRequest(defaultContext, usageContext),
|
|
2928
|
+
"openai",
|
|
2929
|
+
String(params.model)
|
|
2930
|
+
);
|
|
2055
2931
|
return usageTap.withUsage(beginRequest, async (ctx) => {
|
|
2056
2932
|
const hintedParams = applyVendorHints ? applyChatVendorHints(params, ctx.begin.data.vendorHints) : params;
|
|
2057
2933
|
const finalParams = await compressChatParamsForCall({
|
|
@@ -2064,12 +2940,17 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2064
2940
|
withUsage,
|
|
2065
2941
|
operation: "chat.completions.stream"
|
|
2066
2942
|
});
|
|
2943
|
+
ctx.setUsage(openAIRequestExecutionMetadata(finalParams, provider));
|
|
2067
2944
|
const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId);
|
|
2068
2945
|
const apiPromise = originalStream(finalParams, request);
|
|
2069
2946
|
const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {
|
|
2070
2947
|
ensureAsyncIterable(rawStream, "chat.completions.stream");
|
|
2071
2948
|
const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {
|
|
2072
|
-
const usage = await extractUsageFromStream(
|
|
2949
|
+
const usage = await extractUsageFromStream(
|
|
2950
|
+
rawStream,
|
|
2951
|
+
ctx.begin.data.vendorHints,
|
|
2952
|
+
provider
|
|
2953
|
+
);
|
|
2073
2954
|
if (usage) {
|
|
2074
2955
|
ctx.setUsage(usage);
|
|
2075
2956
|
}
|
|
@@ -2093,19 +2974,70 @@ function createChatCompletionsProxy(resource, usageTap, defaultContext, applyVen
|
|
|
2093
2974
|
return new Proxy(resource, handler);
|
|
2094
2975
|
}
|
|
2095
2976
|
async function compressChatParamsForCall(args) {
|
|
2096
|
-
|
|
2977
|
+
let compression = resolveEffectivePromptCompressionOptions(
|
|
2097
2978
|
args.defaultPromptCompression,
|
|
2098
2979
|
args.callPromptCompression
|
|
2099
2980
|
);
|
|
2981
|
+
const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
|
|
2982
|
+
const policyDriven = !compression && Boolean(runtimePolicy);
|
|
2983
|
+
if (policyDriven && runtimePolicy) {
|
|
2984
|
+
if (!runtimePolicy.selected) {
|
|
2985
|
+
args.ctx.setUsage({
|
|
2986
|
+
runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
|
|
2987
|
+
});
|
|
2988
|
+
return args.params;
|
|
2989
|
+
}
|
|
2990
|
+
compression = openAIPolicyCompressionOptions(runtimePolicy);
|
|
2991
|
+
if (runtimePolicy.mode === "SHADOW") {
|
|
2992
|
+
const startedAt2 = Date.now();
|
|
2993
|
+
void compressChatParams(
|
|
2994
|
+
args.params,
|
|
2995
|
+
args.usageTap,
|
|
2996
|
+
compression,
|
|
2997
|
+
args.withUsage?.signal
|
|
2998
|
+
).then((outcome2) => {
|
|
2999
|
+
args.ctx.setUsage({
|
|
3000
|
+
runtimeCompression: openAIRuntimeMeasurement({
|
|
3001
|
+
policy: runtimePolicy,
|
|
3002
|
+
telemetry: buildPromptCompressionTelemetry(outcome2.segments),
|
|
3003
|
+
latencyMs: Date.now() - startedAt2,
|
|
3004
|
+
shadow: true,
|
|
3005
|
+
originalPayload: args.params,
|
|
3006
|
+
resultPayload: outcome2.params
|
|
3007
|
+
})
|
|
3008
|
+
});
|
|
3009
|
+
}).catch(() => {
|
|
3010
|
+
args.ctx.setUsage({
|
|
3011
|
+
runtimeCompression: failedRuntimeMeasurement(
|
|
3012
|
+
runtimePolicy,
|
|
3013
|
+
Date.now() - startedAt2
|
|
3014
|
+
)
|
|
3015
|
+
});
|
|
3016
|
+
});
|
|
3017
|
+
return args.params;
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
2100
3020
|
if (!compression) {
|
|
2101
3021
|
return args.params;
|
|
2102
3022
|
}
|
|
3023
|
+
const startedAt = Date.now();
|
|
2103
3024
|
const outcome = await compressChatParams(
|
|
2104
3025
|
args.params,
|
|
2105
3026
|
args.usageTap,
|
|
2106
3027
|
compression,
|
|
2107
3028
|
args.withUsage?.signal
|
|
2108
3029
|
);
|
|
3030
|
+
if (policyDriven && runtimePolicy) {
|
|
3031
|
+
const runtimeMeasurement = openAIRuntimeMeasurement({
|
|
3032
|
+
policy: runtimePolicy,
|
|
3033
|
+
telemetry: buildPromptCompressionTelemetry(outcome.segments),
|
|
3034
|
+
latencyMs: Date.now() - startedAt,
|
|
3035
|
+
originalPayload: args.params,
|
|
3036
|
+
resultPayload: outcome.params
|
|
3037
|
+
});
|
|
3038
|
+
args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
|
|
3039
|
+
if (runtimeMeasurement.decision !== "compressed") return args.params;
|
|
3040
|
+
}
|
|
2109
3041
|
await recordCompressionOutcome({
|
|
2110
3042
|
outcome,
|
|
2111
3043
|
compression,
|
|
@@ -2118,19 +3050,70 @@ async function compressChatParamsForCall(args) {
|
|
|
2118
3050
|
return outcome.params;
|
|
2119
3051
|
}
|
|
2120
3052
|
async function compressResponsesParamsForCall(args) {
|
|
2121
|
-
|
|
3053
|
+
let compression = resolveEffectivePromptCompressionOptions(
|
|
2122
3054
|
args.defaultPromptCompression,
|
|
2123
3055
|
args.callPromptCompression
|
|
2124
3056
|
);
|
|
3057
|
+
const runtimePolicy = args.ctx.begin.data.runtimeCompressionPolicy;
|
|
3058
|
+
const policyDriven = !compression && Boolean(runtimePolicy);
|
|
3059
|
+
if (policyDriven && runtimePolicy) {
|
|
3060
|
+
if (!runtimePolicy.selected) {
|
|
3061
|
+
args.ctx.setUsage({
|
|
3062
|
+
runtimeCompression: bypassedRuntimeMeasurement(runtimePolicy)
|
|
3063
|
+
});
|
|
3064
|
+
return args.params;
|
|
3065
|
+
}
|
|
3066
|
+
compression = openAIPolicyCompressionOptions(runtimePolicy);
|
|
3067
|
+
if (runtimePolicy.mode === "SHADOW") {
|
|
3068
|
+
const startedAt2 = Date.now();
|
|
3069
|
+
void compressResponsesParams(
|
|
3070
|
+
args.params,
|
|
3071
|
+
args.usageTap,
|
|
3072
|
+
compression,
|
|
3073
|
+
args.withUsage?.signal
|
|
3074
|
+
).then((outcome2) => {
|
|
3075
|
+
args.ctx.setUsage({
|
|
3076
|
+
runtimeCompression: openAIRuntimeMeasurement({
|
|
3077
|
+
policy: runtimePolicy,
|
|
3078
|
+
telemetry: buildPromptCompressionTelemetry(outcome2.segments),
|
|
3079
|
+
latencyMs: Date.now() - startedAt2,
|
|
3080
|
+
shadow: true,
|
|
3081
|
+
originalPayload: args.params,
|
|
3082
|
+
resultPayload: outcome2.params
|
|
3083
|
+
})
|
|
3084
|
+
});
|
|
3085
|
+
}).catch(() => {
|
|
3086
|
+
args.ctx.setUsage({
|
|
3087
|
+
runtimeCompression: failedRuntimeMeasurement(
|
|
3088
|
+
runtimePolicy,
|
|
3089
|
+
Date.now() - startedAt2
|
|
3090
|
+
)
|
|
3091
|
+
});
|
|
3092
|
+
});
|
|
3093
|
+
return args.params;
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
2125
3096
|
if (!compression) {
|
|
2126
3097
|
return args.params;
|
|
2127
3098
|
}
|
|
3099
|
+
const startedAt = Date.now();
|
|
2128
3100
|
const outcome = await compressResponsesParams(
|
|
2129
3101
|
args.params,
|
|
2130
3102
|
args.usageTap,
|
|
2131
3103
|
compression,
|
|
2132
3104
|
args.withUsage?.signal
|
|
2133
3105
|
);
|
|
3106
|
+
if (policyDriven && runtimePolicy) {
|
|
3107
|
+
const runtimeMeasurement = openAIRuntimeMeasurement({
|
|
3108
|
+
policy: runtimePolicy,
|
|
3109
|
+
telemetry: buildPromptCompressionTelemetry(outcome.segments),
|
|
3110
|
+
latencyMs: Date.now() - startedAt,
|
|
3111
|
+
originalPayload: args.params,
|
|
3112
|
+
resultPayload: outcome.params
|
|
3113
|
+
});
|
|
3114
|
+
args.ctx.setUsage({ runtimeCompression: runtimeMeasurement });
|
|
3115
|
+
if (runtimeMeasurement.decision !== "compressed") return args.params;
|
|
3116
|
+
}
|
|
2134
3117
|
await recordCompressionOutcome({
|
|
2135
3118
|
outcome,
|
|
2136
3119
|
compression,
|
|
@@ -2142,6 +3125,79 @@ async function compressResponsesParamsForCall(args) {
|
|
|
2142
3125
|
});
|
|
2143
3126
|
return outcome.params;
|
|
2144
3127
|
}
|
|
3128
|
+
function openAIPolicyCompressionOptions(policy) {
|
|
3129
|
+
const roles = Object.fromEntries(
|
|
3130
|
+
["system", "user", "tool", "assistant"].filter((role) => policy.snapshot.scope.messageRoles.includes(role)).map((role) => [role, true])
|
|
3131
|
+
);
|
|
3132
|
+
return {
|
|
3133
|
+
provider: "usagetap",
|
|
3134
|
+
roles,
|
|
3135
|
+
minContextTokens: policy.snapshot.scope.minimumTokens,
|
|
3136
|
+
failOpen: true
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
function bypassedRuntimeMeasurement(policy) {
|
|
3140
|
+
return {
|
|
3141
|
+
policyId: policy.policyId,
|
|
3142
|
+
policyVersionId: policy.policyVersionId,
|
|
3143
|
+
policyVersion: policy.version,
|
|
3144
|
+
rolloutMode: policy.mode,
|
|
3145
|
+
decision: "bypassed",
|
|
3146
|
+
reasonCode: "not_in_rollout_sample",
|
|
3147
|
+
methodsAttempted: [],
|
|
3148
|
+
methodsApplied: [],
|
|
3149
|
+
originalTokens: 0,
|
|
3150
|
+
resultTokens: 0,
|
|
3151
|
+
savedTokens: 0,
|
|
3152
|
+
reductionPercentage: 0,
|
|
3153
|
+
compressionLatencyMs: 0,
|
|
3154
|
+
compressionComputeCostUsd: 0,
|
|
3155
|
+
financialsEstimated: true
|
|
3156
|
+
};
|
|
3157
|
+
}
|
|
3158
|
+
function failedRuntimeMeasurement(policy, latencyMs) {
|
|
3159
|
+
return {
|
|
3160
|
+
...bypassedRuntimeMeasurement(policy),
|
|
3161
|
+
decision: "fallback",
|
|
3162
|
+
reasonCode: "transformation_failed",
|
|
3163
|
+
methodsAttempted: ["deterministic"],
|
|
3164
|
+
compressionLatencyMs: latencyMs
|
|
3165
|
+
};
|
|
3166
|
+
}
|
|
3167
|
+
function openAIRuntimeMeasurement(input) {
|
|
3168
|
+
const telemetry = input.telemetry;
|
|
3169
|
+
const originalTokens = telemetry?.originalTokens ?? estimatePromptTokens(JSON.stringify(input.originalPayload));
|
|
3170
|
+
const resultTokens = telemetry?.compressedTokens ?? originalTokens;
|
|
3171
|
+
const savedTokens = Math.max(0, telemetry?.savedTokens ?? 0);
|
|
3172
|
+
const reductionPercentage = originalTokens > 0 ? savedTokens / originalTokens * 100 : 0;
|
|
3173
|
+
const belowMinimumSize = originalTokens < input.policy.snapshot.scope.minimumTokens;
|
|
3174
|
+
const latencyExceeded = input.latencyMs > input.policy.snapshot.gates.maximumLatencyMs;
|
|
3175
|
+
const belowSavings = savedTokens < input.policy.snapshot.gates.minimumTokensRemoved || reductionPercentage < input.policy.snapshot.gates.minimumReductionPercentage;
|
|
3176
|
+
const accepted = !belowMinimumSize && !latencyExceeded && !belowSavings;
|
|
3177
|
+
const deepText = input.policy.snapshot.methods.deepText.enabled && !input.policy.snapshot.rollout.deterministicOnly && (input.policy.mode !== "CANARY" || input.policy.snapshot.rollout.deepTextCanaryAllowed);
|
|
3178
|
+
const reasonCode = belowMinimumSize ? "below_minimum_size" : latencyExceeded ? "latency_budget_exceeded" : belowSavings ? "below_minimum_savings" : void 0;
|
|
3179
|
+
const measurement = {
|
|
3180
|
+
policyId: input.policy.policyId,
|
|
3181
|
+
policyVersionId: input.policy.policyVersionId,
|
|
3182
|
+
policyVersion: input.policy.version,
|
|
3183
|
+
rolloutMode: input.policy.mode,
|
|
3184
|
+
decision: accepted ? input.shadow ? "bypassed" : "compressed" : latencyExceeded ? "fallback" : "skipped",
|
|
3185
|
+
...reasonCode ? { reasonCode } : {},
|
|
3186
|
+
methodsAttempted: [
|
|
3187
|
+
"deterministic",
|
|
3188
|
+
...deepText ? ["deep_text"] : []
|
|
3189
|
+
],
|
|
3190
|
+
methodsApplied: accepted ? ["deterministic"] : [],
|
|
3191
|
+
originalTokens,
|
|
3192
|
+
resultTokens: accepted ? resultTokens : originalTokens,
|
|
3193
|
+
savedTokens: accepted ? savedTokens : 0,
|
|
3194
|
+
reductionPercentage: accepted ? reductionPercentage : 0,
|
|
3195
|
+
compressionLatencyMs: input.latencyMs,
|
|
3196
|
+
compressionComputeCostUsd: 0,
|
|
3197
|
+
financialsEstimated: true
|
|
3198
|
+
};
|
|
3199
|
+
return measurement;
|
|
3200
|
+
}
|
|
2145
3201
|
async function recordCompressionOutcome(args) {
|
|
2146
3202
|
const telemetry = buildPromptCompressionTelemetry(args.outcome.segments);
|
|
2147
3203
|
if (!telemetry) {
|
|
@@ -2195,6 +3251,10 @@ async function compressChatParams(params, usageTap, compression, signal) {
|
|
|
2195
3251
|
const result = await usageTap.compressPromptMessages(source, {
|
|
2196
3252
|
provider: "usagetap",
|
|
2197
3253
|
failOpen: compression.failOpen,
|
|
3254
|
+
mode: compression.mode,
|
|
3255
|
+
latencyBudgetMs: compression.latencyBudgetMs,
|
|
3256
|
+
compactEmptyUserMessages: compression.compactEmptyUserMessages,
|
|
3257
|
+
compactDuplicateUserTextParts: compression.compactDuplicateUserTextParts,
|
|
2198
3258
|
aggressiveness: resolveMessageEndpointAggressiveness(compression),
|
|
2199
3259
|
signal
|
|
2200
3260
|
});
|
|
@@ -2660,23 +3720,53 @@ function resolveBeginRequest(defaults, override) {
|
|
|
2660
3720
|
}
|
|
2661
3721
|
const tags = mergeTags(base.tags, current.tags);
|
|
2662
3722
|
const begin = { customerId };
|
|
3723
|
+
const runtimeCompressionContext = current.runtimeCompressionContext ?? base.runtimeCompressionContext;
|
|
3724
|
+
if (runtimeCompressionContext) {
|
|
3725
|
+
begin.runtimeCompressionContext = runtimeCompressionContext;
|
|
3726
|
+
}
|
|
2663
3727
|
const requested = current.requested ?? base.requested;
|
|
2664
3728
|
if (requested) begin.requested = requested;
|
|
2665
3729
|
const feature = current.feature ?? base.feature;
|
|
2666
3730
|
if (feature) begin.feature = feature;
|
|
3731
|
+
const runId = current.runId ?? base.runId;
|
|
3732
|
+
if (runId) begin.runId = runId;
|
|
2667
3733
|
const idempotency = current.idempotency ?? base.idempotency;
|
|
2668
3734
|
if (idempotency) begin.idempotency = idempotency;
|
|
2669
3735
|
const customerName = current.customerName ?? base.customerName;
|
|
2670
3736
|
if (customerName) begin.customerName = customerName;
|
|
2671
3737
|
const customerEmail = current.customerEmail ?? base.customerEmail;
|
|
2672
3738
|
if (customerEmail) begin.customerEmail = customerEmail;
|
|
3739
|
+
const customerUserId = current.customerUserId ?? base.customerUserId;
|
|
3740
|
+
if (customerUserId) begin.customerUserId = customerUserId;
|
|
3741
|
+
const customerUserName = current.customerUserName ?? base.customerUserName;
|
|
3742
|
+
if (customerUserName) begin.customerUserName = customerUserName;
|
|
3743
|
+
const customerUserEmail = current.customerUserEmail ?? base.customerUserEmail;
|
|
3744
|
+
if (customerUserEmail) begin.customerUserEmail = customerUserEmail;
|
|
3745
|
+
const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
|
|
3746
|
+
if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
|
|
3747
|
+
const holdUsd = current.holdUsd ?? base.holdUsd;
|
|
3748
|
+
if (typeof holdUsd === "number") begin.holdUsd = holdUsd;
|
|
3749
|
+
const batch = current.batch ?? base.batch;
|
|
3750
|
+
if (typeof batch === "boolean") begin.batch = batch;
|
|
3751
|
+
const pricingMode = current.pricingMode ?? base.pricingMode;
|
|
3752
|
+
if (pricingMode) begin.pricingMode = pricingMode;
|
|
2673
3753
|
if (tags?.length) {
|
|
2674
3754
|
begin.tags = tags;
|
|
2675
3755
|
}
|
|
2676
3756
|
return begin;
|
|
2677
3757
|
}
|
|
2678
|
-
function
|
|
2679
|
-
|
|
3758
|
+
function withRuntimeCompressionContext(begin, provider, model) {
|
|
3759
|
+
return {
|
|
3760
|
+
...begin,
|
|
3761
|
+
runtimeCompressionContext: {
|
|
3762
|
+
...begin.runtimeCompressionContext ?? {},
|
|
3763
|
+
provider,
|
|
3764
|
+
model
|
|
3765
|
+
}
|
|
3766
|
+
};
|
|
3767
|
+
}
|
|
3768
|
+
function transformApiPromise(apiPromise, onResolve, onReject) {
|
|
3769
|
+
const resolvedPromise = Promise.resolve(apiPromise).then(onResolve, onReject);
|
|
2680
3770
|
if (isObjectRecord(apiPromise)) {
|
|
2681
3771
|
const proto = Object.getPrototypeOf(apiPromise);
|
|
2682
3772
|
if (proto) {
|
|
@@ -2811,12 +3901,12 @@ function applyResponsesVendorHints(params, hints) {
|
|
|
2811
3901
|
}
|
|
2812
3902
|
return next;
|
|
2813
3903
|
}
|
|
2814
|
-
async function extractUsageFromStream(stream, hints) {
|
|
3904
|
+
async function extractUsageFromStream(stream, hints, provider = "openai") {
|
|
2815
3905
|
const finalPayload = await resolveStreamFinalPayload(stream);
|
|
2816
3906
|
if (!finalPayload) {
|
|
2817
3907
|
return void 0;
|
|
2818
3908
|
}
|
|
2819
|
-
return inferUsageFromResponse(finalPayload, hints);
|
|
3909
|
+
return inferUsageFromResponse(finalPayload, hints, provider);
|
|
2820
3910
|
}
|
|
2821
3911
|
async function resolveStreamFinalPayload(stream) {
|
|
2822
3912
|
if (!stream || typeof stream !== "object") {
|
|
@@ -2889,14 +3979,14 @@ function setHeaderIfPossible(res, key, value) {
|
|
|
2889
3979
|
res.setHeader(key, value);
|
|
2890
3980
|
}
|
|
2891
3981
|
}
|
|
2892
|
-
function tryInferUsage(response, hints, extractor, ctx) {
|
|
3982
|
+
function tryInferUsage(response, hints, extractor, ctx, provider = "openai") {
|
|
2893
3983
|
const explicit = extractor?.(response);
|
|
2894
|
-
const inferred = explicit ?? inferUsageFromResponse(response, hints);
|
|
3984
|
+
const inferred = explicit ?? inferUsageFromResponse(response, hints, provider);
|
|
2895
3985
|
if (inferred) {
|
|
2896
3986
|
ctx.setUsage(inferred);
|
|
2897
3987
|
}
|
|
2898
3988
|
}
|
|
2899
|
-
function inferUsageFromResponse(response, hints) {
|
|
3989
|
+
function inferUsageFromResponse(response, hints, provider = "openai") {
|
|
2900
3990
|
if (!response || typeof response !== "object") {
|
|
2901
3991
|
return void 0;
|
|
2902
3992
|
}
|
|
@@ -2904,12 +3994,41 @@ function inferUsageFromResponse(response, hints) {
|
|
|
2904
3994
|
if (!candidate.usage) {
|
|
2905
3995
|
return void 0;
|
|
2906
3996
|
}
|
|
2907
|
-
const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
|
|
3997
|
+
const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.input_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
|
|
3998
|
+
const responseEffort = normalizeExecutionReasoningEffort(
|
|
3999
|
+
candidate.reasoning?.effort
|
|
4000
|
+
);
|
|
2908
4001
|
return {
|
|
4002
|
+
providerUsed: provider,
|
|
2909
4003
|
modelUsed: candidate.model ?? hints?.preferredModel,
|
|
2910
|
-
inputTokens: candidate.usage.prompt_tokens,
|
|
2911
|
-
responseTokens: candidate.usage.completion_tokens,
|
|
2912
|
-
cachedInputTokens
|
|
4004
|
+
inputTokens: candidate.usage.prompt_tokens ?? candidate.usage.input_tokens,
|
|
4005
|
+
responseTokens: candidate.usage.completion_tokens ?? candidate.usage.output_tokens,
|
|
4006
|
+
cachedInputTokens,
|
|
4007
|
+
reasoningTokens: candidate.usage.completion_tokens_details?.reasoning_tokens ?? candidate.usage.output_tokens_details?.reasoning_tokens,
|
|
4008
|
+
...responseEffort ? {
|
|
4009
|
+
reasoningEffort: responseEffort,
|
|
4010
|
+
reasoningEffortSource: "provider_response"
|
|
4011
|
+
} : {},
|
|
4012
|
+
...typeof candidate.reasoning?.type === "string" ? { reasoningMode: candidate.reasoning.type } : typeof candidate.reasoning?.mode === "string" ? { reasoningMode: candidate.reasoning.mode } : {}
|
|
4013
|
+
};
|
|
4014
|
+
}
|
|
4015
|
+
function normalizeExecutionReasoningEffort(value) {
|
|
4016
|
+
return value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : void 0;
|
|
4017
|
+
}
|
|
4018
|
+
function openAIRequestExecutionMetadata(params, provider) {
|
|
4019
|
+
const record = params && typeof params === "object" ? params : {};
|
|
4020
|
+
const reasoning = record.reasoning && typeof record.reasoning === "object" ? record.reasoning : void 0;
|
|
4021
|
+
const effort = normalizeExecutionReasoningEffort(
|
|
4022
|
+
record.reasoning_effort ?? reasoning?.effort ?? record.thinking_level
|
|
4023
|
+
);
|
|
4024
|
+
const mode = typeof reasoning?.type === "string" ? reasoning.type : typeof reasoning?.mode === "string" ? reasoning.mode : void 0;
|
|
4025
|
+
const rawBudget = reasoning?.budget_tokens ?? record.thinking_budget ?? record.thinking_budget_tokens;
|
|
4026
|
+
const budget = typeof rawBudget === "number" && Number.isInteger(rawBudget) && rawBudget >= 0 ? rawBudget : void 0;
|
|
4027
|
+
return {
|
|
4028
|
+
providerUsed: provider,
|
|
4029
|
+
...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
|
|
4030
|
+
...mode ? { reasoningMode: mode } : {},
|
|
4031
|
+
...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
|
|
2913
4032
|
};
|
|
2914
4033
|
}
|
|
2915
4034
|
function wrapStreamForUsageTap(source, finalize, ctx) {
|
|
@@ -3011,9 +4130,24 @@ function isIteratorResult(value) {
|
|
|
3011
4130
|
|
|
3012
4131
|
// src/adapters/openrouter.ts
|
|
3013
4132
|
function createOpenRouterAdapter(init) {
|
|
3014
|
-
return createOpenAIAdapter(init);
|
|
4133
|
+
return createOpenAIAdapter({ ...init, provider: "openrouter" });
|
|
4134
|
+
}
|
|
4135
|
+
function withMetering2(client, customer) {
|
|
4136
|
+
return withMetering(
|
|
4137
|
+
client,
|
|
4138
|
+
typeof customer === "string" ? { customerId: customer, provider: "openrouter" } : { ...customer, provider: "openrouter" }
|
|
4139
|
+
);
|
|
4140
|
+
}
|
|
4141
|
+
function wrapOpenAI2(client, usageTap, options = {}) {
|
|
4142
|
+
return wrapOpenAI(client, usageTap, {
|
|
4143
|
+
...options,
|
|
4144
|
+
provider: "openrouter"
|
|
4145
|
+
});
|
|
4146
|
+
}
|
|
4147
|
+
function withSampling2(client, options = {}) {
|
|
4148
|
+
return withSampling(client, { ...options, provider: "openrouter" });
|
|
3015
4149
|
}
|
|
3016
4150
|
|
|
3017
|
-
export { createOpenRouterAdapter, withCompression, withMetering, wrapOpenAI };
|
|
4151
|
+
export { createOpenRouterAdapter, withCompression, withMetering2 as withMetering, withSampling2 as withSampling, wrapOpenAI2 as wrapOpenAI };
|
|
3018
4152
|
//# sourceMappingURL=index.mjs.map
|
|
3019
4153
|
//# sourceMappingURL=index.mjs.map
|