@usagetap/sdk 1.4.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -18
- package/dist/adapters/anthropic.cjs +195 -235
- package/dist/adapters/anthropic.cjs.map +1 -1
- package/dist/adapters/anthropic.d.cts +1 -1
- package/dist/adapters/anthropic.d.ts +1 -1
- package/dist/adapters/anthropic.mjs +195 -235
- package/dist/adapters/anthropic.mjs.map +1 -1
- package/dist/adapters/openai.cjs +256 -274
- package/dist/adapters/openai.cjs.map +1 -1
- package/dist/adapters/openai.d.cts +1 -1
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/adapters/openai.mjs +256 -274
- package/dist/adapters/openai.mjs.map +1 -1
- package/dist/adapters/openrouter.cjs +256 -274
- package/dist/adapters/openrouter.cjs.map +1 -1
- package/dist/adapters/openrouter.d.cts +1 -1
- package/dist/adapters/openrouter.d.ts +1 -1
- package/dist/adapters/openrouter.mjs +256 -274
- package/dist/adapters/openrouter.mjs.map +1 -1
- package/dist/anthropic/index.cjs +195 -235
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.d.cts +1 -1
- package/dist/anthropic/index.d.ts +1 -1
- package/dist/anthropic/index.mjs +195 -235
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/{client-CExQ8e1T.d.cts → client-C0UiaqVB.d.cts} +141 -61
- package/dist/{client-CExQ8e1T.d.ts → client-C0UiaqVB.d.ts} +141 -61
- package/dist/express/index.cjs +189 -246
- package/dist/express/index.cjs.map +1 -1
- package/dist/express/index.d.cts +1 -1
- package/dist/express/index.d.ts +1 -1
- package/dist/express/index.mjs +189 -246
- package/dist/express/index.mjs.map +1 -1
- package/dist/index.cjs +391 -175
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.mjs +391 -175
- package/dist/index.mjs.map +1 -1
- package/dist/openai/index.cjs +256 -274
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.d.cts +1 -1
- package/dist/openai/index.d.ts +1 -1
- package/dist/openai/index.mjs +256 -274
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openrouter/index.cjs +256 -274
- package/dist/openrouter/index.cjs.map +1 -1
- package/dist/openrouter/index.d.cts +1 -1
- package/dist/openrouter/index.d.ts +1 -1
- package/dist/openrouter/index.mjs +256 -274
- package/dist/openrouter/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1027,6 +1027,8 @@ var SummarizationResource = class {
|
|
|
1027
1027
|
};
|
|
1028
1028
|
var GatewayResource = class {
|
|
1029
1029
|
chat;
|
|
1030
|
+
/** Buffered OpenAI Responses-compatible requests. */
|
|
1031
|
+
responses;
|
|
1030
1032
|
models;
|
|
1031
1033
|
batches;
|
|
1032
1034
|
transport;
|
|
@@ -1048,6 +1050,15 @@ var GatewayResource = class {
|
|
|
1048
1050
|
})
|
|
1049
1051
|
}
|
|
1050
1052
|
};
|
|
1053
|
+
this.responses = {
|
|
1054
|
+
create: (params, options) => this.transport.request({
|
|
1055
|
+
method: "POST",
|
|
1056
|
+
path: "/v1/responses",
|
|
1057
|
+
body: params,
|
|
1058
|
+
options,
|
|
1059
|
+
response: "json"
|
|
1060
|
+
})
|
|
1061
|
+
};
|
|
1051
1062
|
this.models = {
|
|
1052
1063
|
list: (options) => this.transport.request({
|
|
1053
1064
|
method: "GET",
|
|
@@ -1133,7 +1144,7 @@ var USER_AGENT = "UsageTapClient";
|
|
|
1133
1144
|
var CANONICAL_MEDIA_TYPE2 = "application/vnd.usagetap.v1+json";
|
|
1134
1145
|
var DEFAULT_BASE_URL = "https://api.usagetap.com";
|
|
1135
1146
|
var DEFAULT_RUN_INACTIVITY_MS = 60 * 60 * 1e3;
|
|
1136
|
-
var SDK_VERSION = "1.
|
|
1147
|
+
var SDK_VERSION = "1.7.0" ;
|
|
1137
1148
|
var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
1138
1149
|
var UsageTapClient = class {
|
|
1139
1150
|
/** OpenAI-compatible chat, model, and native batch operations. */
|
|
@@ -1535,9 +1546,18 @@ var UsageTapClient = class {
|
|
|
1535
1546
|
responseTokens: request.responseTokens,
|
|
1536
1547
|
cachedInputTokens: request.cachedInputTokens,
|
|
1537
1548
|
cacheWriteInputTokens: request.cacheWriteInputTokens,
|
|
1549
|
+
cacheWrite5mInputTokens: request.cacheWrite5mInputTokens,
|
|
1550
|
+
cacheWrite1hInputTokens: request.cacheWrite1hInputTokens,
|
|
1538
1551
|
reasoningTokens: request.reasoningTokens,
|
|
1539
1552
|
searches: request.searches,
|
|
1540
1553
|
audioSeconds: request.audioSeconds,
|
|
1554
|
+
imageInputCount: request.imageInputCount,
|
|
1555
|
+
imageInputTokens: request.imageInputTokens,
|
|
1556
|
+
imageOutputCount: request.imageOutputCount,
|
|
1557
|
+
imageOutputTokens: request.imageOutputTokens,
|
|
1558
|
+
audioInputTokens: request.audioInputTokens,
|
|
1559
|
+
cachedAudioInputTokens: request.cachedAudioInputTokens,
|
|
1560
|
+
audioOutputTokens: request.audioOutputTokens,
|
|
1541
1561
|
costUsd: response.data.costUSD
|
|
1542
1562
|
},
|
|
1543
1563
|
correlationId: response.correlationId
|
|
@@ -1624,10 +1644,10 @@ var UsageTapClient = class {
|
|
|
1624
1644
|
"incrementCustomMeter requires meterSlot"
|
|
1625
1645
|
);
|
|
1626
1646
|
}
|
|
1627
|
-
if (!["CUSTOM1", "CUSTOM2"].includes(request.meterSlot)) {
|
|
1647
|
+
if (!["CUSTOM1", "CUSTOM2", "AGENTIC_API"].includes(request.meterSlot)) {
|
|
1628
1648
|
throw new UsageTapError(
|
|
1629
1649
|
"USAGETAP_BAD_REQUEST",
|
|
1630
|
-
"meterSlot must be CUSTOM1 or
|
|
1650
|
+
"meterSlot must be CUSTOM1, CUSTOM2 or AGENTIC_API"
|
|
1631
1651
|
);
|
|
1632
1652
|
}
|
|
1633
1653
|
if (typeof request.amount !== "number" || !Number.isFinite(request.amount) || request.amount <= 0) {
|
|
@@ -1700,6 +1720,28 @@ var UsageTapClient = class {
|
|
|
1700
1720
|
let handlerResult;
|
|
1701
1721
|
let handlerError;
|
|
1702
1722
|
let endCallError;
|
|
1723
|
+
let finalizationDeferred = false;
|
|
1724
|
+
let finalizationPromise;
|
|
1725
|
+
const finalize = () => {
|
|
1726
|
+
if (!finalizationPromise) {
|
|
1727
|
+
finalizationPromise = this.endCall(
|
|
1728
|
+
{
|
|
1729
|
+
callId: beginResponse.data.callId,
|
|
1730
|
+
// Pass context for metric tracking
|
|
1731
|
+
customerId: beginRequest.customerId,
|
|
1732
|
+
feature: beginRequest.feature ?? this.defaultFeature,
|
|
1733
|
+
tags: beginRequest.tags ?? this.defaultTags,
|
|
1734
|
+
...usage,
|
|
1735
|
+
error: errorPayload
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
...options,
|
|
1739
|
+
correlationId: beginResponse.correlationId
|
|
1740
|
+
}
|
|
1741
|
+
).then(() => void 0);
|
|
1742
|
+
}
|
|
1743
|
+
return finalizationPromise;
|
|
1744
|
+
};
|
|
1703
1745
|
const context = {
|
|
1704
1746
|
begin: beginResponse,
|
|
1705
1747
|
setUsage: (u) => {
|
|
@@ -1707,6 +1749,10 @@ var UsageTapClient = class {
|
|
|
1707
1749
|
},
|
|
1708
1750
|
setError: (err) => {
|
|
1709
1751
|
errorPayload = err;
|
|
1752
|
+
},
|
|
1753
|
+
deferFinalization: () => {
|
|
1754
|
+
finalizationDeferred = true;
|
|
1755
|
+
return finalize;
|
|
1710
1756
|
}
|
|
1711
1757
|
};
|
|
1712
1758
|
try {
|
|
@@ -1720,24 +1766,12 @@ var UsageTapClient = class {
|
|
|
1720
1766
|
};
|
|
1721
1767
|
}
|
|
1722
1768
|
} finally {
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
feature: beginRequest.feature ?? this.defaultFeature,
|
|
1730
|
-
tags: beginRequest.tags ?? this.defaultTags,
|
|
1731
|
-
...usage,
|
|
1732
|
-
error: errorPayload
|
|
1733
|
-
},
|
|
1734
|
-
{
|
|
1735
|
-
...options,
|
|
1736
|
-
correlationId: beginResponse.correlationId
|
|
1737
|
-
}
|
|
1738
|
-
);
|
|
1739
|
-
} catch (error) {
|
|
1740
|
-
endCallError = error;
|
|
1769
|
+
if (handlerError || !finalizationDeferred) {
|
|
1770
|
+
try {
|
|
1771
|
+
await finalize();
|
|
1772
|
+
} catch (error) {
|
|
1773
|
+
endCallError = error;
|
|
1774
|
+
}
|
|
1741
1775
|
}
|
|
1742
1776
|
}
|
|
1743
1777
|
if (handlerError) {
|
|
@@ -2079,7 +2113,8 @@ var UsageTapClient = class {
|
|
|
2079
2113
|
}
|
|
2080
2114
|
toHttpError(status, payload, correlationId) {
|
|
2081
2115
|
const code = mapStatusToErrorCode(status);
|
|
2082
|
-
const
|
|
2116
|
+
const apiCode = payload?.error?.code ?? payload?.result?.code ?? "UNKNOWN";
|
|
2117
|
+
const retryable = isRetryableStatus(status) || isRetryableApiCode(apiCode);
|
|
2083
2118
|
const message = payload?.error?.message ?? payload?.result?.message ?? `UsageTap responded with HTTP ${status}`;
|
|
2084
2119
|
return new UsageTapError(code, message, {
|
|
2085
2120
|
status,
|
|
@@ -2112,7 +2147,7 @@ function isRetryableStatus(status) {
|
|
|
2112
2147
|
}
|
|
2113
2148
|
function isRetryableApiCode(code) {
|
|
2114
2149
|
const normalized = code.toUpperCase();
|
|
2115
|
-
return normalized.includes("TRANSIENT") || normalized.includes("RETRY") || normalized.includes("TIMEOUT") || normalized.includes("THROTTLE") || normalized.includes("RATE_LIMIT");
|
|
2150
|
+
return normalized === "PAYG_CONFLICT" || normalized.includes("TRANSIENT") || normalized.includes("RETRY") || normalized.includes("TIMEOUT") || normalized.includes("THROTTLE") || normalized.includes("RATE_LIMIT");
|
|
2116
2151
|
}
|
|
2117
2152
|
function mapApiCodeToError(code) {
|
|
2118
2153
|
const normalized = code.toUpperCase();
|
|
@@ -2181,6 +2216,12 @@ function wrapEndCallError(error, correlationId) {
|
|
|
2181
2216
|
}
|
|
2182
2217
|
|
|
2183
2218
|
// src/adapters/fetch-wrapper.ts
|
|
2219
|
+
var USAGETAP_CONTEXT_HEADERS = [
|
|
2220
|
+
"x-usagetap-customer-id",
|
|
2221
|
+
"x-usagetap-feature",
|
|
2222
|
+
"x-usagetap-tags",
|
|
2223
|
+
"x-usagetap-run-id"
|
|
2224
|
+
];
|
|
2184
2225
|
function isJsonRecord(value) {
|
|
2185
2226
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2186
2227
|
}
|
|
@@ -2209,59 +2250,103 @@ function parseStringArray(value) {
|
|
|
2209
2250
|
}
|
|
2210
2251
|
return void 0;
|
|
2211
2252
|
}
|
|
2253
|
+
function contextOverrideFromHeaders(headers) {
|
|
2254
|
+
const context = {};
|
|
2255
|
+
const customerId = headers.get("x-usagetap-customer-id");
|
|
2256
|
+
const feature = headers.get("x-usagetap-feature");
|
|
2257
|
+
const tags = headers.get("x-usagetap-tags");
|
|
2258
|
+
const runId = headers.get("x-usagetap-run-id");
|
|
2259
|
+
if (customerId) context.customerId = customerId;
|
|
2260
|
+
if (feature) context.feature = feature;
|
|
2261
|
+
if (tags) {
|
|
2262
|
+
const parsed = parseStringArray(tags);
|
|
2263
|
+
if (parsed) context.tags = parsed;
|
|
2264
|
+
}
|
|
2265
|
+
if (runId) context.runId = runId;
|
|
2266
|
+
return context;
|
|
2267
|
+
}
|
|
2268
|
+
function ensureStreamUsageBody(body) {
|
|
2269
|
+
const streamOptions = isJsonRecord(body.stream_options) ? body.stream_options : {};
|
|
2270
|
+
return {
|
|
2271
|
+
...body,
|
|
2272
|
+
stream_options: {
|
|
2273
|
+
...streamOptions,
|
|
2274
|
+
include_usage: true
|
|
2275
|
+
}
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
async function bodyTextFromInit(body) {
|
|
2279
|
+
if (typeof body === "string") return body;
|
|
2280
|
+
if (body instanceof URLSearchParams) return body.toString();
|
|
2281
|
+
if (body instanceof Blob) return body.text();
|
|
2282
|
+
if (body instanceof ArrayBuffer) return new TextDecoder().decode(body);
|
|
2283
|
+
if (ArrayBuffer.isView(body)) {
|
|
2284
|
+
return new TextDecoder().decode(
|
|
2285
|
+
new Uint8Array(body.buffer, body.byteOffset, body.byteLength)
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
return void 0;
|
|
2289
|
+
}
|
|
2212
2290
|
function wrapFetch(usageTap, options) {
|
|
2213
2291
|
const {
|
|
2214
2292
|
defaultContext,
|
|
2215
2293
|
baseFetch = globalThis.fetch,
|
|
2216
2294
|
autoIdempotency = true,
|
|
2217
2295
|
isOpenAIEndpoint = defaultIsOpenAIEndpoint,
|
|
2218
|
-
provider = "openai"
|
|
2296
|
+
provider = "openai",
|
|
2297
|
+
onMeteringError,
|
|
2298
|
+
strictMetering = false
|
|
2219
2299
|
} = options;
|
|
2300
|
+
const meteringFailureOptions = { onMeteringError, strictMetering };
|
|
2220
2301
|
return async function wrappedFetch(input, init) {
|
|
2221
2302
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
2222
2303
|
if (!isOpenAIEndpoint(url)) {
|
|
2223
2304
|
return baseFetch(input, init);
|
|
2224
2305
|
}
|
|
2306
|
+
const inputRequest = input instanceof Request ? input : void 0;
|
|
2307
|
+
const effectiveHeaders = new Headers(inputRequest?.headers);
|
|
2308
|
+
new Headers(init?.headers).forEach((value, key) => effectiveHeaders.set(key, value));
|
|
2309
|
+
const vendorHeaders = new Headers(effectiveHeaders);
|
|
2310
|
+
const contextOverride = contextOverrideFromHeaders(effectiveHeaders);
|
|
2311
|
+
for (const header of USAGETAP_CONTEXT_HEADERS) {
|
|
2312
|
+
vendorHeaders.delete(header);
|
|
2313
|
+
}
|
|
2225
2314
|
let body;
|
|
2226
2315
|
let isStreaming = false;
|
|
2227
|
-
const contextOverride = {};
|
|
2228
2316
|
try {
|
|
2229
|
-
if (init?.body
|
|
2230
|
-
const
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
body = parsedBody;
|
|
2235
|
-
isStreaming = parsedBody.stream === true;
|
|
2236
|
-
const headers = new Headers(init.headers);
|
|
2237
|
-
const customerIdHeader = headers.get("x-usagetap-customer-id");
|
|
2238
|
-
const featureHeader = headers.get("x-usagetap-feature");
|
|
2239
|
-
const tagsHeader = headers.get("x-usagetap-tags");
|
|
2240
|
-
const runIdHeader = headers.get("x-usagetap-run-id");
|
|
2241
|
-
if (customerIdHeader) {
|
|
2242
|
-
contextOverride.customerId = customerIdHeader;
|
|
2243
|
-
}
|
|
2244
|
-
if (featureHeader) {
|
|
2245
|
-
contextOverride.feature = featureHeader;
|
|
2246
|
-
}
|
|
2247
|
-
if (tagsHeader) {
|
|
2248
|
-
const tags = parseStringArray(tagsHeader);
|
|
2249
|
-
if (tags) {
|
|
2250
|
-
contextOverride.tags = tags;
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2253
|
-
if (runIdHeader) {
|
|
2254
|
-
contextOverride.runId = runIdHeader;
|
|
2255
|
-
}
|
|
2317
|
+
if (init?.body) {
|
|
2318
|
+
const bodyText = await bodyTextFromInit(init.body);
|
|
2319
|
+
body = bodyText ? parseJsonRecord(bodyText) : void 0;
|
|
2320
|
+
} else if (inputRequest?.body) {
|
|
2321
|
+
body = parseJsonRecord(await inputRequest.clone().text());
|
|
2256
2322
|
}
|
|
2257
2323
|
} catch {
|
|
2258
|
-
|
|
2324
|
+
body = void 0;
|
|
2325
|
+
}
|
|
2326
|
+
if (!body) {
|
|
2327
|
+
const error = new Error(
|
|
2328
|
+
"Unable to parse the intercepted provider request body as JSON"
|
|
2329
|
+
);
|
|
2330
|
+
await reportMeteringError(meteringFailureOptions, {
|
|
2331
|
+
stage: "request",
|
|
2332
|
+
error
|
|
2333
|
+
});
|
|
2334
|
+
if (strictMetering) throw error;
|
|
2335
|
+
return baseFetch(input, { ...init, headers: vendorHeaders });
|
|
2259
2336
|
}
|
|
2337
|
+
isStreaming = body.stream === true;
|
|
2338
|
+
const vendorBody = isStreaming && url.includes("/v1/chat/completions") ? JSON.stringify(ensureStreamUsageBody(body)) : void 0;
|
|
2260
2339
|
const context = {
|
|
2261
2340
|
...defaultContext,
|
|
2262
2341
|
...contextOverride,
|
|
2263
2342
|
idempotency: autoIdempotency ? crypto.randomUUID() : void 0
|
|
2264
2343
|
};
|
|
2344
|
+
if (requestUsesWebSearch(body)) {
|
|
2345
|
+
context.requested = {
|
|
2346
|
+
...context.requested ?? {},
|
|
2347
|
+
search: true
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2265
2350
|
let callState;
|
|
2266
2351
|
try {
|
|
2267
2352
|
const beginResponse = await usageTap.beginCall(context);
|
|
@@ -2280,36 +2365,64 @@ function wrapFetch(usageTap, options) {
|
|
|
2280
2365
|
batch: context.batch,
|
|
2281
2366
|
pricingMode: context.batch ? "batch" : "standard"
|
|
2282
2367
|
} : {}
|
|
2283
|
-
}
|
|
2284
|
-
finalized: false
|
|
2368
|
+
}
|
|
2285
2369
|
};
|
|
2286
2370
|
} catch (error) {
|
|
2287
2371
|
if (isUsageTapError(error) && error.code === "USAGETAP_CIRCUIT_OPEN") {
|
|
2288
2372
|
throw error;
|
|
2289
2373
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
2374
|
+
await reportMeteringError(meteringFailureOptions, {
|
|
2375
|
+
stage: "begin",
|
|
2376
|
+
error
|
|
2377
|
+
});
|
|
2378
|
+
if (strictMetering) throw error;
|
|
2379
|
+
return baseFetch(input, {
|
|
2380
|
+
...init,
|
|
2381
|
+
headers: vendorHeaders,
|
|
2382
|
+
...vendorBody ? { body: vendorBody } : {}
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
if (callState.correlationId) {
|
|
2386
|
+
vendorHeaders.set("x-usage-correlation-id", callState.correlationId);
|
|
2292
2387
|
}
|
|
2293
2388
|
const modifiedInit = {
|
|
2294
2389
|
...init,
|
|
2295
|
-
headers:
|
|
2296
|
-
|
|
2297
|
-
"x-usage-correlation-id": callState.correlationId || ""
|
|
2298
|
-
}
|
|
2390
|
+
headers: vendorHeaders,
|
|
2391
|
+
...vendorBody ? { body: vendorBody } : {}
|
|
2299
2392
|
};
|
|
2300
2393
|
try {
|
|
2301
2394
|
const response = await baseFetch(input, modifiedInit);
|
|
2395
|
+
if (!response.ok) {
|
|
2396
|
+
await finalizeHttpError(
|
|
2397
|
+
response,
|
|
2398
|
+
callState,
|
|
2399
|
+
usageTap,
|
|
2400
|
+
meteringFailureOptions
|
|
2401
|
+
);
|
|
2402
|
+
return response;
|
|
2403
|
+
}
|
|
2302
2404
|
if (isStreaming) {
|
|
2303
|
-
return wrapStreamingResponse(
|
|
2405
|
+
return await wrapStreamingResponse(
|
|
2406
|
+
response,
|
|
2407
|
+
callState,
|
|
2408
|
+
usageTap,
|
|
2409
|
+
meteringFailureOptions
|
|
2410
|
+
);
|
|
2304
2411
|
} else {
|
|
2305
|
-
return await wrapNonStreamingResponse(
|
|
2412
|
+
return await wrapNonStreamingResponse(
|
|
2413
|
+
response,
|
|
2414
|
+
callState,
|
|
2415
|
+
usageTap,
|
|
2416
|
+
body,
|
|
2417
|
+
meteringFailureOptions
|
|
2418
|
+
);
|
|
2306
2419
|
}
|
|
2307
2420
|
} catch (error) {
|
|
2308
2421
|
const message = error instanceof Error ? error.message : String(error);
|
|
2309
|
-
await finalizeCall(callState, usageTap, {
|
|
2422
|
+
await finalizeCall(callState, usageTap, meteringFailureOptions, {
|
|
2310
2423
|
code: "VENDOR_ERROR",
|
|
2311
2424
|
message
|
|
2312
|
-
});
|
|
2425
|
+
}, { responseErrorMessage: message });
|
|
2313
2426
|
throw error;
|
|
2314
2427
|
}
|
|
2315
2428
|
};
|
|
@@ -2317,137 +2430,217 @@ function wrapFetch(usageTap, options) {
|
|
|
2317
2430
|
function defaultIsOpenAIEndpoint(url) {
|
|
2318
2431
|
return url.includes("/v1/chat/completions") || url.includes("/v1/responses") || url.includes("/v1/embeddings");
|
|
2319
2432
|
}
|
|
2320
|
-
|
|
2433
|
+
function requestUsesWebSearch(body) {
|
|
2434
|
+
return Array.isArray(body?.tools) && body.tools.some((tool) => {
|
|
2435
|
+
if (!isJsonRecord(tool)) return false;
|
|
2436
|
+
return tool.type === "web_search" || tool.type === "web_search_preview";
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
function usageFromOpenAIResponse(parsed, provider) {
|
|
2440
|
+
const usage = {};
|
|
2441
|
+
const usageBlock = parsed.usage;
|
|
2442
|
+
if (isJsonRecord(usageBlock)) {
|
|
2443
|
+
const usageRecord = usageBlock;
|
|
2444
|
+
const promptDetails = isJsonRecord(
|
|
2445
|
+
usageRecord.prompt_tokens_details ?? usageRecord.input_tokens_details
|
|
2446
|
+
) ? usageRecord.prompt_tokens_details ?? usageRecord.input_tokens_details : void 0;
|
|
2447
|
+
const completionDetails = isJsonRecord(
|
|
2448
|
+
usageRecord.completion_tokens_details ?? usageRecord.output_tokens_details
|
|
2449
|
+
) ? usageRecord.completion_tokens_details ?? usageRecord.output_tokens_details : void 0;
|
|
2450
|
+
const cachedTokenDetails = isJsonRecord(promptDetails?.cached_tokens_details) ? promptDetails.cached_tokens_details : void 0;
|
|
2451
|
+
const cacheCreation = isJsonRecord(usageRecord.cache_creation) ? usageRecord.cache_creation : void 0;
|
|
2452
|
+
const promptTokens = readNumber(
|
|
2453
|
+
usageRecord.prompt_tokens ?? usageRecord.input_tokens
|
|
2454
|
+
);
|
|
2455
|
+
if (promptTokens !== void 0) usage.inputTokens = promptTokens;
|
|
2456
|
+
const completionTokens = readNumber(
|
|
2457
|
+
usageRecord.completion_tokens ?? usageRecord.output_tokens
|
|
2458
|
+
);
|
|
2459
|
+
if (completionTokens !== void 0) usage.responseTokens = completionTokens;
|
|
2460
|
+
const cachedTokens = readNumber(
|
|
2461
|
+
usageRecord.prompt_cache_hit_tokens ?? usageRecord.cache_read_input_tokens ?? usageRecord.cached_tokens ?? promptDetails?.cached_tokens
|
|
2462
|
+
);
|
|
2463
|
+
if (cachedTokens !== void 0) usage.cachedInputTokens = cachedTokens;
|
|
2464
|
+
const cacheWriteTokens = readNumber(
|
|
2465
|
+
usageRecord.cache_creation_input_tokens ?? usageRecord.cache_write_input_tokens ?? usageRecord.cache_write_tokens ?? promptDetails?.cache_write_tokens ?? promptDetails?.cache_creation_tokens
|
|
2466
|
+
);
|
|
2467
|
+
if (cacheWriteTokens !== void 0) {
|
|
2468
|
+
usage.cacheWriteInputTokens = cacheWriteTokens;
|
|
2469
|
+
}
|
|
2470
|
+
const cacheWrite5m = readNumber(
|
|
2471
|
+
usageRecord.cache_write_5m_input_tokens ?? cacheCreation?.ephemeral_5m_input_tokens
|
|
2472
|
+
);
|
|
2473
|
+
if (cacheWrite5m !== void 0) usage.cacheWrite5mInputTokens = cacheWrite5m;
|
|
2474
|
+
const cacheWrite1h = readNumber(
|
|
2475
|
+
usageRecord.cache_write_1h_input_tokens ?? cacheCreation?.ephemeral_1h_input_tokens
|
|
2476
|
+
);
|
|
2477
|
+
if (cacheWrite1h !== void 0) usage.cacheWrite1hInputTokens = cacheWrite1h;
|
|
2478
|
+
if (provider?.toLowerCase() === "anthropic" && promptTokens !== void 0) {
|
|
2479
|
+
usage.inputTokens = promptTokens + (cachedTokens ?? 0) + (cacheWriteTokens ?? 0);
|
|
2480
|
+
}
|
|
2481
|
+
const audioInputTokens = readNumber(promptDetails?.audio_tokens);
|
|
2482
|
+
if (audioInputTokens !== void 0) usage.audioInputTokens = audioInputTokens;
|
|
2483
|
+
const cachedAudioInputTokens = readNumber(
|
|
2484
|
+
promptDetails?.cached_audio_tokens ?? cachedTokenDetails?.audio_tokens
|
|
2485
|
+
);
|
|
2486
|
+
if (cachedAudioInputTokens !== void 0) {
|
|
2487
|
+
usage.cachedAudioInputTokens = cachedAudioInputTokens;
|
|
2488
|
+
}
|
|
2489
|
+
const imageInputTokens = readNumber(promptDetails?.image_tokens);
|
|
2490
|
+
if (imageInputTokens !== void 0) usage.imageInputTokens = imageInputTokens;
|
|
2491
|
+
const imageOutputTokens = readNumber(completionDetails?.image_tokens);
|
|
2492
|
+
if (imageOutputTokens !== void 0) usage.imageOutputTokens = imageOutputTokens;
|
|
2493
|
+
const audioOutputTokens = readNumber(completionDetails?.audio_tokens);
|
|
2494
|
+
if (audioOutputTokens !== void 0) usage.audioOutputTokens = audioOutputTokens;
|
|
2495
|
+
const reasoningTokens = readNumber(
|
|
2496
|
+
usageRecord.reasoning_tokens ?? completionDetails?.reasoning_tokens
|
|
2497
|
+
);
|
|
2498
|
+
if (reasoningTokens !== void 0) usage.reasoningTokens = reasoningTokens;
|
|
2499
|
+
const serverToolUse = isJsonRecord(usageRecord.server_tool_use) ? usageRecord.server_tool_use : void 0;
|
|
2500
|
+
const outputSearches = Array.isArray(parsed.output) ? parsed.output.filter(
|
|
2501
|
+
(item) => isJsonRecord(item) && item.type === "web_search_call"
|
|
2502
|
+
).length : 0;
|
|
2503
|
+
const searches = readNumber(
|
|
2504
|
+
usageRecord.searches ?? usageRecord.web_search_queries ?? serverToolUse?.web_search_requests ?? (outputSearches > 0 ? outputSearches : void 0)
|
|
2505
|
+
);
|
|
2506
|
+
if (searches !== void 0 && searches > 0) usage.searches = searches;
|
|
2507
|
+
}
|
|
2508
|
+
const modelFromResponse = readString(parsed.model);
|
|
2509
|
+
if (modelFromResponse) usage.modelUsed = modelFromResponse;
|
|
2510
|
+
const reasoning = isJsonRecord(parsed.reasoning) ? parsed.reasoning : void 0;
|
|
2511
|
+
const responseEffort = normalizeReasoningEffort(reasoning?.effort);
|
|
2512
|
+
if (responseEffort) {
|
|
2513
|
+
usage.reasoningEffort = responseEffort;
|
|
2514
|
+
usage.reasoningEffortSource = "provider_response";
|
|
2515
|
+
}
|
|
2516
|
+
const responseMode = readString(reasoning?.type ?? reasoning?.mode);
|
|
2517
|
+
if (responseMode) usage.reasoningMode = responseMode;
|
|
2518
|
+
return usage;
|
|
2519
|
+
}
|
|
2520
|
+
async function wrapNonStreamingResponse(response, callState, usageTap, requestBody, meteringFailureOptions) {
|
|
2321
2521
|
const clonedResponse = response.clone();
|
|
2322
2522
|
try {
|
|
2323
2523
|
const parsed = await clonedResponse.json();
|
|
2324
|
-
const usage = {};
|
|
2325
|
-
if (isJsonRecord(parsed)) {
|
|
2326
|
-
const usageBlock = parsed.usage;
|
|
2327
|
-
if (isJsonRecord(usageBlock)) {
|
|
2328
|
-
const usageRecord = usageBlock;
|
|
2329
|
-
const promptDetails = isJsonRecord(usageRecord.prompt_tokens_details) ? usageRecord.prompt_tokens_details : void 0;
|
|
2330
|
-
const completionDetails = isJsonRecord(
|
|
2331
|
-
usageRecord.completion_tokens_details ?? usageRecord.output_tokens_details
|
|
2332
|
-
) ? usageRecord.completion_tokens_details ?? usageRecord.output_tokens_details : void 0;
|
|
2333
|
-
const promptTokens = readNumber(usageRecord.prompt_tokens ?? usageRecord.input_tokens);
|
|
2334
|
-
if (promptTokens !== void 0) {
|
|
2335
|
-
usage.inputTokens = promptTokens;
|
|
2336
|
-
}
|
|
2337
|
-
const completionTokens = readNumber(usageRecord.completion_tokens ?? usageRecord.output_tokens);
|
|
2338
|
-
if (completionTokens !== void 0) {
|
|
2339
|
-
usage.responseTokens = completionTokens;
|
|
2340
|
-
}
|
|
2341
|
-
const cachedTokens = readNumber(
|
|
2342
|
-
usageRecord.prompt_cache_hit_tokens ?? usageRecord.cache_read_input_tokens ?? usageRecord.cached_tokens ?? promptDetails?.cached_tokens
|
|
2343
|
-
);
|
|
2344
|
-
if (cachedTokens !== void 0) {
|
|
2345
|
-
usage.cachedInputTokens = cachedTokens;
|
|
2346
|
-
}
|
|
2347
|
-
const reasoningTokens = readNumber(
|
|
2348
|
-
usageRecord.reasoning_tokens ?? completionDetails?.reasoning_tokens
|
|
2349
|
-
);
|
|
2350
|
-
if (reasoningTokens !== void 0) {
|
|
2351
|
-
usage.reasoningTokens = reasoningTokens;
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
const modelFromResponse = readString(parsed.model);
|
|
2355
|
-
if (modelFromResponse) {
|
|
2356
|
-
usage.modelUsed = modelFromResponse;
|
|
2357
|
-
}
|
|
2358
|
-
const reasoning = isJsonRecord(parsed.reasoning) ? parsed.reasoning : void 0;
|
|
2359
|
-
const responseEffort = normalizeReasoningEffort(reasoning?.effort);
|
|
2360
|
-
if (responseEffort) {
|
|
2361
|
-
usage.reasoningEffort = responseEffort;
|
|
2362
|
-
usage.reasoningEffortSource = "provider_response";
|
|
2363
|
-
}
|
|
2364
|
-
const responseMode = readString(reasoning?.type ?? reasoning?.mode);
|
|
2365
|
-
if (responseMode) usage.reasoningMode = responseMode;
|
|
2366
|
-
}
|
|
2524
|
+
const usage = isJsonRecord(parsed) ? usageFromOpenAIResponse(parsed, readString(callState.usage.providerUsed)) : {};
|
|
2367
2525
|
if (!usage.modelUsed && requestBody) {
|
|
2368
2526
|
const requestModel = readString(requestBody.model);
|
|
2369
2527
|
if (requestModel) {
|
|
2370
2528
|
usage.modelUsed = requestModel;
|
|
2371
2529
|
}
|
|
2372
2530
|
}
|
|
2373
|
-
await finalizeCall(callState, usageTap, void 0,
|
|
2531
|
+
await finalizeCall(callState, usageTap, meteringFailureOptions, void 0, {
|
|
2532
|
+
...usage,
|
|
2533
|
+
responseStatusCode: response.status
|
|
2534
|
+
});
|
|
2374
2535
|
return response;
|
|
2375
2536
|
} catch (error) {
|
|
2376
2537
|
const message = error instanceof Error ? error.message : String(error);
|
|
2377
|
-
await finalizeCall(callState, usageTap, {
|
|
2538
|
+
await finalizeCall(callState, usageTap, meteringFailureOptions, {
|
|
2378
2539
|
code: "RESPONSE_PARSE_ERROR",
|
|
2379
2540
|
message
|
|
2541
|
+
}, {
|
|
2542
|
+
responseStatusCode: response.status,
|
|
2543
|
+
responseErrorMessage: message
|
|
2380
2544
|
});
|
|
2381
2545
|
return response;
|
|
2382
2546
|
}
|
|
2383
2547
|
}
|
|
2384
|
-
function
|
|
2548
|
+
async function finalizeHttpError(response, callState, usageTap, meteringFailureOptions) {
|
|
2549
|
+
let message = `${response.status} ${response.statusText || "Provider request failed"}`.trim();
|
|
2550
|
+
try {
|
|
2551
|
+
const text = await response.clone().text();
|
|
2552
|
+
const parsed = parseJsonRecord(text);
|
|
2553
|
+
const errorRecord = parsed && isJsonRecord(parsed.error) ? parsed.error : void 0;
|
|
2554
|
+
message = readString(errorRecord?.message) ?? readString(parsed?.message) ?? (text.trim() || message);
|
|
2555
|
+
} catch {
|
|
2556
|
+
}
|
|
2557
|
+
await finalizeCall(callState, usageTap, meteringFailureOptions, {
|
|
2558
|
+
code: "VENDOR_HTTP_ERROR",
|
|
2559
|
+
message
|
|
2560
|
+
}, {
|
|
2561
|
+
responseStatusCode: response.status,
|
|
2562
|
+
responseErrorMessage: message
|
|
2563
|
+
});
|
|
2564
|
+
}
|
|
2565
|
+
async function wrapStreamingResponse(response, callState, usageTap, meteringFailureOptions) {
|
|
2385
2566
|
if (!response.body) {
|
|
2386
|
-
|
|
2567
|
+
await finalizeCall(callState, usageTap, meteringFailureOptions, {
|
|
2387
2568
|
code: "NO_RESPONSE_BODY",
|
|
2388
2569
|
message: "Streaming response has no body"
|
|
2570
|
+
}, {
|
|
2571
|
+
responseStatusCode: response.status,
|
|
2572
|
+
responseErrorMessage: "Streaming response has no body"
|
|
2389
2573
|
});
|
|
2390
2574
|
return response;
|
|
2391
2575
|
}
|
|
2392
|
-
const
|
|
2576
|
+
const reader = response.body.getReader();
|
|
2393
2577
|
const accumulatedUsage = {};
|
|
2394
2578
|
const textDecoder = new TextDecoder();
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2579
|
+
let pendingSseText = "";
|
|
2580
|
+
const applyUsageEvent = (eventText) => {
|
|
2581
|
+
const data = eventText.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
2582
|
+
if (!data || data === "[DONE]") return;
|
|
2583
|
+
const parsedRecord = parseJsonRecord(data);
|
|
2584
|
+
if (!parsedRecord) return;
|
|
2585
|
+
const responseRecord = isJsonRecord(parsedRecord.response) ? parsedRecord.response : parsedRecord;
|
|
2586
|
+
Object.assign(
|
|
2587
|
+
accumulatedUsage,
|
|
2588
|
+
usageFromOpenAIResponse(
|
|
2589
|
+
responseRecord,
|
|
2590
|
+
readString(callState.usage.providerUsed)
|
|
2591
|
+
)
|
|
2592
|
+
);
|
|
2593
|
+
};
|
|
2594
|
+
const applyCompleteSseEvents = (final = false) => {
|
|
2595
|
+
const events = pendingSseText.split(/\r?\n\r?\n/);
|
|
2596
|
+
const tail = events.pop() ?? "";
|
|
2597
|
+
pendingSseText = final ? "" : tail;
|
|
2598
|
+
for (const eventText of events) applyUsageEvent(eventText);
|
|
2599
|
+
if (final && tail) applyUsageEvent(tail);
|
|
2600
|
+
};
|
|
2601
|
+
const finalizeStream = async (error) => {
|
|
2602
|
+
pendingSseText += textDecoder.decode();
|
|
2603
|
+
applyCompleteSseEvents(true);
|
|
2604
|
+
await finalizeCall(
|
|
2605
|
+
callState,
|
|
2606
|
+
usageTap,
|
|
2607
|
+
meteringFailureOptions,
|
|
2608
|
+
error,
|
|
2609
|
+
{
|
|
2610
|
+
...accumulatedUsage,
|
|
2611
|
+
responseStatusCode: response.status,
|
|
2612
|
+
...error ? { responseErrorMessage: error.message } : {}
|
|
2613
|
+
}
|
|
2614
|
+
);
|
|
2615
|
+
};
|
|
2616
|
+
const wrappedBody = new ReadableStream({
|
|
2617
|
+
async pull(controller) {
|
|
2398
2618
|
try {
|
|
2399
|
-
const
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
if (data === "[DONE]") continue;
|
|
2405
|
-
const parsedRecord = parseJsonRecord(data);
|
|
2406
|
-
if (!parsedRecord) {
|
|
2407
|
-
continue;
|
|
2408
|
-
}
|
|
2409
|
-
const usageBlock = parsedRecord.usage;
|
|
2410
|
-
if (isJsonRecord(usageBlock)) {
|
|
2411
|
-
const usageRecord = usageBlock;
|
|
2412
|
-
const promptDetails = isJsonRecord(usageRecord.prompt_tokens_details) ? usageRecord.prompt_tokens_details : void 0;
|
|
2413
|
-
const completionDetails = isJsonRecord(
|
|
2414
|
-
usageRecord.completion_tokens_details ?? usageRecord.output_tokens_details
|
|
2415
|
-
) ? usageRecord.completion_tokens_details ?? usageRecord.output_tokens_details : void 0;
|
|
2416
|
-
const promptTokens = readNumber(usageRecord.prompt_tokens ?? usageRecord.input_tokens);
|
|
2417
|
-
if (promptTokens !== void 0) {
|
|
2418
|
-
accumulatedUsage.inputTokens = promptTokens;
|
|
2419
|
-
}
|
|
2420
|
-
const completionTokens = readNumber(usageRecord.completion_tokens ?? usageRecord.output_tokens);
|
|
2421
|
-
if (completionTokens !== void 0) {
|
|
2422
|
-
accumulatedUsage.responseTokens = completionTokens;
|
|
2423
|
-
}
|
|
2424
|
-
const cachedTokens = readNumber(
|
|
2425
|
-
usageRecord.prompt_cache_hit_tokens ?? usageRecord.cache_read_input_tokens ?? usageRecord.cached_tokens ?? promptDetails?.cached_tokens
|
|
2426
|
-
);
|
|
2427
|
-
if (cachedTokens !== void 0) {
|
|
2428
|
-
accumulatedUsage.cachedInputTokens = cachedTokens;
|
|
2429
|
-
}
|
|
2430
|
-
const reasoningTokens = readNumber(
|
|
2431
|
-
usageRecord.reasoning_tokens ?? completionDetails?.reasoning_tokens
|
|
2432
|
-
);
|
|
2433
|
-
if (reasoningTokens !== void 0) {
|
|
2434
|
-
accumulatedUsage.reasoningTokens = reasoningTokens;
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
const model = readString(parsedRecord.model);
|
|
2438
|
-
if (model) {
|
|
2439
|
-
accumulatedUsage.modelUsed = model;
|
|
2440
|
-
}
|
|
2441
|
-
}
|
|
2619
|
+
const result = await reader.read();
|
|
2620
|
+
if (result.done) {
|
|
2621
|
+
await finalizeStream();
|
|
2622
|
+
controller.close();
|
|
2623
|
+
return;
|
|
2442
2624
|
}
|
|
2443
|
-
|
|
2625
|
+
const chunk = result.value;
|
|
2626
|
+
pendingSseText += textDecoder.decode(chunk, { stream: true });
|
|
2627
|
+
applyCompleteSseEvents();
|
|
2628
|
+
controller.enqueue(chunk);
|
|
2629
|
+
} catch (error) {
|
|
2630
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2631
|
+
await finalizeStream({ code: "VENDOR_STREAM_ERROR", message }).catch(() => void 0);
|
|
2632
|
+
controller.error(error);
|
|
2444
2633
|
}
|
|
2445
2634
|
},
|
|
2446
|
-
async
|
|
2447
|
-
|
|
2635
|
+
async cancel(reason) {
|
|
2636
|
+
const message = reason instanceof Error ? reason.message : typeof reason === "string" && reason ? reason : "Provider stream consumption was cancelled before completion";
|
|
2637
|
+
try {
|
|
2638
|
+
await reader.cancel(reason);
|
|
2639
|
+
} finally {
|
|
2640
|
+
await finalizeStream({ code: "STREAM_ABORTED", message });
|
|
2641
|
+
}
|
|
2448
2642
|
}
|
|
2449
2643
|
});
|
|
2450
|
-
const wrappedBody = originalBody.pipeThrough(transformStream);
|
|
2451
2644
|
return new Response(wrappedBody, {
|
|
2452
2645
|
status: response.status,
|
|
2453
2646
|
statusText: response.statusText,
|
|
@@ -2468,23 +2661,46 @@ function executionMetadataFromOpenAIRequest(body, provider) {
|
|
|
2468
2661
|
);
|
|
2469
2662
|
return {
|
|
2470
2663
|
providerUsed: provider,
|
|
2664
|
+
...typeof body?.model === "string" ? { modelUsed: body.model } : {},
|
|
2471
2665
|
...effort ? { reasoningEffort: effort, reasoningEffortSource: "provider_request" } : {},
|
|
2472
2666
|
...mode ? { reasoningMode: mode } : {},
|
|
2473
2667
|
...budget !== void 0 ? { reasoningBudgetTokens: budget } : {}
|
|
2474
2668
|
};
|
|
2475
2669
|
}
|
|
2476
|
-
async function finalizeCall(callState, usageTap, error, usage) {
|
|
2477
|
-
if (callState.
|
|
2478
|
-
|
|
2670
|
+
async function finalizeCall(callState, usageTap, meteringFailureOptions, error, usage) {
|
|
2671
|
+
if (!callState.finalizationPromise) {
|
|
2672
|
+
callState.finalizationPromise = (async () => {
|
|
2673
|
+
try {
|
|
2674
|
+
await usageTap.endCall({
|
|
2675
|
+
callId: callState.callId,
|
|
2676
|
+
...callState.usage,
|
|
2677
|
+
...usage,
|
|
2678
|
+
error
|
|
2679
|
+
});
|
|
2680
|
+
} catch (endError) {
|
|
2681
|
+
await reportMeteringError(meteringFailureOptions, {
|
|
2682
|
+
stage: "end",
|
|
2683
|
+
callId: callState.callId,
|
|
2684
|
+
error: endError
|
|
2685
|
+
});
|
|
2686
|
+
if (meteringFailureOptions.strictMetering) {
|
|
2687
|
+
throw endError;
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
})();
|
|
2691
|
+
}
|
|
2692
|
+
await callState.finalizationPromise;
|
|
2693
|
+
}
|
|
2694
|
+
async function reportMeteringError(options, event) {
|
|
2695
|
+
console.error("[wrapFetch] Metering failure", {
|
|
2696
|
+
stage: event.stage,
|
|
2697
|
+
callId: event.callId,
|
|
2698
|
+
message: event.error instanceof Error ? event.error.message : String(event.error)
|
|
2699
|
+
});
|
|
2479
2700
|
try {
|
|
2480
|
-
await
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
...usage,
|
|
2484
|
-
error
|
|
2485
|
-
});
|
|
2486
|
-
} catch (err) {
|
|
2487
|
-
console.error("[wrapFetch] Failed to finalize call:", err);
|
|
2701
|
+
await options.onMeteringError?.(event);
|
|
2702
|
+
} catch (callbackError) {
|
|
2703
|
+
console.error("[wrapFetch] onMeteringError callback failed", callbackError);
|
|
2488
2704
|
}
|
|
2489
2705
|
}
|
|
2490
2706
|
|