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