@axlsdk/axl 0.20.0 → 0.21.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 +7 -0
- package/dist/index.cjs +1452 -312
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -66
- package/dist/index.d.ts +131 -66
- package/dist/index.js +1452 -312
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -705,7 +705,7 @@ async function fetchWithRetry(input, init, opts) {
|
|
|
705
705
|
for (let attempt = 0; ; attempt++) {
|
|
706
706
|
let res;
|
|
707
707
|
try {
|
|
708
|
-
res = await fetch(input, init);
|
|
708
|
+
res = await fetch(input, { ...init, redirect: "manual" });
|
|
709
709
|
} catch (err) {
|
|
710
710
|
if (isAbortError2(err, init?.signal ?? void 0)) throw err;
|
|
711
711
|
if (attempt >= maxRetries) {
|
|
@@ -875,6 +875,74 @@ var RateLimiter = class {
|
|
|
875
875
|
}
|
|
876
876
|
};
|
|
877
877
|
|
|
878
|
+
// src/providers/builtin-table-pricing.ts
|
|
879
|
+
var policies = /* @__PURE__ */ new WeakMap();
|
|
880
|
+
function attachBuiltinTablePricingPolicy(profile, policy) {
|
|
881
|
+
if (profile.pricing.kind !== "table") {
|
|
882
|
+
throw new Error("Built-in table pricing policy requires table pricing.");
|
|
883
|
+
}
|
|
884
|
+
policies.set(profile.pricing.table, policy);
|
|
885
|
+
}
|
|
886
|
+
function hasFunctionToolsOnly(value) {
|
|
887
|
+
return value === void 0 || Array.isArray(value) && value.every(
|
|
888
|
+
(tool2) => tool2 !== null && typeof tool2 === "object" && tool2.type === "function"
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
function hasValidRequiredCacheSplit(context) {
|
|
892
|
+
const usage = context.response?.usage;
|
|
893
|
+
const hit = usage?.prompt_cache_hit_tokens;
|
|
894
|
+
const miss = usage?.prompt_cache_miss_tokens;
|
|
895
|
+
return typeof hit === "number" && typeof miss === "number" && Number.isSafeInteger(hit) && Number.isSafeInteger(miss) && hit >= 0 && miss >= 0 && hit + miss === usage?.prompt_tokens;
|
|
896
|
+
}
|
|
897
|
+
function isBuiltinTablePricingEligible(profile, context) {
|
|
898
|
+
const policy = profile.pricing.kind === "table" ? policies.get(profile.pricing.table) : void 0;
|
|
899
|
+
if (!policy) return true;
|
|
900
|
+
if (policy.canonicalBaseUrl && context.baseUrl !== policy.canonicalBaseUrl) return false;
|
|
901
|
+
if (policy.requireCacheSplit && !hasValidRequiredCacheSplit(context)) return false;
|
|
902
|
+
if (policy.functionToolsOnly && !hasFunctionToolsOnly(context.request?.tools)) return false;
|
|
903
|
+
if (policy.chargedRequestModifiers?.some((modifier) => context.request?.[modifier] !== void 0)) {
|
|
904
|
+
return false;
|
|
905
|
+
}
|
|
906
|
+
if (!policy.supportedServiceTiers) return true;
|
|
907
|
+
const requestTier = context.request?.service_tier ?? context.request?.serviceTier;
|
|
908
|
+
const responseTier = context.response?.service_tier ?? context.response?.serviceTier;
|
|
909
|
+
const effectiveTier = responseTier ?? requestTier;
|
|
910
|
+
if (effectiveTier === void 0) return true;
|
|
911
|
+
return typeof effectiveTier === "string" && policy.supportedServiceTiers.includes(effectiveTier);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// src/http-transport.ts
|
|
915
|
+
function assertSafeProviderBaseUrl(baseUrl, surface, dangerouslyAllowInsecureHttp = false) {
|
|
916
|
+
assertSafeHttpEndpoint(baseUrl, surface, dangerouslyAllowInsecureHttp, "prompts and credentials");
|
|
917
|
+
}
|
|
918
|
+
function assertSafeHttpEndpoint(endpoint, surface, dangerouslyAllowInsecureHttp = false, exposedData = "sensitive data") {
|
|
919
|
+
let url;
|
|
920
|
+
try {
|
|
921
|
+
url = new URL(endpoint);
|
|
922
|
+
} catch {
|
|
923
|
+
throw unsafeTransportError(surface, "a valid absolute HTTP(S) URL", exposedData);
|
|
924
|
+
}
|
|
925
|
+
if (url.protocol === "https:") return;
|
|
926
|
+
if (url.protocol !== "http:") {
|
|
927
|
+
throw unsafeTransportError(surface, "an HTTPS URL", exposedData);
|
|
928
|
+
}
|
|
929
|
+
if (dangerouslyAllowInsecureHttp || isLiteralLoopbackHost(url.hostname)) return;
|
|
930
|
+
throw unsafeTransportError(
|
|
931
|
+
surface,
|
|
932
|
+
"an HTTPS URL, or set dangerouslyAllowInsecureHttp: true only for a deliberately insecure endpoint",
|
|
933
|
+
exposedData
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
function isLiteralLoopbackHost(hostname) {
|
|
937
|
+
return hostname === "localhost" || hostname === "localhost." || /^127(?:\.\d{1,3}){3}$/.test(hostname) || hostname === "[::1]";
|
|
938
|
+
}
|
|
939
|
+
function unsafeTransportError(surface, remediation, exposedData) {
|
|
940
|
+
return new AxlError(
|
|
941
|
+
"UNSAFE_TRANSPORT",
|
|
942
|
+
`${surface} requires ${remediation}. Remote HTTP can expose ${exposedData} in transit.`
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
|
|
878
946
|
// src/providers/openai-compatible.ts
|
|
879
947
|
function resolvePerModel(value, model, fallback) {
|
|
880
948
|
if (value === void 0) return fallback;
|
|
@@ -889,9 +957,13 @@ function sortedKeys(table) {
|
|
|
889
957
|
}
|
|
890
958
|
return keys;
|
|
891
959
|
}
|
|
892
|
-
function priceFromTable(table, model, promptTokens, completionTokens, cachedTokens) {
|
|
960
|
+
function priceFromTable(table, model, promptTokens, completionTokens, cachedTokens, match = "prefix") {
|
|
961
|
+
const cached = cachedTokens ?? 0;
|
|
962
|
+
if (!Number.isSafeInteger(promptTokens) || !Number.isSafeInteger(completionTokens) || !Number.isSafeInteger(cached) || promptTokens < 0 || completionTokens < 0 || cached < 0 || cached > promptTokens) {
|
|
963
|
+
return void 0;
|
|
964
|
+
}
|
|
893
965
|
let pricing = table[model];
|
|
894
|
-
if (!pricing) {
|
|
966
|
+
if (!pricing && match === "prefix") {
|
|
895
967
|
for (const key of sortedKeys(table)) {
|
|
896
968
|
if (model.startsWith(key)) {
|
|
897
969
|
pricing = table[key];
|
|
@@ -901,7 +973,6 @@ function priceFromTable(table, model, promptTokens, completionTokens, cachedToke
|
|
|
901
973
|
}
|
|
902
974
|
if (!pricing) return void 0;
|
|
903
975
|
const [inputRate, outputRate, cacheMultiplier] = pricing;
|
|
904
|
-
const cached = cachedTokens ?? 0;
|
|
905
976
|
const inputCost = (promptTokens - cached) * inputRate + cached * inputRate * cacheMultiplier;
|
|
906
977
|
return inputCost + completionTokens * outputRate;
|
|
907
978
|
}
|
|
@@ -1015,6 +1086,11 @@ var OpenAICompatibleProvider = class {
|
|
|
1015
1086
|
const envBase = p.envBaseUrl ? process.env[p.envBaseUrl] : void 0;
|
|
1016
1087
|
const explicitBase = options.baseUrl ?? envBase;
|
|
1017
1088
|
this.baseUrl = (explicitBase ?? p.defaultBaseUrl).replace(/\/$/, "");
|
|
1089
|
+
assertSafeProviderBaseUrl(
|
|
1090
|
+
this.baseUrl,
|
|
1091
|
+
`${p.label ?? p.name} provider`,
|
|
1092
|
+
options.dangerouslyAllowInsecureHttp
|
|
1093
|
+
);
|
|
1018
1094
|
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
1019
1095
|
const label = p.label ?? p.name;
|
|
1020
1096
|
if (p.requireExplicitBaseUrl && explicitBase === void 0) {
|
|
@@ -1062,7 +1138,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1062
1138
|
});
|
|
1063
1139
|
}
|
|
1064
1140
|
const json = await res.json();
|
|
1065
|
-
return this.parseResponse(json, options.model);
|
|
1141
|
+
return this.parseResponse(json, this.requestModel(body, options.model), body);
|
|
1066
1142
|
}
|
|
1067
1143
|
async *stream(messages, options) {
|
|
1068
1144
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
@@ -1090,7 +1166,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1090
1166
|
if (!res.body) {
|
|
1091
1167
|
throw new Error(`${this.profile.label ?? this.profile.name} stream response has no body`);
|
|
1092
1168
|
}
|
|
1093
|
-
yield* this.parseSSEStream(res.body, options.model);
|
|
1169
|
+
yield* this.parseSSEStream(res.body, this.requestModel(body, options.model), body);
|
|
1094
1170
|
}
|
|
1095
1171
|
// ---------------------------------------------------------------------------
|
|
1096
1172
|
// Request building
|
|
@@ -1111,12 +1187,13 @@ var OpenAICompatibleProvider = class {
|
|
|
1111
1187
|
buildRequestBody(messages, options, stream) {
|
|
1112
1188
|
const profile = this.profile;
|
|
1113
1189
|
const resolved = resolveThinkingOptions(options);
|
|
1190
|
+
const effectiveModel = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
|
|
1114
1191
|
const body = {
|
|
1115
|
-
model:
|
|
1116
|
-
messages: messages.map((m) => this.formatMessage(m,
|
|
1192
|
+
model: effectiveModel,
|
|
1193
|
+
messages: messages.map((m) => this.formatMessage(m, effectiveModel)),
|
|
1117
1194
|
stream
|
|
1118
1195
|
};
|
|
1119
|
-
const emitResult = profile.reasoning.emit(body, resolved,
|
|
1196
|
+
const emitResult = profile.reasoning.emit(body, resolved, effectiveModel) ?? {};
|
|
1120
1197
|
const stripTemperature = emitResult.stripTemperature ?? false;
|
|
1121
1198
|
if (options.temperature !== void 0 && !stripTemperature) {
|
|
1122
1199
|
body.temperature = options.temperature;
|
|
@@ -1127,7 +1204,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1127
1204
|
if (options.stop) body.stop = options.stop;
|
|
1128
1205
|
if (options.tools && options.tools.length > 0) {
|
|
1129
1206
|
body.tools = options.tools;
|
|
1130
|
-
if (resolvePerModel(profile.parallelToolCalls,
|
|
1207
|
+
if (resolvePerModel(profile.parallelToolCalls, effectiveModel, false)) {
|
|
1131
1208
|
body.parallel_tool_calls = true;
|
|
1132
1209
|
}
|
|
1133
1210
|
}
|
|
@@ -1135,7 +1212,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1135
1212
|
if (options.responseFormat) {
|
|
1136
1213
|
const supportsSchema = resolvePerModel(
|
|
1137
1214
|
profile.capabilities?.supportsJsonSchema,
|
|
1138
|
-
|
|
1215
|
+
effectiveModel,
|
|
1139
1216
|
true
|
|
1140
1217
|
);
|
|
1141
1218
|
body.response_format = options.responseFormat.type === "json_schema" && !supportsSchema ? { type: "json_object" } : options.responseFormat;
|
|
@@ -1146,7 +1223,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1146
1223
|
if (profile.requestDefaults) Object.assign(body, profile.requestDefaults);
|
|
1147
1224
|
const userKeys = options.providerOptions ? Object.keys(options.providerOptions) : [];
|
|
1148
1225
|
if (options.providerOptions) Object.assign(body, options.providerOptions);
|
|
1149
|
-
const forbidden = resolvePerModel(profile.capabilities?.forbiddenParams,
|
|
1226
|
+
const forbidden = resolvePerModel(profile.capabilities?.forbiddenParams, effectiveModel, []);
|
|
1150
1227
|
for (const key of forbidden) {
|
|
1151
1228
|
if (!userKeys.includes(key)) delete body[key];
|
|
1152
1229
|
}
|
|
@@ -1181,6 +1258,9 @@ var OpenAICompatibleProvider = class {
|
|
|
1181
1258
|
}
|
|
1182
1259
|
return `${label} API error (${status}): ${body}`;
|
|
1183
1260
|
}
|
|
1261
|
+
requestModel(body, fallback) {
|
|
1262
|
+
return typeof body.model === "string" ? body.model : fallback;
|
|
1263
|
+
}
|
|
1184
1264
|
toUsage(raw) {
|
|
1185
1265
|
if (!raw) return void 0;
|
|
1186
1266
|
return {
|
|
@@ -1188,10 +1268,27 @@ var OpenAICompatibleProvider = class {
|
|
|
1188
1268
|
completion_tokens: raw.completion_tokens,
|
|
1189
1269
|
total_tokens: raw.total_tokens,
|
|
1190
1270
|
reasoning_tokens: raw.completion_tokens_details?.reasoning_tokens,
|
|
1191
|
-
|
|
1271
|
+
// DeepSeek reports the cache split at the top level rather than under
|
|
1272
|
+
// prompt_tokens_details. Preserve the normalized hit count for the
|
|
1273
|
+
// generic table estimator; its split is validated before pricing.
|
|
1274
|
+
cached_tokens: raw.prompt_cache_hit_tokens ?? raw.prompt_tokens_details?.cached_tokens,
|
|
1275
|
+
cache_write_tokens: raw.prompt_tokens_details?.cache_write_tokens
|
|
1192
1276
|
};
|
|
1193
1277
|
}
|
|
1194
|
-
|
|
1278
|
+
reportedCost(raw) {
|
|
1279
|
+
if (!raw) return void 0;
|
|
1280
|
+
if (this.profile.name === "xai") {
|
|
1281
|
+
const ticks = raw.cost_in_usd_ticks;
|
|
1282
|
+
return typeof ticks === "number" && Number.isSafeInteger(ticks) && ticks >= 0 ? ticks / 1e10 : void 0;
|
|
1283
|
+
}
|
|
1284
|
+
return typeof raw.cost === "number" && Number.isFinite(raw.cost) && raw.cost >= 0 ? raw.cost : void 0;
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* The generic profile path stays flat and public. Native subclasses may
|
|
1288
|
+
* override this protected seam to use a provider-internal estimator without
|
|
1289
|
+
* widening `PricingSource` or `ProviderProfile`.
|
|
1290
|
+
*/
|
|
1291
|
+
computeCost(model, usage, reportedCost, context) {
|
|
1195
1292
|
const p = this.profile.pricing;
|
|
1196
1293
|
if (p.kind === "zero") return 0;
|
|
1197
1294
|
if (!usage) return void 0;
|
|
@@ -1199,14 +1296,22 @@ var OpenAICompatibleProvider = class {
|
|
|
1199
1296
|
case "unknown":
|
|
1200
1297
|
return void 0;
|
|
1201
1298
|
case "from-response":
|
|
1202
|
-
return typeof reportedCost === "number" && Number.isFinite(reportedCost) ? reportedCost : void 0;
|
|
1299
|
+
return typeof reportedCost === "number" && Number.isFinite(reportedCost) && reportedCost >= 0 ? reportedCost : void 0;
|
|
1203
1300
|
case "table": {
|
|
1301
|
+
if (!isBuiltinTablePricingEligible(this.profile, {
|
|
1302
|
+
baseUrl: this.baseUrl,
|
|
1303
|
+
request: context?.request,
|
|
1304
|
+
response: context?.response
|
|
1305
|
+
})) {
|
|
1306
|
+
return void 0;
|
|
1307
|
+
}
|
|
1204
1308
|
const c = priceFromTable(
|
|
1205
1309
|
p.table,
|
|
1206
1310
|
model,
|
|
1207
1311
|
usage.prompt_tokens,
|
|
1208
1312
|
usage.completion_tokens,
|
|
1209
|
-
usage.cached_tokens
|
|
1313
|
+
usage.cached_tokens,
|
|
1314
|
+
p.match
|
|
1210
1315
|
);
|
|
1211
1316
|
return c !== void 0 && Number.isFinite(c) ? c : void 0;
|
|
1212
1317
|
}
|
|
@@ -1216,7 +1321,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1216
1321
|
}
|
|
1217
1322
|
}
|
|
1218
1323
|
}
|
|
1219
|
-
parseResponse(json, model) {
|
|
1324
|
+
parseResponse(json, model, request) {
|
|
1220
1325
|
const message = json.choices?.[0]?.message ?? {};
|
|
1221
1326
|
let content = normalizeMessageContent(message.content);
|
|
1222
1327
|
let thinking;
|
|
@@ -1262,7 +1367,10 @@ var OpenAICompatibleProvider = class {
|
|
|
1262
1367
|
}
|
|
1263
1368
|
}
|
|
1264
1369
|
const usage = this.toUsage(json.usage);
|
|
1265
|
-
const cost = this.computeCost(model, usage, json.usage
|
|
1370
|
+
const cost = this.computeCost(json.model ?? model, usage, this.reportedCost(json.usage), {
|
|
1371
|
+
request,
|
|
1372
|
+
response: json
|
|
1373
|
+
});
|
|
1266
1374
|
return {
|
|
1267
1375
|
content,
|
|
1268
1376
|
thinking_content: thinking || void 0,
|
|
@@ -1285,11 +1393,12 @@ var OpenAICompatibleProvider = class {
|
|
|
1285
1393
|
// ---------------------------------------------------------------------------
|
|
1286
1394
|
// SSE streaming
|
|
1287
1395
|
// ---------------------------------------------------------------------------
|
|
1288
|
-
async *parseSSEStream(body, model) {
|
|
1396
|
+
async *parseSSEStream(body, model, request) {
|
|
1289
1397
|
const reader = body.getReader();
|
|
1290
1398
|
const decoder = new TextDecoder();
|
|
1291
1399
|
let buffer = "";
|
|
1292
1400
|
let usageData;
|
|
1401
|
+
let pricingResponse;
|
|
1293
1402
|
const capture = this.profile.reasoning.capture;
|
|
1294
1403
|
const thinkScanner = capture === "think_tags" ? new ThinkTagScanner() : void 0;
|
|
1295
1404
|
let reasoningText = "";
|
|
@@ -1330,7 +1439,15 @@ var OpenAICompatibleProvider = class {
|
|
|
1330
1439
|
return {
|
|
1331
1440
|
type: "done",
|
|
1332
1441
|
usage,
|
|
1333
|
-
cost: this.computeCost(
|
|
1442
|
+
cost: this.computeCost(
|
|
1443
|
+
pricingResponse?.model ?? model,
|
|
1444
|
+
usage,
|
|
1445
|
+
this.reportedCost(usageData),
|
|
1446
|
+
{
|
|
1447
|
+
request,
|
|
1448
|
+
response: pricingResponse
|
|
1449
|
+
}
|
|
1450
|
+
),
|
|
1334
1451
|
providerMetadata
|
|
1335
1452
|
};
|
|
1336
1453
|
};
|
|
@@ -1371,8 +1488,12 @@ var OpenAICompatibleProvider = class {
|
|
|
1371
1488
|
body: JSON.stringify(parsed.error)
|
|
1372
1489
|
});
|
|
1373
1490
|
}
|
|
1491
|
+
if (parsed.model !== void 0 || parsed.service_tier !== void 0 || parsed.serviceTier !== void 0) {
|
|
1492
|
+
pricingResponse = { ...pricingResponse, ...parsed };
|
|
1493
|
+
}
|
|
1374
1494
|
if (parsed.usage) {
|
|
1375
1495
|
usageData = parsed.usage;
|
|
1496
|
+
pricingResponse = { ...pricingResponse, ...parsed };
|
|
1376
1497
|
if (!parsed.choices || parsed.choices.length === 0) continue;
|
|
1377
1498
|
}
|
|
1378
1499
|
const delta = parsed.choices?.[0]?.delta;
|
|
@@ -1446,38 +1567,328 @@ var OpenAICompatibleProvider = class {
|
|
|
1446
1567
|
|
|
1447
1568
|
// src/providers/openai.ts
|
|
1448
1569
|
var OPENAI_PRICING = {
|
|
1449
|
-
//
|
|
1570
|
+
// Flat, exact Standard rows only. Context-tiered and cache-write-priced
|
|
1571
|
+
// models intentionally stay out of this compatibility view.
|
|
1450
1572
|
"gpt-4o": [25e-7, 1e-5, 0.5],
|
|
1451
1573
|
"gpt-4o-mini": [15e-8, 6e-7, 0.5],
|
|
1452
|
-
"gpt-4-turbo": [1e-5, 3e-5, 0.5],
|
|
1453
|
-
"gpt-4": [3e-5, 6e-5, 0.5],
|
|
1454
|
-
"gpt-3.5-turbo": [5e-7, 15e-7, 0.5],
|
|
1455
1574
|
o1: [15e-6, 6e-5, 0.5],
|
|
1456
|
-
"o1-mini": [3e-6, 12e-6, 0.5],
|
|
1457
|
-
"o1-pro": [15e-5, 6e-4, 0.5],
|
|
1458
|
-
// gpt-4.1 / o3 / o4 era — cache reads at 25% of input rate
|
|
1459
1575
|
"gpt-4.1": [2e-6, 8e-6, 0.25],
|
|
1460
1576
|
"gpt-4.1-mini": [4e-7, 16e-7, 0.25],
|
|
1461
1577
|
"gpt-4.1-nano": [1e-7, 4e-7, 0.25],
|
|
1462
|
-
o3: [
|
|
1578
|
+
o3: [2e-6, 8e-6, 0.25],
|
|
1463
1579
|
"o3-mini": [11e-7, 44e-7, 0.25],
|
|
1464
|
-
"o3-pro": [2e-5, 8e-5, 0.25],
|
|
1465
1580
|
"o4-mini": [11e-7, 44e-7, 0.25],
|
|
1466
|
-
// gpt-5 era — cache reads at 10% of input rate
|
|
1467
1581
|
"gpt-5": [125e-8, 1e-5, 0.1],
|
|
1468
1582
|
"gpt-5-mini": [25e-8, 2e-6, 0.1],
|
|
1469
1583
|
"gpt-5-nano": [5e-8, 4e-7, 0.1],
|
|
1470
1584
|
"gpt-5.1": [125e-8, 1e-5, 0.1],
|
|
1471
|
-
"gpt-5.2": [175e-8, 14e-6, 0.1]
|
|
1472
|
-
"gpt-5.3": [175e-8, 14e-6, 0.1],
|
|
1473
|
-
"gpt-5.4": [25e-7, 15e-6, 0.1],
|
|
1474
|
-
"gpt-5.4-pro": [3e-5, 18e-5, 0.1],
|
|
1475
|
-
// gpt-5.5 era (flagship released 2026-04) — cache reads at 10% of input rate
|
|
1476
|
-
"gpt-5.5": [5e-6, 3e-5, 0.1],
|
|
1477
|
-
"gpt-5.5-pro": [3e-5, 18e-5, 0.1]
|
|
1585
|
+
"gpt-5.2": [175e-8, 14e-6, 0.1]
|
|
1478
1586
|
};
|
|
1587
|
+
var M = 1e6;
|
|
1588
|
+
var LONG_CONTEXT_BOUNDARY = 272e3;
|
|
1589
|
+
var withLongContext = (short) => ({
|
|
1590
|
+
input: short.input * 2,
|
|
1591
|
+
cachedInput: short.cachedInput === void 0 ? void 0 : short.cachedInput * 2,
|
|
1592
|
+
cacheWrite: short.cacheWrite === void 0 ? void 0 : short.cacheWrite * 2,
|
|
1593
|
+
output: short.output * 1.5
|
|
1594
|
+
});
|
|
1595
|
+
var DIRECT_OPENAI_CATALOG = [
|
|
1596
|
+
{
|
|
1597
|
+
aliases: ["gpt-5.6", "gpt-5.6-sol"],
|
|
1598
|
+
snapshotBase: "gpt-5.6-sol",
|
|
1599
|
+
short: { input: 5 / M, cachedInput: 0.5 / M, cacheWrite: 6.25 / M, output: 30 / M },
|
|
1600
|
+
long: withLongContext({
|
|
1601
|
+
input: 5 / M,
|
|
1602
|
+
cachedInput: 0.5 / M,
|
|
1603
|
+
cacheWrite: 6.25 / M,
|
|
1604
|
+
output: 30 / M
|
|
1605
|
+
}),
|
|
1606
|
+
contextBoundary: LONG_CONTEXT_BOUNDARY
|
|
1607
|
+
},
|
|
1608
|
+
{
|
|
1609
|
+
aliases: ["gpt-5.6-terra"],
|
|
1610
|
+
snapshotBase: "gpt-5.6-terra",
|
|
1611
|
+
short: { input: 2 / M, cachedInput: 0.2 / M, cacheWrite: 2.5 / M, output: 12 / M },
|
|
1612
|
+
long: withLongContext({
|
|
1613
|
+
input: 2 / M,
|
|
1614
|
+
cachedInput: 0.2 / M,
|
|
1615
|
+
cacheWrite: 2.5 / M,
|
|
1616
|
+
output: 12 / M
|
|
1617
|
+
}),
|
|
1618
|
+
contextBoundary: LONG_CONTEXT_BOUNDARY
|
|
1619
|
+
},
|
|
1620
|
+
{
|
|
1621
|
+
aliases: ["gpt-5.6-luna"],
|
|
1622
|
+
snapshotBase: "gpt-5.6-luna",
|
|
1623
|
+
short: { input: 0.2 / M, cachedInput: 0.02 / M, cacheWrite: 0.25 / M, output: 1.2 / M },
|
|
1624
|
+
long: withLongContext({
|
|
1625
|
+
input: 0.2 / M,
|
|
1626
|
+
cachedInput: 0.02 / M,
|
|
1627
|
+
cacheWrite: 0.25 / M,
|
|
1628
|
+
output: 1.2 / M
|
|
1629
|
+
}),
|
|
1630
|
+
contextBoundary: LONG_CONTEXT_BOUNDARY
|
|
1631
|
+
},
|
|
1632
|
+
// Existing direct models retain literal current Standard rows. These flat
|
|
1633
|
+
// rows keep native pricing aligned with the compatibility wrapper while the
|
|
1634
|
+
// private estimator still validates categories and billing mode strictly.
|
|
1635
|
+
{
|
|
1636
|
+
aliases: ["gpt-4o", "gpt-4o-2024-08-06", "gpt-4o-2024-11-20"],
|
|
1637
|
+
snapshotBase: "gpt-4o",
|
|
1638
|
+
short: { input: 2.5 / M, cachedInput: 1.25 / M, output: 10 / M }
|
|
1639
|
+
},
|
|
1640
|
+
{
|
|
1641
|
+
aliases: ["gpt-4o-mini", "gpt-4o-mini-2024-07-18"],
|
|
1642
|
+
snapshotBase: "gpt-4o-mini",
|
|
1643
|
+
short: { input: 0.15 / M, cachedInput: 0.075 / M, output: 0.6 / M }
|
|
1644
|
+
},
|
|
1645
|
+
{
|
|
1646
|
+
aliases: ["gpt-3.5-turbo"],
|
|
1647
|
+
snapshotBase: "gpt-3.5-turbo",
|
|
1648
|
+
short: { input: 0.5 / M, output: 1.5 / M }
|
|
1649
|
+
},
|
|
1650
|
+
{
|
|
1651
|
+
aliases: ["o1", "o1-2024-12-17"],
|
|
1652
|
+
snapshotBase: "o1",
|
|
1653
|
+
short: { input: 15 / M, cachedInput: 7.5 / M, output: 60 / M }
|
|
1654
|
+
},
|
|
1655
|
+
{
|
|
1656
|
+
aliases: ["o1-pro", "o1-pro-2025-03-19"],
|
|
1657
|
+
snapshotBase: "o1-pro",
|
|
1658
|
+
short: { input: 150 / M, output: 600 / M }
|
|
1659
|
+
},
|
|
1660
|
+
{
|
|
1661
|
+
aliases: ["gpt-4.1", "gpt-4.1-2025-04-14"],
|
|
1662
|
+
snapshotBase: "gpt-4.1",
|
|
1663
|
+
short: { input: 2 / M, cachedInput: 0.5 / M, output: 8 / M }
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
aliases: ["gpt-4.1-mini", "gpt-4.1-mini-2025-04-14"],
|
|
1667
|
+
snapshotBase: "gpt-4.1-mini",
|
|
1668
|
+
short: { input: 0.4 / M, cachedInput: 0.1 / M, output: 1.6 / M }
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
aliases: ["gpt-4.1-nano", "gpt-4.1-nano-2025-04-14"],
|
|
1672
|
+
snapshotBase: "gpt-4.1-nano",
|
|
1673
|
+
short: { input: 0.1 / M, cachedInput: 0.025 / M, output: 0.4 / M }
|
|
1674
|
+
},
|
|
1675
|
+
{
|
|
1676
|
+
aliases: ["o3", "o3-2025-04-16"],
|
|
1677
|
+
snapshotBase: "o3",
|
|
1678
|
+
short: { input: 2 / M, cachedInput: 0.5 / M, output: 8 / M }
|
|
1679
|
+
},
|
|
1680
|
+
{
|
|
1681
|
+
aliases: ["o3-mini", "o3-mini-2025-01-31"],
|
|
1682
|
+
snapshotBase: "o3-mini",
|
|
1683
|
+
short: { input: 1.1 / M, cachedInput: 0.55 / M, output: 4.4 / M }
|
|
1684
|
+
},
|
|
1685
|
+
{
|
|
1686
|
+
aliases: ["o3-pro", "o3-pro-2025-06-10"],
|
|
1687
|
+
snapshotBase: "o3-pro",
|
|
1688
|
+
short: { input: 20 / M, output: 80 / M }
|
|
1689
|
+
},
|
|
1690
|
+
{
|
|
1691
|
+
aliases: ["o4-mini", "o4-mini-2025-04-16"],
|
|
1692
|
+
snapshotBase: "o4-mini",
|
|
1693
|
+
short: { input: 1.1 / M, cachedInput: 0.275 / M, output: 4.4 / M }
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
aliases: ["gpt-5", "gpt-5-2025-08-07"],
|
|
1697
|
+
snapshotBase: "gpt-5",
|
|
1698
|
+
short: { input: 1.25 / M, cachedInput: 0.125 / M, output: 10 / M }
|
|
1699
|
+
},
|
|
1700
|
+
{
|
|
1701
|
+
aliases: ["gpt-5-mini", "gpt-5-mini-2025-08-07"],
|
|
1702
|
+
snapshotBase: "gpt-5-mini",
|
|
1703
|
+
short: { input: 0.25 / M, cachedInput: 0.025 / M, output: 2 / M }
|
|
1704
|
+
},
|
|
1705
|
+
{
|
|
1706
|
+
aliases: ["gpt-5-nano", "gpt-5-nano-2025-08-07"],
|
|
1707
|
+
snapshotBase: "gpt-5-nano",
|
|
1708
|
+
short: { input: 0.05 / M, cachedInput: 5e-3 / M, output: 0.4 / M }
|
|
1709
|
+
},
|
|
1710
|
+
{
|
|
1711
|
+
aliases: ["gpt-5.1", "gpt-5.1-2025-11-13"],
|
|
1712
|
+
snapshotBase: "gpt-5.1",
|
|
1713
|
+
short: { input: 1.25 / M, cachedInput: 0.125 / M, output: 10 / M }
|
|
1714
|
+
},
|
|
1715
|
+
{
|
|
1716
|
+
aliases: ["gpt-5.2", "gpt-5.2-2025-12-11"],
|
|
1717
|
+
snapshotBase: "gpt-5.2",
|
|
1718
|
+
short: { input: 1.75 / M, cachedInput: 0.175 / M, output: 14 / M }
|
|
1719
|
+
},
|
|
1720
|
+
{
|
|
1721
|
+
aliases: ["gpt-5.4", "gpt-5.4-2026-03-05"],
|
|
1722
|
+
snapshotBase: "gpt-5.4",
|
|
1723
|
+
short: { input: 2.5 / M, cachedInput: 0.25 / M, output: 15 / M },
|
|
1724
|
+
long: withLongContext({ input: 2.5 / M, cachedInput: 0.25 / M, output: 15 / M }),
|
|
1725
|
+
contextBoundary: LONG_CONTEXT_BOUNDARY
|
|
1726
|
+
},
|
|
1727
|
+
{
|
|
1728
|
+
aliases: ["gpt-5.4-pro", "gpt-5.4-pro-2026-03-05"],
|
|
1729
|
+
snapshotBase: "gpt-5.4-pro",
|
|
1730
|
+
short: { input: 30 / M, output: 180 / M },
|
|
1731
|
+
long: withLongContext({ input: 30 / M, output: 180 / M }),
|
|
1732
|
+
contextBoundary: LONG_CONTEXT_BOUNDARY
|
|
1733
|
+
},
|
|
1734
|
+
{
|
|
1735
|
+
aliases: ["gpt-5.5", "gpt-5.5-2026-04-23"],
|
|
1736
|
+
snapshotBase: "gpt-5.5",
|
|
1737
|
+
short: { input: 5 / M, cachedInput: 0.5 / M, output: 30 / M },
|
|
1738
|
+
long: withLongContext({ input: 5 / M, cachedInput: 0.5 / M, output: 30 / M }),
|
|
1739
|
+
contextBoundary: LONG_CONTEXT_BOUNDARY
|
|
1740
|
+
},
|
|
1741
|
+
{
|
|
1742
|
+
aliases: ["gpt-5.5-pro", "gpt-5.5-pro-2026-04-23"],
|
|
1743
|
+
snapshotBase: "gpt-5.5-pro",
|
|
1744
|
+
short: { input: 30 / M, output: 180 / M },
|
|
1745
|
+
long: withLongContext({ input: 30 / M, output: 180 / M }),
|
|
1746
|
+
contextBoundary: LONG_CONTEXT_BOUNDARY
|
|
1747
|
+
},
|
|
1748
|
+
{
|
|
1749
|
+
aliases: ["gpt-5.4-mini", "gpt-5.4-mini-2026-03-17"],
|
|
1750
|
+
short: { input: 0.75 / M, cachedInput: 0.075 / M, output: 4.5 / M }
|
|
1751
|
+
},
|
|
1752
|
+
{
|
|
1753
|
+
aliases: ["gpt-5.4-nano", "gpt-5.4-nano-2026-03-17"],
|
|
1754
|
+
short: { input: 0.2 / M, cachedInput: 0.02 / M, output: 1.25 / M }
|
|
1755
|
+
},
|
|
1756
|
+
{
|
|
1757
|
+
aliases: ["gpt-5.2-pro", "gpt-5.2-pro-2025-12-11"],
|
|
1758
|
+
short: { input: 21 / M, output: 168 / M }
|
|
1759
|
+
},
|
|
1760
|
+
{ aliases: ["gpt-5-pro", "gpt-5-pro-2025-10-06"], short: { input: 15 / M, output: 120 / M } },
|
|
1761
|
+
{ aliases: ["gpt-3.5-turbo-instruct"], short: { input: 1.5 / M, output: 2 / M } },
|
|
1762
|
+
{ aliases: ["davinci-002"], short: { input: 2 / M, output: 2 / M } },
|
|
1763
|
+
{ aliases: ["babbage-002"], short: { input: 0.4 / M, output: 0.4 / M } },
|
|
1764
|
+
// Explicit snapshots with their own current dedicated-table rows. Do not
|
|
1765
|
+
// generalize these dates: a plausible-looking unlisted snapshot is unpriced.
|
|
1766
|
+
{ aliases: ["gpt-4o-2024-05-13"], short: { input: 5 / M, output: 15 / M } },
|
|
1767
|
+
{ aliases: ["gpt-4-turbo-2024-04-09"], short: { input: 10 / M, output: 30 / M } },
|
|
1768
|
+
{ aliases: ["gpt-4-0613"], short: { input: 30 / M, output: 60 / M } },
|
|
1769
|
+
{
|
|
1770
|
+
aliases: ["gpt-3.5-turbo-0125"],
|
|
1771
|
+
short: { input: 0.5 / M, output: 1.5 / M }
|
|
1772
|
+
},
|
|
1773
|
+
{ aliases: ["gpt-3.5-turbo-1106"], short: { input: 1 / M, output: 2 / M } }
|
|
1774
|
+
];
|
|
1775
|
+
function directOpenAIModel(model) {
|
|
1776
|
+
return DIRECT_OPENAI_CATALOG.find((entry) => entry.aliases.includes(model));
|
|
1777
|
+
}
|
|
1778
|
+
var CANONICAL_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
1779
|
+
function isTextContentPart(value, type) {
|
|
1780
|
+
return value !== null && typeof value === "object" && value.type === type && typeof value.text === "string";
|
|
1781
|
+
}
|
|
1782
|
+
function hasUnmodeledDirectOpenAIContent(request) {
|
|
1783
|
+
if ("messages" in request) {
|
|
1784
|
+
if (!Array.isArray(request.messages)) return true;
|
|
1785
|
+
for (const message of request.messages) {
|
|
1786
|
+
if (message === null || typeof message !== "object") return true;
|
|
1787
|
+
const content = message.content;
|
|
1788
|
+
if (content === void 0 || content === null || typeof content === "string") continue;
|
|
1789
|
+
if (!Array.isArray(content) || content.some((part) => !isTextContentPart(part, "text"))) {
|
|
1790
|
+
return true;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
return false;
|
|
1794
|
+
}
|
|
1795
|
+
if ("input" in request) {
|
|
1796
|
+
const input = request.input;
|
|
1797
|
+
if (typeof input === "string") return false;
|
|
1798
|
+
if (!Array.isArray(input)) return true;
|
|
1799
|
+
for (const item of input) {
|
|
1800
|
+
if (item === null || typeof item !== "object") return true;
|
|
1801
|
+
const typed = item;
|
|
1802
|
+
switch (typed.type) {
|
|
1803
|
+
case "message": {
|
|
1804
|
+
const content = typed.content;
|
|
1805
|
+
if (typeof content === "string") break;
|
|
1806
|
+
if (!Array.isArray(content) || content.some((part) => !isTextContentPart(part, "input_text"))) {
|
|
1807
|
+
return true;
|
|
1808
|
+
}
|
|
1809
|
+
break;
|
|
1810
|
+
}
|
|
1811
|
+
case "function_call":
|
|
1812
|
+
break;
|
|
1813
|
+
case "function_call_output": {
|
|
1814
|
+
const output = typed.output;
|
|
1815
|
+
if (typeof output === "string") break;
|
|
1816
|
+
if (!Array.isArray(output) || output.some((part) => !isTextContentPart(part, "input_text"))) {
|
|
1817
|
+
return true;
|
|
1818
|
+
}
|
|
1819
|
+
break;
|
|
1820
|
+
}
|
|
1821
|
+
case "custom_tool_call":
|
|
1822
|
+
if (typeof typed.input !== "string") return true;
|
|
1823
|
+
break;
|
|
1824
|
+
case "custom_tool_call_output":
|
|
1825
|
+
if (typeof typed.output !== "string") return true;
|
|
1826
|
+
break;
|
|
1827
|
+
case "reasoning":
|
|
1828
|
+
break;
|
|
1829
|
+
default:
|
|
1830
|
+
return true;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
return false;
|
|
1834
|
+
}
|
|
1835
|
+
return false;
|
|
1836
|
+
}
|
|
1837
|
+
function isEligibleDirectOpenAIContext(context) {
|
|
1838
|
+
const request = context?.request;
|
|
1839
|
+
const response = context?.response;
|
|
1840
|
+
if (context?.baseUrl !== void 0 && context.baseUrl !== CANONICAL_OPENAI_BASE_URL) return false;
|
|
1841
|
+
if (request && hasUnmodeledDirectOpenAIContent(request)) return false;
|
|
1842
|
+
const tier = response?.service_tier ?? response?.serviceTier ?? request?.service_tier ?? request?.serviceTier;
|
|
1843
|
+
if (tier !== void 0 && (typeof tier !== "string" || !["standard", "default"].includes(tier.toLowerCase()))) {
|
|
1844
|
+
return false;
|
|
1845
|
+
}
|
|
1846
|
+
if (["region", "inference_geo", "data_residency"].some((key) => request?.[key] !== void 0)) {
|
|
1847
|
+
return false;
|
|
1848
|
+
}
|
|
1849
|
+
if (["previous_response_id", "conversation", "prompt"].some((key) => request?.[key] !== void 0)) {
|
|
1850
|
+
return false;
|
|
1851
|
+
}
|
|
1852
|
+
const reasoning = request?.reasoning;
|
|
1853
|
+
if (reasoning !== null && typeof reasoning === "object" && "mode" in reasoning && reasoning.mode !== void 0) {
|
|
1854
|
+
return false;
|
|
1855
|
+
}
|
|
1856
|
+
if (request?.modalities !== void 0 && (!Array.isArray(request.modalities) || request.modalities.length === 0 || request.modalities.some((modality) => modality !== "text")) || ["audio", "image_generation", "web_search_options"].some((key) => request?.[key] !== void 0)) {
|
|
1857
|
+
return false;
|
|
1858
|
+
}
|
|
1859
|
+
if (Array.isArray(request?.tools) && request.tools.some(
|
|
1860
|
+
(tool2) => tool2 === null || typeof tool2 !== "object" || !["function", "custom"].includes(tool2.type)
|
|
1861
|
+
)) {
|
|
1862
|
+
return false;
|
|
1863
|
+
}
|
|
1864
|
+
return true;
|
|
1865
|
+
}
|
|
1866
|
+
function estimateDirectOpenAICost(model, usage, context) {
|
|
1867
|
+
if (!isEligibleDirectOpenAIContext(context)) return void 0;
|
|
1868
|
+
const entry = directOpenAIModel(model);
|
|
1869
|
+
if (!entry) return void 0;
|
|
1870
|
+
const { prompt_tokens, completion_tokens, cached_tokens, cache_write_tokens } = usage;
|
|
1871
|
+
const cached = cached_tokens ?? 0;
|
|
1872
|
+
const cacheWrite = cache_write_tokens ?? 0;
|
|
1873
|
+
if (![prompt_tokens, completion_tokens, cached, cacheWrite].every(
|
|
1874
|
+
(count) => Number.isSafeInteger(count) && count >= 0
|
|
1875
|
+
) || cached + cacheWrite > prompt_tokens) {
|
|
1876
|
+
return void 0;
|
|
1877
|
+
}
|
|
1878
|
+
const rates = entry.long !== void 0 && entry.contextBoundary !== void 0 && prompt_tokens > entry.contextBoundary ? entry.long : entry.short;
|
|
1879
|
+
if (cached > 0 && rates.cachedInput === void 0 || cacheWrite > 0 && rates.cacheWrite === void 0) {
|
|
1880
|
+
return void 0;
|
|
1881
|
+
}
|
|
1882
|
+
const ordinary = prompt_tokens - cached - cacheWrite;
|
|
1883
|
+
return ordinary * rates.input + cached * (rates.cachedInput ?? 0) + cacheWrite * (rates.cacheWrite ?? 0) + completion_tokens * rates.output;
|
|
1884
|
+
}
|
|
1479
1885
|
function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens) {
|
|
1480
|
-
return
|
|
1886
|
+
return estimateDirectOpenAICost(model, {
|
|
1887
|
+
prompt_tokens: promptTokens,
|
|
1888
|
+
completion_tokens: completionTokens,
|
|
1889
|
+
total_tokens: promptTokens + completionTokens,
|
|
1890
|
+
cached_tokens: cachedTokens
|
|
1891
|
+
});
|
|
1481
1892
|
}
|
|
1482
1893
|
function isOSeriesModel(model) {
|
|
1483
1894
|
return /^(o1|o3|o4-mini)/.test(model);
|
|
@@ -1485,6 +1896,15 @@ function isOSeriesModel(model) {
|
|
|
1485
1896
|
function supportsReasoningEffort(model) {
|
|
1486
1897
|
return isOSeriesModel(model) || /^gpt-5/.test(model);
|
|
1487
1898
|
}
|
|
1899
|
+
var GPT_56_REASONING_MODELS = /* @__PURE__ */ new Set([
|
|
1900
|
+
"gpt-5.6",
|
|
1901
|
+
"gpt-5.6-sol",
|
|
1902
|
+
"gpt-5.6-terra",
|
|
1903
|
+
"gpt-5.6-luna"
|
|
1904
|
+
]);
|
|
1905
|
+
function supportsMaxReasoningEffort(model) {
|
|
1906
|
+
return GPT_56_REASONING_MODELS.has(model);
|
|
1907
|
+
}
|
|
1488
1908
|
function supportsReasoningNone(model) {
|
|
1489
1909
|
return /^gpt-5\.[1-9]/.test(model);
|
|
1490
1910
|
}
|
|
@@ -1494,31 +1914,46 @@ function supportsXhigh(model) {
|
|
|
1494
1914
|
function clampReasoningEffort(model, effort) {
|
|
1495
1915
|
if (model.startsWith("gpt-5-pro")) return "high";
|
|
1496
1916
|
if (effort === "none" && !supportsReasoningNone(model)) return "minimal";
|
|
1917
|
+
if (effort === "max" && !supportsMaxReasoningEffort(model)) {
|
|
1918
|
+
return clampReasoningEffort(model, "xhigh");
|
|
1919
|
+
}
|
|
1497
1920
|
if (effort === "xhigh" && !supportsXhigh(model)) return "high";
|
|
1498
1921
|
return effort;
|
|
1499
1922
|
}
|
|
1500
|
-
function effortToReasoningEffort(effort) {
|
|
1501
|
-
return effort === "max" ? "xhigh" : effort;
|
|
1923
|
+
function effortToReasoningEffort(effort, model) {
|
|
1924
|
+
return effort === "max" && model !== void 0 && supportsMaxReasoningEffort(model) ? "max" : effort === "max" ? "xhigh" : effort;
|
|
1502
1925
|
}
|
|
1503
1926
|
function budgetToReasoningEffort(budget) {
|
|
1504
1927
|
if (budget <= 1024) return "low";
|
|
1505
1928
|
if (budget <= 8192) return "medium";
|
|
1506
1929
|
return "high";
|
|
1507
1930
|
}
|
|
1931
|
+
function resolveOpenAIReasoningEffort(model, resolved) {
|
|
1932
|
+
if (!supportsReasoningEffort(model)) return void 0;
|
|
1933
|
+
if (resolved.hasBudgetOverride) {
|
|
1934
|
+
return clampReasoningEffort(model, budgetToReasoningEffort(resolved.thinkingBudget));
|
|
1935
|
+
}
|
|
1936
|
+
if (!resolved.thinkingDisabled && resolved.activeEffort) {
|
|
1937
|
+
return clampReasoningEffort(model, effortToReasoningEffort(resolved.activeEffort, model));
|
|
1938
|
+
}
|
|
1939
|
+
return resolved.thinkingDisabled ? clampReasoningEffort(model, "none") : void 0;
|
|
1940
|
+
}
|
|
1941
|
+
var warnedChatMaxDowngrades = /* @__PURE__ */ new Set();
|
|
1942
|
+
function resolveOpenAIChatReasoningEffort(model, resolved) {
|
|
1943
|
+
const effort = resolveOpenAIReasoningEffort(model, resolved);
|
|
1944
|
+
if (effort !== "max") return effort;
|
|
1945
|
+
if (resolved.activeEffort === "max" && !warnedChatMaxDowngrades.has(model)) {
|
|
1946
|
+
warnedChatMaxDowngrades.add(model);
|
|
1947
|
+
console.warn(
|
|
1948
|
+
`[axl] OpenAI Chat Completions does not accept effort "max" for ${model}; using "xhigh". Use the OpenAI Responses provider for native "max".`
|
|
1949
|
+
);
|
|
1950
|
+
}
|
|
1951
|
+
return "xhigh";
|
|
1952
|
+
}
|
|
1508
1953
|
var openaiReasoningEmit = (body, resolved, model) => {
|
|
1509
1954
|
const oSeries = isOSeriesModel(model);
|
|
1510
1955
|
const reasoningCapable = supportsReasoningEffort(model);
|
|
1511
|
-
const
|
|
1512
|
-
let wireEffort;
|
|
1513
|
-
if (reasoningCapable) {
|
|
1514
|
-
if (hasBudgetOverride) {
|
|
1515
|
-
wireEffort = clampReasoningEffort(model, budgetToReasoningEffort(thinkingBudget));
|
|
1516
|
-
} else if (!thinkingDisabled && activeEffort) {
|
|
1517
|
-
wireEffort = clampReasoningEffort(model, effortToReasoningEffort(activeEffort));
|
|
1518
|
-
} else if (thinkingDisabled) {
|
|
1519
|
-
wireEffort = clampReasoningEffort(model, "none");
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1956
|
+
const wireEffort = resolveOpenAIChatReasoningEffort(model, resolved);
|
|
1522
1957
|
if (wireEffort) body.reasoning_effort = wireEffort;
|
|
1523
1958
|
return { stripTemperature: oSeries || reasoningCapable && wireEffort !== void 0 };
|
|
1524
1959
|
};
|
|
@@ -1528,7 +1963,7 @@ var OPENAI_PROFILE = {
|
|
|
1528
1963
|
defaultBaseUrl: "https://api.openai.com/v1",
|
|
1529
1964
|
envApiKey: "OPENAI_API_KEY",
|
|
1530
1965
|
envBaseUrl: "OPENAI_BASE_URL",
|
|
1531
|
-
pricing: { kind: "table", table: OPENAI_PRICING },
|
|
1966
|
+
pricing: { kind: "table", table: OPENAI_PRICING, match: "exact" },
|
|
1532
1967
|
reasoning: { emit: openaiReasoningEmit, capture: "none" },
|
|
1533
1968
|
roleFor: (role, model) => role === "system" && isOSeriesModel(model) ? "developer" : role,
|
|
1534
1969
|
maxTokensField: "max_completion_tokens",
|
|
@@ -1538,6 +1973,9 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
|
|
|
1538
1973
|
constructor(options = {}) {
|
|
1539
1974
|
super({ profile: OPENAI_PROFILE, ...options });
|
|
1540
1975
|
}
|
|
1976
|
+
computeCost(model, usage, _reportedCost, context) {
|
|
1977
|
+
return usage ? estimateDirectOpenAICost(model, usage, { ...context, baseUrl: this.baseUrl }) : void 0;
|
|
1978
|
+
}
|
|
1541
1979
|
};
|
|
1542
1980
|
|
|
1543
1981
|
// src/providers/openai-responses.ts
|
|
@@ -1553,6 +1991,11 @@ var OpenAIResponsesProvider = class {
|
|
|
1553
1991
|
constructor(options = {}) {
|
|
1554
1992
|
this.apiKeySource = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
|
|
1555
1993
|
this.baseUrl = (options.baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
1994
|
+
assertSafeProviderBaseUrl(
|
|
1995
|
+
this.baseUrl,
|
|
1996
|
+
"OpenAI Responses provider",
|
|
1997
|
+
options.dangerouslyAllowInsecureHttp
|
|
1998
|
+
);
|
|
1556
1999
|
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
1557
2000
|
if (typeof this.apiKeySource === "string" && !this.apiKeySource) {
|
|
1558
2001
|
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey in options.");
|
|
@@ -1598,7 +2041,7 @@ var OpenAIResponsesProvider = class {
|
|
|
1598
2041
|
});
|
|
1599
2042
|
}
|
|
1600
2043
|
const json = await res.json();
|
|
1601
|
-
return this.parseResponse(json, options.model);
|
|
2044
|
+
return this.parseResponse(json, this.requestModel(body, options.model), body);
|
|
1602
2045
|
}
|
|
1603
2046
|
// ---------------------------------------------------------------------------
|
|
1604
2047
|
// stream - SSE streaming
|
|
@@ -1630,30 +2073,23 @@ var OpenAIResponsesProvider = class {
|
|
|
1630
2073
|
if (!res.body) {
|
|
1631
2074
|
throw new Error("OpenAI Responses stream has no body");
|
|
1632
2075
|
}
|
|
1633
|
-
yield* this.parseSSEStream(res.body, options.model);
|
|
2076
|
+
yield* this.parseSSEStream(res.body, this.requestModel(body, options.model), body);
|
|
1634
2077
|
}
|
|
1635
2078
|
// ---------------------------------------------------------------------------
|
|
1636
2079
|
// Internal: build request body
|
|
1637
2080
|
// ---------------------------------------------------------------------------
|
|
1638
2081
|
buildRequestBody(messages, options, stream) {
|
|
1639
|
-
const
|
|
1640
|
-
const
|
|
1641
|
-
const
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
wireEffort = clampReasoningEffort(options.model, budgetToReasoningEffort(thinkingBudget));
|
|
1646
|
-
} else if (!thinkingDisabled && activeEffort) {
|
|
1647
|
-
wireEffort = clampReasoningEffort(options.model, effortToReasoningEffort(activeEffort));
|
|
1648
|
-
} else if (thinkingDisabled) {
|
|
1649
|
-
wireEffort = clampReasoningEffort(options.model, "none");
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
2082
|
+
const effectiveModel = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
|
|
2083
|
+
const oSeries = isOSeriesModel(effectiveModel);
|
|
2084
|
+
const reasoningCapable = supportsReasoningEffort(effectiveModel);
|
|
2085
|
+
const resolved = resolveThinkingOptions(options);
|
|
2086
|
+
const { includeThoughts } = resolved;
|
|
2087
|
+
const wireEffort = resolveOpenAIReasoningEffort(effectiveModel, resolved);
|
|
1652
2088
|
const stripTemp = oSeries || reasoningCapable && wireEffort !== void 0;
|
|
1653
2089
|
const systemMessages = messages.filter((m) => m.role === "system");
|
|
1654
2090
|
const nonSystemMessages = messages.filter((m) => m.role !== "system");
|
|
1655
2091
|
const body = {
|
|
1656
|
-
model:
|
|
2092
|
+
model: effectiveModel,
|
|
1657
2093
|
input: this.buildInput(nonSystemMessages),
|
|
1658
2094
|
store: false,
|
|
1659
2095
|
stream
|
|
@@ -1700,6 +2136,9 @@ var OpenAIResponsesProvider = class {
|
|
|
1700
2136
|
}
|
|
1701
2137
|
return body;
|
|
1702
2138
|
}
|
|
2139
|
+
requestModel(body, fallback) {
|
|
2140
|
+
return typeof body.model === "string" ? body.model : fallback;
|
|
2141
|
+
}
|
|
1703
2142
|
// ---------------------------------------------------------------------------
|
|
1704
2143
|
// Internal: message → input mapping
|
|
1705
2144
|
// ---------------------------------------------------------------------------
|
|
@@ -1767,7 +2206,7 @@ var OpenAIResponsesProvider = class {
|
|
|
1767
2206
|
// ---------------------------------------------------------------------------
|
|
1768
2207
|
// Internal: parse non-streaming response
|
|
1769
2208
|
// ---------------------------------------------------------------------------
|
|
1770
|
-
parseResponse(json, model) {
|
|
2209
|
+
parseResponse(json, model, request) {
|
|
1771
2210
|
let content = "";
|
|
1772
2211
|
let thinkingContent = "";
|
|
1773
2212
|
const toolCalls = [];
|
|
@@ -1804,9 +2243,14 @@ var OpenAIResponsesProvider = class {
|
|
|
1804
2243
|
completion_tokens: json.usage.output_tokens,
|
|
1805
2244
|
total_tokens: json.usage.total_tokens,
|
|
1806
2245
|
reasoning_tokens: json.usage.output_tokens_details?.reasoning_tokens,
|
|
1807
|
-
cached_tokens: json.usage.input_tokens_details?.cached_tokens
|
|
2246
|
+
cached_tokens: json.usage.input_tokens_details?.cached_tokens,
|
|
2247
|
+
cache_write_tokens: json.usage.input_tokens_details?.cache_write_tokens
|
|
1808
2248
|
} : void 0;
|
|
1809
|
-
const cost = usage ?
|
|
2249
|
+
const cost = usage ? estimateDirectOpenAICost(json.model ?? model, usage, {
|
|
2250
|
+
baseUrl: this.baseUrl,
|
|
2251
|
+
request,
|
|
2252
|
+
response: json
|
|
2253
|
+
}) : void 0;
|
|
1810
2254
|
const providerMetadata = reasoningItems.length > 0 ? { openaiReasoningItems: reasoningItems } : void 0;
|
|
1811
2255
|
return {
|
|
1812
2256
|
content,
|
|
@@ -1820,7 +2264,7 @@ var OpenAIResponsesProvider = class {
|
|
|
1820
2264
|
// ---------------------------------------------------------------------------
|
|
1821
2265
|
// Internal: parse SSE stream
|
|
1822
2266
|
// ---------------------------------------------------------------------------
|
|
1823
|
-
async *parseSSEStream(body, model) {
|
|
2267
|
+
async *parseSSEStream(body, model, request) {
|
|
1824
2268
|
const reader = body.getReader();
|
|
1825
2269
|
const decoder = new TextDecoder();
|
|
1826
2270
|
let buffer = "";
|
|
@@ -1848,7 +2292,7 @@ var OpenAIResponsesProvider = class {
|
|
|
1848
2292
|
} catch {
|
|
1849
2293
|
continue;
|
|
1850
2294
|
}
|
|
1851
|
-
const chunk = this.handleStreamEvent(eventType, data, model, callIdMap);
|
|
2295
|
+
const chunk = this.handleStreamEvent(eventType, data, model, callIdMap, request);
|
|
1852
2296
|
if (chunk) {
|
|
1853
2297
|
yield chunk;
|
|
1854
2298
|
if (chunk.type === "done") return;
|
|
@@ -1862,7 +2306,7 @@ var OpenAIResponsesProvider = class {
|
|
|
1862
2306
|
reader.releaseLock();
|
|
1863
2307
|
}
|
|
1864
2308
|
}
|
|
1865
|
-
handleStreamEvent(eventType, data, model, callIdMap) {
|
|
2309
|
+
handleStreamEvent(eventType, data, model, callIdMap, request) {
|
|
1866
2310
|
switch (eventType) {
|
|
1867
2311
|
case "response.output_text.delta":
|
|
1868
2312
|
return { type: "text_delta", content: data.delta ?? "" };
|
|
@@ -1896,19 +2340,19 @@ var OpenAIResponsesProvider = class {
|
|
|
1896
2340
|
completion_tokens: response.usage.output_tokens,
|
|
1897
2341
|
total_tokens: response.usage.total_tokens,
|
|
1898
2342
|
reasoning_tokens: response.usage.output_tokens_details?.reasoning_tokens,
|
|
1899
|
-
cached_tokens: response.usage.input_tokens_details?.cached_tokens
|
|
2343
|
+
cached_tokens: response.usage.input_tokens_details?.cached_tokens,
|
|
2344
|
+
cache_write_tokens: response.usage.input_tokens_details?.cache_write_tokens
|
|
1900
2345
|
} : void 0;
|
|
1901
2346
|
const reasoningItems = response?.output?.filter((item) => item.type === "reasoning") ?? [];
|
|
1902
2347
|
const providerMetadata = reasoningItems.length > 0 ? { openaiReasoningItems: reasoningItems } : void 0;
|
|
1903
2348
|
return {
|
|
1904
2349
|
type: "done",
|
|
1905
2350
|
usage,
|
|
1906
|
-
cost: usage ?
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
) : void 0,
|
|
2351
|
+
cost: usage ? estimateDirectOpenAICost(response?.model ?? model, usage, {
|
|
2352
|
+
baseUrl: this.baseUrl,
|
|
2353
|
+
request,
|
|
2354
|
+
response
|
|
2355
|
+
}) : void 0,
|
|
1912
2356
|
providerMetadata
|
|
1913
2357
|
};
|
|
1914
2358
|
}
|
|
@@ -1942,68 +2386,357 @@ var OpenAIResponsesProvider = class {
|
|
|
1942
2386
|
|
|
1943
2387
|
// src/providers/anthropic.ts
|
|
1944
2388
|
var ANTHROPIC_API_VERSION = "2023-06-01";
|
|
1945
|
-
var
|
|
1946
|
-
"claude-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
"claude-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
"claude-
|
|
1960
|
-
|
|
1961
|
-
|
|
2389
|
+
var CLAUDE_CAPABILITIES = {
|
|
2390
|
+
"claude-fable-5": {
|
|
2391
|
+
thinking: "adaptive-always-on",
|
|
2392
|
+
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
2393
|
+
manualBudget: false,
|
|
2394
|
+
stripTemperature: true
|
|
2395
|
+
},
|
|
2396
|
+
"claude-opus-5": {
|
|
2397
|
+
thinking: "adaptive-default-on",
|
|
2398
|
+
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
2399
|
+
manualBudget: false,
|
|
2400
|
+
stripTemperature: true,
|
|
2401
|
+
disableAt: "high"
|
|
2402
|
+
},
|
|
2403
|
+
"claude-sonnet-5": {
|
|
2404
|
+
thinking: "adaptive-default-on",
|
|
2405
|
+
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
2406
|
+
manualBudget: false,
|
|
2407
|
+
stripTemperature: true,
|
|
2408
|
+
disableAt: "max"
|
|
2409
|
+
},
|
|
2410
|
+
"claude-opus-4-8": {
|
|
2411
|
+
thinking: "adaptive-optional",
|
|
2412
|
+
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
2413
|
+
manualBudget: false,
|
|
2414
|
+
stripTemperature: true
|
|
2415
|
+
},
|
|
2416
|
+
"claude-opus-4-7": {
|
|
2417
|
+
thinking: "adaptive-optional",
|
|
2418
|
+
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
2419
|
+
manualBudget: false,
|
|
2420
|
+
stripTemperature: true
|
|
2421
|
+
},
|
|
2422
|
+
"claude-opus-4-6": {
|
|
2423
|
+
thinking: "adaptive-optional",
|
|
2424
|
+
effortLevels: ["low", "medium", "high", "max"],
|
|
2425
|
+
manualBudget: true
|
|
2426
|
+
},
|
|
2427
|
+
"claude-sonnet-4-6": {
|
|
2428
|
+
thinking: "adaptive-optional",
|
|
2429
|
+
effortLevels: ["low", "medium", "high", "max"],
|
|
2430
|
+
manualBudget: true
|
|
2431
|
+
},
|
|
2432
|
+
"claude-opus-4-5": {
|
|
2433
|
+
thinking: "effort-only",
|
|
2434
|
+
effortLevels: ["low", "medium", "high"],
|
|
2435
|
+
manualBudget: true
|
|
2436
|
+
},
|
|
2437
|
+
"claude-opus-4-5-20251101": {
|
|
2438
|
+
thinking: "effort-only",
|
|
2439
|
+
effortLevels: ["low", "medium", "high"],
|
|
2440
|
+
manualBudget: true
|
|
2441
|
+
}
|
|
1962
2442
|
};
|
|
1963
|
-
var
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
2443
|
+
var LEGACY_CLAUDE_CAPABILITY = {
|
|
2444
|
+
thinking: "legacy-manual",
|
|
2445
|
+
manualBudget: true
|
|
2446
|
+
};
|
|
2447
|
+
var LEGACY_CLAUDE_MODELS = /* @__PURE__ */ new Set([
|
|
2448
|
+
"claude-opus-4-1",
|
|
2449
|
+
"claude-opus-4-1-20250805",
|
|
2450
|
+
"claude-sonnet-4-5",
|
|
2451
|
+
"claude-sonnet-4-5-20250929",
|
|
2452
|
+
"claude-haiku-4-5",
|
|
2453
|
+
"claude-haiku-4-5-20251001",
|
|
2454
|
+
"claude-sonnet-4",
|
|
2455
|
+
"claude-sonnet-4-20250514",
|
|
2456
|
+
"claude-opus-4",
|
|
2457
|
+
"claude-opus-4-20250514",
|
|
2458
|
+
"claude-3-7-sonnet",
|
|
2459
|
+
"claude-3-7-sonnet-20250219",
|
|
2460
|
+
"claude-3-5-sonnet",
|
|
2461
|
+
"claude-3-5-sonnet-20241022",
|
|
2462
|
+
"claude-3-5-sonnet-20240620",
|
|
2463
|
+
"claude-3-5-haiku",
|
|
2464
|
+
"claude-3-5-haiku-20241022",
|
|
2465
|
+
"claude-3-opus",
|
|
2466
|
+
"claude-3-opus-20240229",
|
|
2467
|
+
"claude-3-sonnet",
|
|
2468
|
+
"claude-3-sonnet-20240229",
|
|
2469
|
+
"claude-3-haiku",
|
|
2470
|
+
"claude-3-haiku-20240307"
|
|
2471
|
+
]);
|
|
2472
|
+
var PRE_INFERENCE_GEO_MODELS = /* @__PURE__ */ new Set([
|
|
2473
|
+
...LEGACY_CLAUDE_MODELS,
|
|
2474
|
+
"claude-opus-4-5",
|
|
2475
|
+
"claude-opus-4-5-20251101"
|
|
2476
|
+
]);
|
|
2477
|
+
function resolveClaudeCapability(model) {
|
|
2478
|
+
return CLAUDE_CAPABILITIES[model] ?? (LEGACY_CLAUDE_MODELS.has(model) ? LEGACY_CLAUDE_CAPABILITY : void 0);
|
|
2479
|
+
}
|
|
2480
|
+
var ANTHROPIC_RATES = {
|
|
2481
|
+
"claude-fable-5": { input: 1e-5, output: 5e-5 },
|
|
2482
|
+
"claude-opus-5": { input: 5e-6, output: 25e-6 },
|
|
2483
|
+
"claude-sonnet-5": { input: 3e-6, output: 15e-6, sonnet5Intro: true },
|
|
2484
|
+
"claude-opus-4-8": { input: 5e-6, output: 25e-6 },
|
|
2485
|
+
"claude-opus-4-7": { input: 5e-6, output: 25e-6 },
|
|
2486
|
+
"claude-opus-4-6": { input: 5e-6, output: 25e-6 },
|
|
2487
|
+
"claude-sonnet-4-6": { input: 3e-6, output: 15e-6 },
|
|
2488
|
+
"claude-opus-4-5": { input: 5e-6, output: 25e-6 },
|
|
2489
|
+
"claude-opus-4-5-20251101": { input: 5e-6, output: 25e-6 },
|
|
2490
|
+
"claude-opus-4-1": { input: 15e-6, output: 75e-6 },
|
|
2491
|
+
"claude-opus-4-1-20250805": { input: 15e-6, output: 75e-6 },
|
|
2492
|
+
"claude-sonnet-4-5": { input: 3e-6, output: 15e-6 },
|
|
2493
|
+
"claude-sonnet-4-5-20250929": { input: 3e-6, output: 15e-6 },
|
|
2494
|
+
"claude-haiku-4-5": { input: 1e-6, output: 5e-6 },
|
|
2495
|
+
"claude-haiku-4-5-20251001": { input: 1e-6, output: 5e-6 }
|
|
2496
|
+
};
|
|
2497
|
+
function isValidTokenCount(value) {
|
|
2498
|
+
return value !== void 0 && Number.isSafeInteger(value) && value >= 0;
|
|
2499
|
+
}
|
|
2500
|
+
function estimateAnthropicCost(model, usage, now = /* @__PURE__ */ new Date()) {
|
|
2501
|
+
if (!model || !isValidTokenCount(usage.inputTokens) || !isValidTokenCount(usage.outputTokens)) {
|
|
2502
|
+
return void 0;
|
|
1975
2503
|
}
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
const cacheRead = cacheReadTokens ?? 0;
|
|
1979
|
-
const
|
|
1980
|
-
const
|
|
1981
|
-
|
|
2504
|
+
const rate = ANTHROPIC_RATES[model];
|
|
2505
|
+
if (!rate) return void 0;
|
|
2506
|
+
const cacheRead = usage.cacheReadTokens ?? 0;
|
|
2507
|
+
const cacheWrite5m = usage.cacheWrite5mTokens ?? 0;
|
|
2508
|
+
const cacheWrite1h = usage.cacheWrite1hTokens ?? 0;
|
|
2509
|
+
if (!isValidTokenCount(cacheRead) || !isValidTokenCount(cacheWrite5m) || !isValidTokenCount(cacheWrite1h)) {
|
|
2510
|
+
return void 0;
|
|
2511
|
+
}
|
|
2512
|
+
const cacheWriteTotal = safeTokenSum(cacheWrite5m, cacheWrite1h);
|
|
2513
|
+
if (cacheWriteTotal === void 0 || safeTokenSum(usage.inputTokens, cacheRead, cacheWriteTotal) === void 0) {
|
|
2514
|
+
return void 0;
|
|
2515
|
+
}
|
|
2516
|
+
if (usage.aggregateCacheWriteTokens !== void 0 && (!isValidTokenCount(usage.aggregateCacheWriteTokens) || usage.aggregateCacheWriteTokens !== cacheWriteTotal)) {
|
|
2517
|
+
return void 0;
|
|
2518
|
+
}
|
|
2519
|
+
const { input, output } = rate.sonnet5Intro && now < /* @__PURE__ */ new Date("2026-09-01T00:00:00Z") ? { input: 2e-6, output: 1e-5 } : rate;
|
|
2520
|
+
return usage.inputTokens * input + cacheRead * input * 0.1 + cacheWrite5m * input * 1.25 + cacheWrite1h * input * 2 + usage.outputTokens * output;
|
|
1982
2521
|
}
|
|
1983
2522
|
var THINKING_BUDGETS = {
|
|
1984
2523
|
low: 1024,
|
|
1985
2524
|
medium: 5e3,
|
|
1986
2525
|
high: 1e4,
|
|
2526
|
+
xhigh: 2e4,
|
|
1987
2527
|
// 30000 (not 32000) to stay under the 32K max_tokens limit on Opus 4/4.1.
|
|
1988
2528
|
// With auto-bump (+1024), max_tokens becomes 31024 which fits all models.
|
|
1989
2529
|
max: 3e4
|
|
1990
2530
|
};
|
|
1991
|
-
function
|
|
1992
|
-
|
|
2531
|
+
function clampAnthropicEffort(capability, effort) {
|
|
2532
|
+
const levels = capability.effortLevels;
|
|
2533
|
+
if (!levels) return effort === "max" || effort === "xhigh" ? "high" : effort;
|
|
2534
|
+
if (levels.includes(effort)) return effort;
|
|
2535
|
+
return "high";
|
|
1993
2536
|
}
|
|
1994
|
-
function
|
|
1995
|
-
|
|
2537
|
+
function budgetToClaudeEffort(budget) {
|
|
2538
|
+
if (budget <= THINKING_BUDGETS.low) return "low";
|
|
2539
|
+
if (budget <= THINKING_BUDGETS.medium) return "medium";
|
|
2540
|
+
if (budget <= THINKING_BUDGETS.high) return "high";
|
|
2541
|
+
if (budget <= THINKING_BUDGETS.xhigh) return "xhigh";
|
|
2542
|
+
return "max";
|
|
2543
|
+
}
|
|
2544
|
+
var warnedFableDisable = /* @__PURE__ */ new Set();
|
|
2545
|
+
function warnFableDisable(model) {
|
|
2546
|
+
if (warnedFableDisable.has(model)) return;
|
|
2547
|
+
warnedFableDisable.add(model);
|
|
2548
|
+
console.warn(
|
|
2549
|
+
`[axl] thinking cannot be disabled on Anthropic ${model}; using adaptive thinking at effort 'low'.`
|
|
2550
|
+
);
|
|
1996
2551
|
}
|
|
1997
|
-
function
|
|
1998
|
-
|
|
2552
|
+
function resolveClaudeThinking(model, resolved) {
|
|
2553
|
+
const capability = resolveClaudeCapability(model);
|
|
2554
|
+
if (!capability) return { stripTemperature: false };
|
|
2555
|
+
const activeEffort = resolved.activeEffort ? clampAnthropicEffort(capability, resolved.activeEffort) : void 0;
|
|
2556
|
+
const isClaude5 = capability.thinking === "adaptive-default-on" || capability.thinking === "adaptive-always-on";
|
|
2557
|
+
if (isClaude5) {
|
|
2558
|
+
if (resolved.hasBudgetOverride) {
|
|
2559
|
+
const effort = clampAnthropicEffort(
|
|
2560
|
+
capability,
|
|
2561
|
+
budgetToClaudeEffort(resolved.thinkingBudget)
|
|
2562
|
+
);
|
|
2563
|
+
return {
|
|
2564
|
+
thinking: { type: "adaptive" },
|
|
2565
|
+
outputConfig: { effort },
|
|
2566
|
+
stripTemperature: true
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
if (resolved.thinkingDisabled) {
|
|
2570
|
+
if (capability.thinking === "adaptive-always-on") {
|
|
2571
|
+
warnFableDisable(model);
|
|
2572
|
+
return {
|
|
2573
|
+
thinking: { type: "adaptive" },
|
|
2574
|
+
outputConfig: { effort: "low" },
|
|
2575
|
+
stripTemperature: true
|
|
2576
|
+
};
|
|
2577
|
+
}
|
|
2578
|
+
if (activeEffort && capability.disableAt && !isEffortAtOrBelow(activeEffort, capability.disableAt)) {
|
|
2579
|
+
return {
|
|
2580
|
+
thinking: { type: "adaptive" },
|
|
2581
|
+
outputConfig: { effort: activeEffort },
|
|
2582
|
+
stripTemperature: true
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2585
|
+
return {
|
|
2586
|
+
thinking: { type: "disabled" },
|
|
2587
|
+
outputConfig: activeEffort ? { effort: activeEffort } : void 0,
|
|
2588
|
+
stripTemperature: true
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
return activeEffort ? {
|
|
2592
|
+
thinking: { type: "adaptive" },
|
|
2593
|
+
outputConfig: { effort: activeEffort },
|
|
2594
|
+
stripTemperature: true
|
|
2595
|
+
} : { stripTemperature: true };
|
|
2596
|
+
}
|
|
2597
|
+
if (resolved.hasBudgetOverride) {
|
|
2598
|
+
if (!capability.manualBudget) {
|
|
2599
|
+
const effort = clampAnthropicEffort(
|
|
2600
|
+
capability,
|
|
2601
|
+
budgetToClaudeEffort(resolved.thinkingBudget)
|
|
2602
|
+
);
|
|
2603
|
+
return capability.thinking === "adaptive-optional" ? {
|
|
2604
|
+
thinking: { type: "adaptive" },
|
|
2605
|
+
outputConfig: { effort },
|
|
2606
|
+
stripTemperature: capability.stripTemperature ?? true
|
|
2607
|
+
} : { outputConfig: { effort }, stripTemperature: capability.stripTemperature ?? false };
|
|
2608
|
+
}
|
|
2609
|
+
return {
|
|
2610
|
+
thinking: { type: "enabled", budget_tokens: resolved.thinkingBudget },
|
|
2611
|
+
outputConfig: activeEffort && capability.effortLevels ? { effort: activeEffort } : void 0,
|
|
2612
|
+
manualBudget: resolved.thinkingBudget,
|
|
2613
|
+
stripTemperature: capability.stripTemperature ?? true
|
|
2614
|
+
};
|
|
2615
|
+
}
|
|
2616
|
+
if (resolved.thinkingDisabled) {
|
|
2617
|
+
return {
|
|
2618
|
+
outputConfig: activeEffort && capability.effortLevels ? { effort: activeEffort } : void 0,
|
|
2619
|
+
stripTemperature: capability.stripTemperature ?? false
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
if (activeEffort && capability.thinking === "adaptive-optional") {
|
|
2623
|
+
return {
|
|
2624
|
+
thinking: { type: "adaptive" },
|
|
2625
|
+
outputConfig: { effort: activeEffort },
|
|
2626
|
+
stripTemperature: capability.stripTemperature ?? true
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
if (activeEffort && capability.thinking === "effort-only") {
|
|
2630
|
+
return { outputConfig: { effort: activeEffort }, stripTemperature: false };
|
|
2631
|
+
}
|
|
2632
|
+
if (activeEffort) {
|
|
2633
|
+
const budget = THINKING_BUDGETS[activeEffort] ?? THINKING_BUDGETS.medium;
|
|
2634
|
+
return {
|
|
2635
|
+
thinking: { type: "enabled", budget_tokens: budget },
|
|
2636
|
+
manualBudget: budget,
|
|
2637
|
+
stripTemperature: true
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
return { stripTemperature: capability.stripTemperature ?? false };
|
|
1999
2641
|
}
|
|
2000
|
-
function
|
|
2001
|
-
|
|
2642
|
+
function isEffortAtOrBelow(effort, ceiling) {
|
|
2643
|
+
const levels = ["low", "medium", "high", "xhigh", "max"];
|
|
2644
|
+
return levels.indexOf(effort) <= levels.indexOf(ceiling);
|
|
2002
2645
|
}
|
|
2003
|
-
function
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2646
|
+
function pricingContextFromBody(body) {
|
|
2647
|
+
return {
|
|
2648
|
+
model: typeof body.model === "string" ? body.model : void 0,
|
|
2649
|
+
unpricedModifier: isAnthropicModifier("inference_geo", body.inference_geo) || isAnthropicModifier("speed", body.speed) || hasAnthropicServerTool(body.tools) || body.fallbacks !== void 0
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
function isModifiedAnthropicResponse(json, model) {
|
|
2653
|
+
return isAnthropicResponseModifier("inference_geo", json.inference_geo, model) || isAnthropicResponseModifier("speed", json.speed, model) || isAnthropicResponseModifier("inference_geo", json.usage?.inference_geo, model) || isAnthropicResponseModifier("speed", json.usage?.speed, model) || hasBilledUnmodeledUsage(json.usage?.server_tool_use) || hasUnmodeledIterations(json.usage?.iterations);
|
|
2654
|
+
}
|
|
2655
|
+
function isAnthropicModifier(kind, value) {
|
|
2656
|
+
if (value === void 0) return false;
|
|
2657
|
+
return kind === "speed" ? value !== "standard" : value !== "global";
|
|
2658
|
+
}
|
|
2659
|
+
function isAnthropicResponseModifier(kind, value, model) {
|
|
2660
|
+
if (value === void 0) return false;
|
|
2661
|
+
if (kind === "speed") return value !== "standard";
|
|
2662
|
+
if (value === "global") return false;
|
|
2663
|
+
return value !== "not_available" || model === void 0 || !PRE_INFERENCE_GEO_MODELS.has(model);
|
|
2664
|
+
}
|
|
2665
|
+
function hasAnthropicServerTool(tools) {
|
|
2666
|
+
return Array.isArray(tools) && tools.some(
|
|
2667
|
+
(tool2) => tool2 !== null && typeof tool2 === "object" && typeof tool2.type === "string"
|
|
2668
|
+
);
|
|
2669
|
+
}
|
|
2670
|
+
function hasBilledUnmodeledUsage(value) {
|
|
2671
|
+
return value !== void 0 && value !== 0;
|
|
2672
|
+
}
|
|
2673
|
+
function hasUnmodeledIterations(value) {
|
|
2674
|
+
return value !== void 0;
|
|
2675
|
+
}
|
|
2676
|
+
function hasFallbackIteration(value) {
|
|
2677
|
+
return Array.isArray(value) && value.some(
|
|
2678
|
+
(iteration) => iteration !== null && typeof iteration === "object" && iteration.type === "fallback_message"
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
function safeTokenSum(...values) {
|
|
2682
|
+
const total = values.reduce((sum, value) => sum + value, 0);
|
|
2683
|
+
return Number.isSafeInteger(total) ? total : void 0;
|
|
2684
|
+
}
|
|
2685
|
+
function normalizeAnthropicUsage(raw) {
|
|
2686
|
+
const inputTokens = raw.input_tokens;
|
|
2687
|
+
const outputTokens = raw.output_tokens;
|
|
2688
|
+
const cacheReadTokens = raw.cache_read_input_tokens ?? 0;
|
|
2689
|
+
const cacheWrite5mTokens = raw.cache_creation?.ephemeral_5m_input_tokens ?? 0;
|
|
2690
|
+
const cacheWrite1hTokens = raw.cache_creation?.ephemeral_1h_input_tokens ?? 0;
|
|
2691
|
+
if (!isValidTokenCount(inputTokens) || !isValidTokenCount(outputTokens) || !isValidTokenCount(cacheReadTokens) || !isValidTokenCount(cacheWrite5mTokens) || !isValidTokenCount(cacheWrite1hTokens)) {
|
|
2692
|
+
return void 0;
|
|
2693
|
+
}
|
|
2694
|
+
const hasTtlBreakdown = raw.cache_creation?.ephemeral_5m_input_tokens !== void 0 || raw.cache_creation?.ephemeral_1h_input_tokens !== void 0;
|
|
2695
|
+
const aggregateCacheWriteTokens = raw.cache_creation_input_tokens;
|
|
2696
|
+
const normalizedCacheWrite = hasTtlBreakdown ? safeTokenSum(cacheWrite5mTokens, cacheWrite1hTokens) : void 0;
|
|
2697
|
+
if (hasTtlBreakdown && normalizedCacheWrite === void 0) return void 0;
|
|
2698
|
+
const promptCacheWrite = aggregateCacheWriteTokens ?? normalizedCacheWrite ?? 0;
|
|
2699
|
+
if (!isValidTokenCount(promptCacheWrite)) return void 0;
|
|
2700
|
+
const promptTokens = safeTokenSum(inputTokens, cacheReadTokens, promptCacheWrite);
|
|
2701
|
+
if (promptTokens === void 0) return void 0;
|
|
2702
|
+
const totalTokens = safeTokenSum(promptTokens, outputTokens);
|
|
2703
|
+
if (totalTokens === void 0) return void 0;
|
|
2704
|
+
return {
|
|
2705
|
+
usage: {
|
|
2706
|
+
prompt_tokens: promptTokens,
|
|
2707
|
+
completion_tokens: outputTokens,
|
|
2708
|
+
total_tokens: totalTokens,
|
|
2709
|
+
cached_tokens: cacheReadTokens > 0 ? cacheReadTokens : void 0,
|
|
2710
|
+
// The aggregate is still useful public usage telemetry even though it
|
|
2711
|
+
// cannot be priced without the 5m/1h split. Keep pricingUsage separate
|
|
2712
|
+
// so estimateAnthropicCost continues to fail closed in that case.
|
|
2713
|
+
cache_write_tokens: (normalizedCacheWrite ?? aggregateCacheWriteTokens ?? 0) > 0 ? normalizedCacheWrite ?? aggregateCacheWriteTokens : void 0
|
|
2714
|
+
},
|
|
2715
|
+
pricingUsage: {
|
|
2716
|
+
inputTokens,
|
|
2717
|
+
outputTokens,
|
|
2718
|
+
cacheReadTokens,
|
|
2719
|
+
cacheWrite5mTokens,
|
|
2720
|
+
cacheWrite1hTokens,
|
|
2721
|
+
aggregateCacheWriteTokens
|
|
2722
|
+
}
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
function mergeAnthropicUsage(initial, update) {
|
|
2726
|
+
return {
|
|
2727
|
+
input_tokens: update.input_tokens ?? initial?.input_tokens,
|
|
2728
|
+
output_tokens: update.output_tokens ?? initial?.output_tokens,
|
|
2729
|
+
cache_read_input_tokens: update.cache_read_input_tokens ?? initial?.cache_read_input_tokens,
|
|
2730
|
+
cache_creation_input_tokens: update.cache_creation_input_tokens ?? initial?.cache_creation_input_tokens,
|
|
2731
|
+
cache_creation: {
|
|
2732
|
+
ephemeral_5m_input_tokens: update.cache_creation?.ephemeral_5m_input_tokens ?? initial?.cache_creation?.ephemeral_5m_input_tokens,
|
|
2733
|
+
ephemeral_1h_input_tokens: update.cache_creation?.ephemeral_1h_input_tokens ?? initial?.cache_creation?.ephemeral_1h_input_tokens
|
|
2734
|
+
},
|
|
2735
|
+
speed: update.speed ?? initial?.speed,
|
|
2736
|
+
inference_geo: update.inference_geo ?? initial?.inference_geo,
|
|
2737
|
+
iterations: update.iterations ?? initial?.iterations,
|
|
2738
|
+
server_tool_use: update.server_tool_use ?? initial?.server_tool_use
|
|
2739
|
+
};
|
|
2007
2740
|
}
|
|
2008
2741
|
var AnthropicProvider = class {
|
|
2009
2742
|
name = "anthropic";
|
|
@@ -2018,6 +2751,11 @@ var AnthropicProvider = class {
|
|
|
2018
2751
|
constructor(options = {}) {
|
|
2019
2752
|
this.apiKeySource = options.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
|
|
2020
2753
|
this.baseUrl = (options.baseUrl ?? "https://api.anthropic.com/v1").replace(/\/$/, "");
|
|
2754
|
+
assertSafeProviderBaseUrl(
|
|
2755
|
+
this.baseUrl,
|
|
2756
|
+
"Anthropic provider",
|
|
2757
|
+
options.dangerouslyAllowInsecureHttp
|
|
2758
|
+
);
|
|
2021
2759
|
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
2022
2760
|
if (typeof this.apiKeySource === "string" && !this.apiKeySource) {
|
|
2023
2761
|
throw new Error(
|
|
@@ -2041,6 +2779,7 @@ var AnthropicProvider = class {
|
|
|
2041
2779
|
async chat(messages, options) {
|
|
2042
2780
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
2043
2781
|
const body = this.buildRequestBody(messages, options, false);
|
|
2782
|
+
const pricingContext = pricingContextFromBody(body);
|
|
2044
2783
|
const res = await fetchWithRetry(
|
|
2045
2784
|
`${this.baseUrl}/messages`,
|
|
2046
2785
|
{
|
|
@@ -2063,7 +2802,7 @@ var AnthropicProvider = class {
|
|
|
2063
2802
|
});
|
|
2064
2803
|
}
|
|
2065
2804
|
const json = await res.json();
|
|
2066
|
-
return this.parseResponse(json,
|
|
2805
|
+
return this.parseResponse(json, pricingContext);
|
|
2067
2806
|
}
|
|
2068
2807
|
// ---------------------------------------------------------------------------
|
|
2069
2808
|
// stream - SSE streaming completion
|
|
@@ -2071,6 +2810,7 @@ var AnthropicProvider = class {
|
|
|
2071
2810
|
async *stream(messages, options) {
|
|
2072
2811
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
2073
2812
|
const body = this.buildRequestBody(messages, options, true);
|
|
2813
|
+
const pricingContext = pricingContextFromBody(body);
|
|
2074
2814
|
const res = await fetchWithRetry(
|
|
2075
2815
|
`${this.baseUrl}/messages`,
|
|
2076
2816
|
{
|
|
@@ -2095,7 +2835,7 @@ var AnthropicProvider = class {
|
|
|
2095
2835
|
if (!res.body) {
|
|
2096
2836
|
throw new Error("Anthropic stream response has no body");
|
|
2097
2837
|
}
|
|
2098
|
-
yield* this.parseSSEStream(res.body,
|
|
2838
|
+
yield* this.parseSSEStream(res.body, pricingContext);
|
|
2099
2839
|
}
|
|
2100
2840
|
// ---------------------------------------------------------------------------
|
|
2101
2841
|
// Internal: request building
|
|
@@ -2119,11 +2859,12 @@ var AnthropicProvider = class {
|
|
|
2119
2859
|
return `Anthropic API error (${status}): ${body}`;
|
|
2120
2860
|
}
|
|
2121
2861
|
buildRequestBody(messages, options, stream) {
|
|
2862
|
+
const effectiveModel = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
|
|
2122
2863
|
const systemMessages = messages.filter((m) => m.role === "system");
|
|
2123
2864
|
const nonSystemMessages = messages.filter((m) => m.role !== "system");
|
|
2124
2865
|
const systemText = systemMessages.map((m) => m.content).join("\n\n");
|
|
2125
2866
|
const body = {
|
|
2126
|
-
model:
|
|
2867
|
+
model: effectiveModel,
|
|
2127
2868
|
messages: this.mapMessages(nonSystemMessages),
|
|
2128
2869
|
max_tokens: options.maxTokens ?? 4096,
|
|
2129
2870
|
stream
|
|
@@ -2140,35 +2881,16 @@ var AnthropicProvider = class {
|
|
|
2140
2881
|
if (options.toolChoice !== void 0) {
|
|
2141
2882
|
body.tool_choice = this.mapToolChoice(options.toolChoice);
|
|
2142
2883
|
}
|
|
2143
|
-
const
|
|
2144
|
-
|
|
2145
|
-
if (
|
|
2146
|
-
|
|
2884
|
+
const thinking = resolveClaudeThinking(effectiveModel, resolveThinkingOptions(options));
|
|
2885
|
+
if (thinking.thinking) body.thinking = thinking.thinking;
|
|
2886
|
+
if (thinking.outputConfig) body.output_config = thinking.outputConfig;
|
|
2887
|
+
if (thinking.manualBudget) {
|
|
2147
2888
|
const currentMax = body.max_tokens;
|
|
2148
|
-
if (currentMax <
|
|
2149
|
-
body.max_tokens =
|
|
2150
|
-
}
|
|
2151
|
-
if (resolvedEffort && supportsEffort(options.model)) {
|
|
2152
|
-
body.output_config = { effort: resolvedEffort };
|
|
2153
|
-
}
|
|
2154
|
-
} else if (thinkingDisabled) {
|
|
2155
|
-
if (resolvedEffort && supportsEffort(options.model)) {
|
|
2156
|
-
body.output_config = { effort: resolvedEffort };
|
|
2157
|
-
}
|
|
2158
|
-
} else if (resolvedEffort && supportsAdaptiveThinking(options.model)) {
|
|
2159
|
-
body.thinking = { type: "adaptive" };
|
|
2160
|
-
body.output_config = { effort: resolvedEffort };
|
|
2161
|
-
} else if (resolvedEffort && supportsEffort(options.model)) {
|
|
2162
|
-
body.output_config = { effort: resolvedEffort };
|
|
2163
|
-
} else if (resolvedEffort) {
|
|
2164
|
-
const budget = THINKING_BUDGETS[resolvedEffort] ?? 5e3;
|
|
2165
|
-
body.thinking = { type: "enabled", budget_tokens: budget };
|
|
2166
|
-
const currentMax = body.max_tokens;
|
|
2167
|
-
if (currentMax < budget + 1024) {
|
|
2168
|
-
body.max_tokens = budget + 1024;
|
|
2889
|
+
if (currentMax < thinking.manualBudget + 1024) {
|
|
2890
|
+
body.max_tokens = thinking.manualBudget + 1024;
|
|
2169
2891
|
}
|
|
2170
2892
|
}
|
|
2171
|
-
if (options.temperature !== void 0 && !
|
|
2893
|
+
if (options.temperature !== void 0 && !thinking.stripTemperature) {
|
|
2172
2894
|
body.temperature = options.temperature;
|
|
2173
2895
|
}
|
|
2174
2896
|
if (options.responseFormat && options.responseFormat.type !== "text") {
|
|
@@ -2193,8 +2915,9 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
2193
2915
|
const result = [];
|
|
2194
2916
|
for (const msg of messages) {
|
|
2195
2917
|
if (msg.role === "assistant") {
|
|
2918
|
+
const replayedThinking = this.getReplayedThinkingBlocks(msg);
|
|
2196
2919
|
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
2197
|
-
const content = [];
|
|
2920
|
+
const content = [...replayedThinking];
|
|
2198
2921
|
if (msg.content) {
|
|
2199
2922
|
content.push({ type: "text", text: msg.content });
|
|
2200
2923
|
}
|
|
@@ -2213,6 +2936,10 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
2213
2936
|
});
|
|
2214
2937
|
}
|
|
2215
2938
|
result.push({ role: "assistant", content });
|
|
2939
|
+
} else if (replayedThinking.length > 0) {
|
|
2940
|
+
const content = [...replayedThinking];
|
|
2941
|
+
if (msg.content) content.push({ type: "text", text: msg.content });
|
|
2942
|
+
result.push({ role: "assistant", content });
|
|
2216
2943
|
} else {
|
|
2217
2944
|
result.push({ role: "assistant", content: msg.content });
|
|
2218
2945
|
}
|
|
@@ -2233,6 +2960,13 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
2233
2960
|
}
|
|
2234
2961
|
return this.mergeConsecutiveRoles(result);
|
|
2235
2962
|
}
|
|
2963
|
+
getReplayedThinkingBlocks(msg) {
|
|
2964
|
+
const blocks = msg.providerMetadata?.anthropicThinkingBlocks;
|
|
2965
|
+
if (!Array.isArray(blocks)) return [];
|
|
2966
|
+
return blocks.filter(
|
|
2967
|
+
(block) => block !== null && typeof block === "object" && (block.type === "thinking" || block.type === "redacted_thinking")
|
|
2968
|
+
);
|
|
2969
|
+
}
|
|
2236
2970
|
/**
|
|
2237
2971
|
* Merge consecutive messages with the same role into a single message.
|
|
2238
2972
|
* This handles cases where multiple tool_result blocks need to be in one user message.
|
|
@@ -2288,16 +3022,24 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
2288
3022
|
// ---------------------------------------------------------------------------
|
|
2289
3023
|
// Internal: response parsing
|
|
2290
3024
|
// ---------------------------------------------------------------------------
|
|
2291
|
-
parseResponse(json,
|
|
3025
|
+
parseResponse(json, pricingContext) {
|
|
2292
3026
|
let content = "";
|
|
2293
3027
|
let thinkingContent = "";
|
|
2294
3028
|
const toolCalls = [];
|
|
2295
|
-
|
|
3029
|
+
const thinkingBlocks = [];
|
|
3030
|
+
const fallbackIndex = lastFallbackIndex(json.content);
|
|
3031
|
+
const hasFallbackBoundary = fallbackIndex >= 0 || hasFallbackIteration(json.usage?.iterations);
|
|
3032
|
+
let refused = json.stop_reason === "refusal";
|
|
3033
|
+
for (const [index, block] of json.content.entries()) {
|
|
2296
3034
|
if (block.type === "thinking") {
|
|
2297
3035
|
thinkingContent += block.thinking;
|
|
3036
|
+
thinkingBlocks.push(block);
|
|
3037
|
+
} else if (block.type === "redacted_thinking") {
|
|
3038
|
+
thinkingBlocks.push(block);
|
|
2298
3039
|
} else if (block.type === "text") {
|
|
2299
3040
|
content += block.text;
|
|
2300
3041
|
} else if (block.type === "tool_use") {
|
|
3042
|
+
if (fallbackIndex >= 0 && index < fallbackIndex) continue;
|
|
2301
3043
|
toolCalls.push({
|
|
2302
3044
|
id: block.id,
|
|
2303
3045
|
type: "function",
|
|
@@ -2306,37 +3048,58 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
2306
3048
|
arguments: JSON.stringify(block.input)
|
|
2307
3049
|
}
|
|
2308
3050
|
});
|
|
3051
|
+
} else if (block.type === "fallback") {
|
|
3052
|
+
} else if (block.type === "refusal") {
|
|
3053
|
+
refused = true;
|
|
2309
3054
|
}
|
|
2310
3055
|
}
|
|
2311
|
-
const
|
|
2312
|
-
const
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
} : void 0;
|
|
2320
|
-
const cost = json.usage ? estimateAnthropicCost(model, totalInput, json.usage.output_tokens, cacheRead, cacheWrite) : void 0;
|
|
3056
|
+
const normalized = json.usage ? normalizeAnthropicUsage(json.usage) : void 0;
|
|
3057
|
+
const effectiveModel = typeof json.model === "string" ? json.model : pricingContext.model;
|
|
3058
|
+
if (hasFallbackBoundary) {
|
|
3059
|
+
throw new Error(
|
|
3060
|
+
"Anthropic fallback responses cannot be continued safely; retry without fallbacks."
|
|
3061
|
+
);
|
|
3062
|
+
}
|
|
3063
|
+
const cost = normalized && !pricingContext.unpricedModifier && !isModifiedAnthropicResponse(json, effectiveModel) && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, normalized.pricingUsage) : void 0;
|
|
2321
3064
|
return {
|
|
2322
3065
|
content,
|
|
2323
3066
|
thinking_content: thinkingContent || void 0,
|
|
2324
3067
|
tool_calls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
2325
|
-
usage,
|
|
2326
|
-
cost
|
|
3068
|
+
usage: normalized?.usage,
|
|
3069
|
+
cost,
|
|
3070
|
+
providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
|
|
2327
3071
|
};
|
|
2328
3072
|
}
|
|
2329
3073
|
// ---------------------------------------------------------------------------
|
|
2330
3074
|
// Internal: SSE stream parsing
|
|
2331
3075
|
// ---------------------------------------------------------------------------
|
|
2332
|
-
async *parseSSEStream(body,
|
|
3076
|
+
async *parseSSEStream(body, pricingContext) {
|
|
2333
3077
|
const reader = body.getReader();
|
|
2334
3078
|
const decoder = new TextDecoder();
|
|
2335
3079
|
let buffer = "";
|
|
2336
3080
|
let currentToolId = "";
|
|
2337
3081
|
let currentToolName = "";
|
|
2338
3082
|
let usage;
|
|
2339
|
-
let
|
|
3083
|
+
let pricingUsage;
|
|
3084
|
+
let rawUsage;
|
|
3085
|
+
const thinkingBlocks = [];
|
|
3086
|
+
let currentThinkingBlock;
|
|
3087
|
+
let effectiveModel = pricingContext.model;
|
|
3088
|
+
let unpricedModifier = pricingContext.unpricedModifier;
|
|
3089
|
+
let hasFallbackBoundary = false;
|
|
3090
|
+
let hasFallbackIterationSignal = false;
|
|
3091
|
+
let refused = false;
|
|
3092
|
+
const finalizeUsage = () => {
|
|
3093
|
+
const normalized = rawUsage ? normalizeAnthropicUsage(rawUsage) : void 0;
|
|
3094
|
+
usage = normalized?.usage;
|
|
3095
|
+
pricingUsage = normalized?.pricingUsage;
|
|
3096
|
+
};
|
|
3097
|
+
const doneChunk = () => ({
|
|
3098
|
+
type: "done",
|
|
3099
|
+
usage,
|
|
3100
|
+
cost: pricingUsage && !unpricedModifier && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, pricingUsage) : void 0,
|
|
3101
|
+
providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
|
|
3102
|
+
});
|
|
2340
3103
|
try {
|
|
2341
3104
|
while (true) {
|
|
2342
3105
|
const { done, value } = await reader.read();
|
|
@@ -2367,13 +3130,31 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
2367
3130
|
name: currentToolName,
|
|
2368
3131
|
arguments: ""
|
|
2369
3132
|
};
|
|
3133
|
+
} else if (block?.type === "thinking" || block?.type === "redacted_thinking") {
|
|
3134
|
+
currentThinkingBlock = {
|
|
3135
|
+
...block,
|
|
3136
|
+
type: block.type
|
|
3137
|
+
};
|
|
3138
|
+
} else if (block?.type === "fallback") {
|
|
3139
|
+
hasFallbackBoundary = true;
|
|
3140
|
+
} else if (block?.type === "refusal") {
|
|
3141
|
+
refused = true;
|
|
2370
3142
|
}
|
|
2371
3143
|
break;
|
|
2372
3144
|
}
|
|
2373
3145
|
case "content_block_delta": {
|
|
2374
3146
|
const delta = event.delta;
|
|
2375
3147
|
if (delta?.type === "thinking_delta" && delta.thinking) {
|
|
3148
|
+
if (currentThinkingBlock?.type === "thinking") {
|
|
3149
|
+
const current = currentThinkingBlock.thinking;
|
|
3150
|
+
currentThinkingBlock.thinking = `${typeof current === "string" ? current : ""}${delta.thinking}`;
|
|
3151
|
+
}
|
|
2376
3152
|
yield { type: "thinking_delta", content: delta.thinking };
|
|
3153
|
+
} else if (delta?.type === "signature_delta" && delta.signature) {
|
|
3154
|
+
if (currentThinkingBlock) {
|
|
3155
|
+
const current = currentThinkingBlock.signature;
|
|
3156
|
+
currentThinkingBlock.signature = `${typeof current === "string" ? current : ""}${delta.signature}`;
|
|
3157
|
+
}
|
|
2377
3158
|
} else if (delta?.type === "text_delta" && delta.text) {
|
|
2378
3159
|
yield { type: "text_delta", content: delta.text };
|
|
2379
3160
|
} else if (delta?.type === "input_json_delta" && delta.partial_json) {
|
|
@@ -2386,76 +3167,82 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
2386
3167
|
break;
|
|
2387
3168
|
}
|
|
2388
3169
|
case "content_block_stop": {
|
|
3170
|
+
if (currentThinkingBlock) {
|
|
3171
|
+
thinkingBlocks.push(currentThinkingBlock);
|
|
3172
|
+
currentThinkingBlock = void 0;
|
|
3173
|
+
}
|
|
2389
3174
|
currentToolId = "";
|
|
2390
3175
|
currentToolName = "";
|
|
2391
3176
|
break;
|
|
2392
3177
|
}
|
|
2393
3178
|
case "message_start": {
|
|
2394
3179
|
if (event.message?.usage) {
|
|
2395
|
-
|
|
2396
|
-
cacheWrite = event.message.usage.cache_creation_input_tokens ?? 0;
|
|
2397
|
-
const inputTokens = (event.message.usage.input_tokens ?? 0) + cacheRead + cacheWrite;
|
|
2398
|
-
usage = {
|
|
2399
|
-
prompt_tokens: inputTokens,
|
|
2400
|
-
completion_tokens: 0,
|
|
2401
|
-
total_tokens: inputTokens,
|
|
2402
|
-
cached_tokens: cacheRead > 0 ? cacheRead : void 0
|
|
2403
|
-
};
|
|
3180
|
+
rawUsage = mergeAnthropicUsage(rawUsage, event.message.usage);
|
|
2404
3181
|
}
|
|
3182
|
+
if (typeof event.message?.model === "string") effectiveModel = event.message.model;
|
|
3183
|
+
const messageFallback = hasFallbackIteration(event.message?.usage?.iterations);
|
|
3184
|
+
if (isAnthropicResponseModifier(
|
|
3185
|
+
"inference_geo",
|
|
3186
|
+
event.message?.inference_geo,
|
|
3187
|
+
effectiveModel
|
|
3188
|
+
) || isAnthropicResponseModifier("speed", event.message?.speed, effectiveModel) || isAnthropicResponseModifier(
|
|
3189
|
+
"inference_geo",
|
|
3190
|
+
event.message?.usage?.inference_geo,
|
|
3191
|
+
effectiveModel
|
|
3192
|
+
) || isAnthropicResponseModifier("speed", event.message?.usage?.speed, effectiveModel) || hasBilledUnmodeledUsage(event.message?.usage?.server_tool_use) || hasUnmodeledIterations(event.message?.usage?.iterations)) {
|
|
3193
|
+
unpricedModifier = true;
|
|
3194
|
+
}
|
|
3195
|
+
if (messageFallback) hasFallbackIterationSignal = true;
|
|
2405
3196
|
break;
|
|
2406
3197
|
}
|
|
2407
3198
|
case "message_delta": {
|
|
2408
3199
|
if (event.usage) {
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
usage
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
};
|
|
3200
|
+
rawUsage = mergeAnthropicUsage(rawUsage, event.usage);
|
|
3201
|
+
finalizeUsage();
|
|
3202
|
+
const terminalFallback = hasFallbackIteration(event.usage.iterations);
|
|
3203
|
+
if (isAnthropicResponseModifier("speed", event.usage.speed, effectiveModel) || isAnthropicResponseModifier(
|
|
3204
|
+
"inference_geo",
|
|
3205
|
+
event.usage.inference_geo,
|
|
3206
|
+
effectiveModel
|
|
3207
|
+
) || hasBilledUnmodeledUsage(event.usage.server_tool_use) || hasUnmodeledIterations(event.usage.iterations)) {
|
|
3208
|
+
unpricedModifier = true;
|
|
2419
3209
|
}
|
|
3210
|
+
if (terminalFallback) hasFallbackIterationSignal = true;
|
|
2420
3211
|
}
|
|
3212
|
+
if (event.delta?.stop_reason === "refusal") refused = true;
|
|
2421
3213
|
break;
|
|
2422
3214
|
}
|
|
2423
3215
|
case "message_stop": {
|
|
2424
|
-
|
|
2425
|
-
|
|
3216
|
+
finalizeUsage();
|
|
3217
|
+
if (hasFallbackBoundary || hasFallbackIterationSignal) {
|
|
3218
|
+
throw new Error(
|
|
3219
|
+
"Anthropic fallback streams cannot be continued safely; retry without fallbacks."
|
|
3220
|
+
);
|
|
2426
3221
|
}
|
|
2427
|
-
yield
|
|
2428
|
-
type: "done",
|
|
2429
|
-
usage,
|
|
2430
|
-
cost: usage ? estimateAnthropicCost(
|
|
2431
|
-
model,
|
|
2432
|
-
usage.prompt_tokens,
|
|
2433
|
-
usage.completion_tokens,
|
|
2434
|
-
usage.cached_tokens,
|
|
2435
|
-
cacheWrite
|
|
2436
|
-
) : void 0
|
|
2437
|
-
};
|
|
3222
|
+
yield doneChunk();
|
|
2438
3223
|
return;
|
|
2439
3224
|
}
|
|
2440
3225
|
}
|
|
2441
3226
|
}
|
|
2442
3227
|
}
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
usage.cached_tokens,
|
|
2451
|
-
cacheWrite
|
|
2452
|
-
) : void 0
|
|
2453
|
-
};
|
|
3228
|
+
finalizeUsage();
|
|
3229
|
+
if (hasFallbackBoundary || hasFallbackIterationSignal) {
|
|
3230
|
+
throw new Error(
|
|
3231
|
+
"Anthropic fallback streams cannot be continued safely; retry without fallbacks."
|
|
3232
|
+
);
|
|
3233
|
+
}
|
|
3234
|
+
yield doneChunk();
|
|
2454
3235
|
} finally {
|
|
2455
3236
|
reader.releaseLock();
|
|
2456
3237
|
}
|
|
2457
3238
|
}
|
|
2458
3239
|
};
|
|
3240
|
+
function lastFallbackIndex(blocks) {
|
|
3241
|
+
for (let index = blocks.length - 1; index >= 0; index--) {
|
|
3242
|
+
if (blocks[index].type === "fallback") return index;
|
|
3243
|
+
}
|
|
3244
|
+
return -1;
|
|
3245
|
+
}
|
|
2459
3246
|
|
|
2460
3247
|
// src/providers/gemini.ts
|
|
2461
3248
|
var GEMINI_DISALLOWED_SCHEMA_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -2503,36 +3290,92 @@ function parseGeminiFunctionResponse(content) {
|
|
|
2503
3290
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { result: parsed };
|
|
2504
3291
|
}
|
|
2505
3292
|
var GEMINI_PRICING = {
|
|
2506
|
-
"gemini-2.5-pro":
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
"gemini-
|
|
2513
|
-
"gemini-
|
|
2514
|
-
"gemini-3
|
|
2515
|
-
"gemini-3.1-
|
|
2516
|
-
|
|
3293
|
+
"gemini-2.5-pro": {
|
|
3294
|
+
input: 125e-8,
|
|
3295
|
+
cached: 125e-9,
|
|
3296
|
+
output: 1e-5,
|
|
3297
|
+
longContext: { input: 25e-7, cached: 25e-8, output: 15e-6 }
|
|
3298
|
+
},
|
|
3299
|
+
"gemini-2.5-flash": { input: 3e-7, cached: 3e-8, output: 25e-7 },
|
|
3300
|
+
"gemini-2.5-flash-lite": { input: 1e-7, cached: 1e-8, output: 4e-7 },
|
|
3301
|
+
"gemini-3-flash-preview": { input: 5e-7, cached: 5e-8, output: 3e-6 },
|
|
3302
|
+
"gemini-3.1-pro-preview": {
|
|
3303
|
+
input: 2e-6,
|
|
3304
|
+
cached: 2e-7,
|
|
3305
|
+
output: 12e-6,
|
|
3306
|
+
longContext: { input: 4e-6, cached: 4e-7, output: 18e-6 }
|
|
3307
|
+
},
|
|
3308
|
+
"gemini-3.1-pro-preview-customtools": {
|
|
3309
|
+
input: 2e-6,
|
|
3310
|
+
cached: 2e-7,
|
|
3311
|
+
output: 12e-6,
|
|
3312
|
+
longContext: { input: 4e-6, cached: 4e-7, output: 18e-6 }
|
|
3313
|
+
},
|
|
3314
|
+
"gemini-3.1-flash-lite": { input: 25e-8, cached: 25e-9, output: 15e-7 },
|
|
3315
|
+
"gemini-3.5-flash": { input: 15e-7, cached: 15e-8, output: 9e-6 },
|
|
3316
|
+
"gemini-3.5-flash-lite": { input: 3e-7, cached: 3e-8, output: 25e-7 },
|
|
3317
|
+
"gemini-3.6-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 }
|
|
2517
3318
|
};
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
function
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
3319
|
+
function isValidTokenCount2(value) {
|
|
3320
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
3321
|
+
}
|
|
3322
|
+
function isStandardGeminiRequestTier(value) {
|
|
3323
|
+
return value === void 0 || value === "SERVICE_TIER_UNSPECIFIED" || value === "SERVICE_TIER_STANDARD";
|
|
3324
|
+
}
|
|
3325
|
+
function isDefinitiveStandardGeminiResponseTier(value) {
|
|
3326
|
+
return value === "SERVICE_TIER_UNSPECIFIED" || value === "SERVICE_TIER_STANDARD" || value === "standard";
|
|
3327
|
+
}
|
|
3328
|
+
function isEligibleGeminiPricing(context, hasDefinitiveStandardResponseTier, hasInvalidResponseTier) {
|
|
3329
|
+
return context.eligibleRequest && isStandardGeminiRequestTier(context.serviceTier) && hasDefinitiveStandardResponseTier && !hasInvalidResponseTier;
|
|
3330
|
+
}
|
|
3331
|
+
function estimateGeminiCost(model, usage) {
|
|
3332
|
+
const pricing = GEMINI_PRICING[model];
|
|
3333
|
+
const cached = usage.cachedTokens ?? 0;
|
|
3334
|
+
if (!pricing || !isValidTokenCount2(usage.inputTokens) || !isValidTokenCount2(usage.outputTokens) || !isValidTokenCount2(cached) || cached > usage.inputTokens) {
|
|
3335
|
+
return void 0;
|
|
2530
3336
|
}
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
3337
|
+
const rate = usage.inputTokens > 2e5 && pricing.longContext ? pricing.longContext : pricing;
|
|
3338
|
+
return (usage.inputTokens - cached) * rate.input + cached * rate.cached + usage.outputTokens * rate.output;
|
|
3339
|
+
}
|
|
3340
|
+
function safeTokenSum2(...values) {
|
|
3341
|
+
const total = values.reduce((sum, value) => sum + value, 0);
|
|
3342
|
+
return Number.isSafeInteger(total) ? total : void 0;
|
|
3343
|
+
}
|
|
3344
|
+
function normalizeGeminiUsage(raw) {
|
|
3345
|
+
const inputTokens = raw.promptTokenCount;
|
|
3346
|
+
const completionTokens = raw.candidatesTokenCount;
|
|
3347
|
+
const totalTokens = raw.totalTokenCount;
|
|
3348
|
+
const cachedTokens = raw.cachedContentTokenCount ?? 0;
|
|
3349
|
+
const reasoningTokens = raw.thoughtsTokenCount ?? 0;
|
|
3350
|
+
const toolUsePromptTokens = raw.toolUsePromptTokenCount ?? 0;
|
|
3351
|
+
if (!isValidTokenCount2(inputTokens) || !isValidTokenCount2(completionTokens) || !isValidTokenCount2(totalTokens) || !isValidTokenCount2(cachedTokens) || !isValidTokenCount2(reasoningTokens) || !isValidTokenCount2(toolUsePromptTokens) || cachedTokens > inputTokens) {
|
|
3352
|
+
return void 0;
|
|
3353
|
+
}
|
|
3354
|
+
const billedOutputTokens = safeTokenSum2(completionTokens, reasoningTokens);
|
|
3355
|
+
const expectedTotal = safeTokenSum2(
|
|
3356
|
+
inputTokens,
|
|
3357
|
+
completionTokens,
|
|
3358
|
+
reasoningTokens,
|
|
3359
|
+
toolUsePromptTokens
|
|
3360
|
+
);
|
|
3361
|
+
if (billedOutputTokens === void 0 || expectedTotal === void 0 || totalTokens !== expectedTotal) {
|
|
3362
|
+
return void 0;
|
|
3363
|
+
}
|
|
3364
|
+
return {
|
|
3365
|
+
usage: {
|
|
3366
|
+
prompt_tokens: inputTokens,
|
|
3367
|
+
completion_tokens: completionTokens,
|
|
3368
|
+
total_tokens: totalTokens,
|
|
3369
|
+
cached_tokens: cachedTokens > 0 ? cachedTokens : void 0,
|
|
3370
|
+
reasoning_tokens: reasoningTokens > 0 ? reasoningTokens : void 0
|
|
3371
|
+
},
|
|
3372
|
+
pricingUsage: {
|
|
3373
|
+
inputTokens,
|
|
3374
|
+
outputTokens: billedOutputTokens,
|
|
3375
|
+
cachedTokens
|
|
3376
|
+
},
|
|
3377
|
+
hasUnmodeledBilledUsage: toolUsePromptTokens !== 0
|
|
3378
|
+
};
|
|
2536
3379
|
}
|
|
2537
3380
|
var THINKING_BUDGETS2 = {
|
|
2538
3381
|
low: 1024,
|
|
@@ -2551,8 +3394,23 @@ var THINKING_LEVELS = {
|
|
|
2551
3394
|
max: "high"
|
|
2552
3395
|
// 3.x caps at 'high'
|
|
2553
3396
|
};
|
|
3397
|
+
var GEMINI_3X_MIN_THINKING_LEVEL = /* @__PURE__ */ new Map([
|
|
3398
|
+
["gemini-3-pro-preview", "minimal"],
|
|
3399
|
+
["gemini-3-flash-preview", "minimal"],
|
|
3400
|
+
["gemini-3.1-pro-preview", "low"],
|
|
3401
|
+
["gemini-3.1-pro-preview-customtools", "low"],
|
|
3402
|
+
["gemini-3.1-flash-lite", "minimal"],
|
|
3403
|
+
["gemini-3.1-flash-lite-preview", "minimal"],
|
|
3404
|
+
["gemini-3.5-flash", "minimal"],
|
|
3405
|
+
["gemini-3.5-flash-lite", "minimal"],
|
|
3406
|
+
["gemini-3.6-flash", "minimal"]
|
|
3407
|
+
]);
|
|
3408
|
+
var GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE = /* @__PURE__ */ new Set([
|
|
3409
|
+
"gemini-3.5-flash-lite",
|
|
3410
|
+
"gemini-3.6-flash"
|
|
3411
|
+
]);
|
|
2554
3412
|
function isGemini3x(model) {
|
|
2555
|
-
return
|
|
3413
|
+
return GEMINI_3X_MIN_THINKING_LEVEL.has(model);
|
|
2556
3414
|
}
|
|
2557
3415
|
function budgetToThinkingLevel(budgetTokens) {
|
|
2558
3416
|
if (budgetTokens <= 1024) return "low";
|
|
@@ -2560,8 +3418,7 @@ function budgetToThinkingLevel(budgetTokens) {
|
|
|
2560
3418
|
return "high";
|
|
2561
3419
|
}
|
|
2562
3420
|
function minThinkingLevel(model) {
|
|
2563
|
-
|
|
2564
|
-
return "minimal";
|
|
3421
|
+
return GEMINI_3X_MIN_THINKING_LEVEL.get(model) ?? "minimal";
|
|
2565
3422
|
}
|
|
2566
3423
|
var _warned3xEffortNone = /* @__PURE__ */ new Set();
|
|
2567
3424
|
function warnGemini3xEffortNone(model) {
|
|
@@ -2589,6 +3446,11 @@ var GeminiProvider = class {
|
|
|
2589
3446
|
/\/$/,
|
|
2590
3447
|
""
|
|
2591
3448
|
);
|
|
3449
|
+
assertSafeProviderBaseUrl(
|
|
3450
|
+
this.baseUrl,
|
|
3451
|
+
"Google provider",
|
|
3452
|
+
options.dangerouslyAllowInsecureHttp
|
|
3453
|
+
);
|
|
2592
3454
|
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
2593
3455
|
if (typeof this.apiKeySource === "string" && !this.apiKeySource) {
|
|
2594
3456
|
throw new Error("Google API key is required. Set GOOGLE_API_KEY or pass apiKey in options.");
|
|
@@ -2606,10 +3468,12 @@ var GeminiProvider = class {
|
|
|
2606
3468
|
// chat - non-streaming completion
|
|
2607
3469
|
// ---------------------------------------------------------------------------
|
|
2608
3470
|
async chat(messages, options) {
|
|
2609
|
-
const headers = this.buildHeaders(await this.resolveKey());
|
|
2610
3471
|
const body = this.buildRequestBody(messages, options);
|
|
3472
|
+
const pricingContext = this.pricingContext(this.requestModel(options), body);
|
|
3473
|
+
this.assertSafeGemini3Continuation(messages, pricingContext.model);
|
|
3474
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
2611
3475
|
const res = await fetchWithRetry(
|
|
2612
|
-
`${this.baseUrl}/models/${
|
|
3476
|
+
`${this.baseUrl}/models/${pricingContext.model}:generateContent`,
|
|
2613
3477
|
{
|
|
2614
3478
|
method: "POST",
|
|
2615
3479
|
headers,
|
|
@@ -2630,16 +3494,18 @@ var GeminiProvider = class {
|
|
|
2630
3494
|
});
|
|
2631
3495
|
}
|
|
2632
3496
|
const json = await res.json();
|
|
2633
|
-
return this.parseResponse(json,
|
|
3497
|
+
return this.parseResponse(json, pricingContext);
|
|
2634
3498
|
}
|
|
2635
3499
|
// ---------------------------------------------------------------------------
|
|
2636
3500
|
// stream - SSE streaming completion
|
|
2637
3501
|
// ---------------------------------------------------------------------------
|
|
2638
3502
|
async *stream(messages, options) {
|
|
2639
|
-
const headers = this.buildHeaders(await this.resolveKey());
|
|
2640
3503
|
const body = this.buildRequestBody(messages, options);
|
|
3504
|
+
const pricingContext = this.pricingContext(this.requestModel(options), body);
|
|
3505
|
+
this.assertSafeGemini3Continuation(messages, pricingContext.model);
|
|
3506
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
2641
3507
|
const res = await fetchWithRetry(
|
|
2642
|
-
`${this.baseUrl}/models/${
|
|
3508
|
+
`${this.baseUrl}/models/${pricingContext.model}:streamGenerateContent?alt=sse`,
|
|
2643
3509
|
{
|
|
2644
3510
|
method: "POST",
|
|
2645
3511
|
headers,
|
|
@@ -2662,7 +3528,7 @@ var GeminiProvider = class {
|
|
|
2662
3528
|
if (!res.body) {
|
|
2663
3529
|
throw new Error("Gemini stream response has no body");
|
|
2664
3530
|
}
|
|
2665
|
-
yield* this.parseSSEStream(res.body,
|
|
3531
|
+
yield* this.parseSSEStream(res.body, pricingContext);
|
|
2666
3532
|
}
|
|
2667
3533
|
// ---------------------------------------------------------------------------
|
|
2668
3534
|
// Internal: request building
|
|
@@ -2683,7 +3549,114 @@ var GeminiProvider = class {
|
|
|
2683
3549
|
}
|
|
2684
3550
|
return `Gemini API error (${status}): ${body}`;
|
|
2685
3551
|
}
|
|
3552
|
+
pricingContext(fallbackModel, body) {
|
|
3553
|
+
return {
|
|
3554
|
+
// Gemini's model is normally selected in the URL. providerOptions is an
|
|
3555
|
+
// explicit raw escape hatch, so honor a post-merge model override for both
|
|
3556
|
+
// the URL and pricing rather than pricing a different request.
|
|
3557
|
+
model: typeof body.model === "string" ? body.model : fallbackModel,
|
|
3558
|
+
serviceTier: body.serviceTier,
|
|
3559
|
+
eligibleRequest: this.baseUrl === "https://generativelanguage.googleapis.com/v1beta" && this.isTextOnlyClientRequest(body)
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3562
|
+
requestModel(options) {
|
|
3563
|
+
return typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
|
|
3564
|
+
}
|
|
3565
|
+
assertSafeGemini3Continuation(messages, model) {
|
|
3566
|
+
if (GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE.has(model)) {
|
|
3567
|
+
const mapped = this.mapMessages(messages.filter((message) => message.role !== "system"));
|
|
3568
|
+
if (mapped.at(-1)?.role === "model") {
|
|
3569
|
+
throw new Error(
|
|
3570
|
+
`${model} does not support a terminal assistant/model prefill; end the request with a user or tool message.`
|
|
3571
|
+
);
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
if (!isGemini3x(model)) return;
|
|
3575
|
+
const replayedNativeCallIds = /* @__PURE__ */ new Set();
|
|
3576
|
+
for (const message of messages) {
|
|
3577
|
+
if (message.role !== "assistant" || !Array.isArray(message.providerMetadata?.geminiParts)) {
|
|
3578
|
+
continue;
|
|
3579
|
+
}
|
|
3580
|
+
for (const part of message.providerMetadata.geminiParts) {
|
|
3581
|
+
const id = part !== null && typeof part === "object" ? part.functionCall?.id : void 0;
|
|
3582
|
+
if (!id) continue;
|
|
3583
|
+
if (replayedNativeCallIds.has(id)) {
|
|
3584
|
+
throw new Error(
|
|
3585
|
+
`${model} native functionCall ids must be globally unique across a replay.`
|
|
3586
|
+
);
|
|
3587
|
+
}
|
|
3588
|
+
replayedNativeCallIds.add(id);
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
const consumedToolMessages = /* @__PURE__ */ new Set();
|
|
3592
|
+
for (const [assistantIndex, message] of messages.entries()) {
|
|
3593
|
+
if (message.role !== "assistant" || !message.tool_calls?.length) continue;
|
|
3594
|
+
const rawParts = message.providerMetadata?.geminiParts;
|
|
3595
|
+
if (!Array.isArray(rawParts)) {
|
|
3596
|
+
throw new Error(
|
|
3597
|
+
`${model} tool continuations require providerMetadata.geminiParts with thought signatures.`
|
|
3598
|
+
);
|
|
3599
|
+
}
|
|
3600
|
+
const nativeCalls = rawParts.flatMap((part) => {
|
|
3601
|
+
const functionCall = part !== null && typeof part === "object" ? part.functionCall : void 0;
|
|
3602
|
+
return functionCall?.id ? [{ id: functionCall.id, name: functionCall.name, part }] : [];
|
|
3603
|
+
});
|
|
3604
|
+
const expectedCalls = new Map(
|
|
3605
|
+
message.tool_calls.map((call) => [call.id, call.function.name])
|
|
3606
|
+
);
|
|
3607
|
+
if (expectedCalls.size !== message.tool_calls.length || nativeCalls.length !== message.tool_calls.length || new Set(nativeCalls.map((call) => call.id)).size !== nativeCalls.length || typeof nativeCalls[0]?.part.thoughtSignature !== "string" || nativeCalls[0].part.thoughtSignature.length === 0 || nativeCalls.some((call) => expectedCalls.get(call.id) !== call.name)) {
|
|
3608
|
+
throw new Error(
|
|
3609
|
+
`${model} tool continuations require exact native functionCall id/name and a thoughtSignature on the first function call.`
|
|
3610
|
+
);
|
|
3611
|
+
}
|
|
3612
|
+
const seenResponses = /* @__PURE__ */ new Set();
|
|
3613
|
+
let toolIndex = assistantIndex + 1;
|
|
3614
|
+
while (messages[toolIndex]?.role === "tool") {
|
|
3615
|
+
const toolMessage = messages[toolIndex];
|
|
3616
|
+
const toolCallId = toolMessage.tool_call_id;
|
|
3617
|
+
if (!toolCallId || !expectedCalls.has(toolCallId) || seenResponses.has(toolCallId)) {
|
|
3618
|
+
throw new Error(
|
|
3619
|
+
`${model} functionResponse requires exactly one matching native functionCall id and name.`
|
|
3620
|
+
);
|
|
3621
|
+
}
|
|
3622
|
+
seenResponses.add(toolCallId);
|
|
3623
|
+
consumedToolMessages.add(toolIndex);
|
|
3624
|
+
toolIndex++;
|
|
3625
|
+
}
|
|
3626
|
+
if (seenResponses.size !== expectedCalls.size) {
|
|
3627
|
+
throw new Error(
|
|
3628
|
+
`${model} tool continuations require one contiguous functionResponse for every native functionCall.`
|
|
3629
|
+
);
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
for (const [index, message] of messages.entries()) {
|
|
3633
|
+
if (message.role === "tool" && !consumedToolMessages.has(index)) {
|
|
3634
|
+
throw new Error(
|
|
3635
|
+
`${model} functionResponse requires exactly one matching native functionCall id and name.`
|
|
3636
|
+
);
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
isTextOnlyClientRequest(body) {
|
|
3641
|
+
if (!Array.isArray(body.contents) || body.cachedContent !== void 0) return false;
|
|
3642
|
+
if (body.contents.some(
|
|
3643
|
+
(content) => content === null || typeof content !== "object" || !Array.isArray(content.parts) || content.parts.some((part) => this.isNonTextGeminiPart(part))
|
|
3644
|
+
)) {
|
|
3645
|
+
return false;
|
|
3646
|
+
}
|
|
3647
|
+
if (body.tools !== void 0 && (!Array.isArray(body.tools) || body.tools.some(
|
|
3648
|
+
(tool2) => tool2 === null || typeof tool2 !== "object" || !Array.isArray(tool2.functionDeclarations) || Object.keys(tool2).some((key) => key !== "functionDeclarations")
|
|
3649
|
+
))) {
|
|
3650
|
+
return false;
|
|
3651
|
+
}
|
|
3652
|
+
const modalities = body.generationConfig?.responseModalities;
|
|
3653
|
+
return modalities === void 0 || modalities === "TEXT" || Array.isArray(modalities) && modalities.length === 1 && modalities[0] === "TEXT";
|
|
3654
|
+
}
|
|
3655
|
+
isNonTextGeminiPart(part) {
|
|
3656
|
+
return part === null || typeof part !== "object" || ["inlineData", "fileData", "executableCode", "codeExecutionResult"].some((key) => key in part);
|
|
3657
|
+
}
|
|
2686
3658
|
buildRequestBody(messages, options) {
|
|
3659
|
+
const requestModel = this.requestModel(options);
|
|
2687
3660
|
const systemMessages = messages.filter((m) => m.role === "system");
|
|
2688
3661
|
const nonSystemMessages = messages.filter((m) => m.role !== "system");
|
|
2689
3662
|
const systemText = systemMessages.map((m) => m.content).join("\n\n");
|
|
@@ -2701,7 +3674,7 @@ var GeminiProvider = class {
|
|
|
2701
3674
|
];
|
|
2702
3675
|
}
|
|
2703
3676
|
const generationConfig = {};
|
|
2704
|
-
if (options.temperature !== void 0) {
|
|
3677
|
+
if (options.temperature !== void 0 && !GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE.has(requestModel)) {
|
|
2705
3678
|
generationConfig.temperature = options.temperature;
|
|
2706
3679
|
}
|
|
2707
3680
|
if (options.maxTokens !== void 0) {
|
|
@@ -2730,18 +3703,18 @@ var GeminiProvider = class {
|
|
|
2730
3703
|
hasBudgetOverride
|
|
2731
3704
|
} = resolveThinkingOptions(options);
|
|
2732
3705
|
if (thinkingDisabled) {
|
|
2733
|
-
if (isGemini3x(
|
|
3706
|
+
if (isGemini3x(requestModel)) {
|
|
2734
3707
|
if (effort === "none") {
|
|
2735
|
-
warnGemini3xEffortNone(
|
|
3708
|
+
warnGemini3xEffortNone(requestModel);
|
|
2736
3709
|
}
|
|
2737
|
-
generationConfig.thinkingConfig = { thinkingLevel: minThinkingLevel(
|
|
3710
|
+
generationConfig.thinkingConfig = { thinkingLevel: minThinkingLevel(requestModel) };
|
|
2738
3711
|
} else {
|
|
2739
3712
|
generationConfig.thinkingConfig = { thinkingBudget: 0 };
|
|
2740
3713
|
}
|
|
2741
3714
|
if (!body.generationConfig) body.generationConfig = generationConfig;
|
|
2742
3715
|
} else if (hasBudgetOverride) {
|
|
2743
3716
|
const config = {};
|
|
2744
|
-
if (isGemini3x(
|
|
3717
|
+
if (isGemini3x(requestModel)) {
|
|
2745
3718
|
config.thinkingLevel = budgetToThinkingLevel(thinkingBudget);
|
|
2746
3719
|
} else {
|
|
2747
3720
|
config.thinkingBudget = thinkingBudget;
|
|
@@ -2751,10 +3724,10 @@ var GeminiProvider = class {
|
|
|
2751
3724
|
if (!body.generationConfig) body.generationConfig = generationConfig;
|
|
2752
3725
|
} else if (activeEffort) {
|
|
2753
3726
|
const config = {};
|
|
2754
|
-
if (isGemini3x(
|
|
3727
|
+
if (isGemini3x(requestModel)) {
|
|
2755
3728
|
config.thinkingLevel = THINKING_LEVELS[activeEffort] ?? "medium";
|
|
2756
3729
|
} else {
|
|
2757
|
-
if (activeEffort === "max" &&
|
|
3730
|
+
if (activeEffort === "max" && requestModel === "gemini-2.5-pro") {
|
|
2758
3731
|
config.thinkingBudget = 32768;
|
|
2759
3732
|
} else {
|
|
2760
3733
|
config.thinkingBudget = THINKING_BUDGETS2[activeEffort] ?? 5e3;
|
|
@@ -2771,7 +3744,9 @@ var GeminiProvider = class {
|
|
|
2771
3744
|
body.toolConfig = { functionCallingConfig: this.mapToolChoice(options.toolChoice) };
|
|
2772
3745
|
}
|
|
2773
3746
|
if (options.providerOptions) {
|
|
2774
|
-
|
|
3747
|
+
const bodyOptions = { ...options.providerOptions };
|
|
3748
|
+
delete bodyOptions.model;
|
|
3749
|
+
Object.assign(body, bodyOptions);
|
|
2775
3750
|
}
|
|
2776
3751
|
return body;
|
|
2777
3752
|
}
|
|
@@ -2789,6 +3764,7 @@ var GeminiProvider = class {
|
|
|
2789
3764
|
mapMessages(messages) {
|
|
2790
3765
|
const toolCallIdToName = /* @__PURE__ */ new Map();
|
|
2791
3766
|
const geminiNativeIds = /* @__PURE__ */ new Set();
|
|
3767
|
+
const geminiNativeNames = /* @__PURE__ */ new Map();
|
|
2792
3768
|
for (const msg of messages) {
|
|
2793
3769
|
if (msg.role === "assistant" && msg.tool_calls) {
|
|
2794
3770
|
for (const tc of msg.tool_calls) {
|
|
@@ -2799,7 +3775,10 @@ var GeminiProvider = class {
|
|
|
2799
3775
|
const rawParts = msg.providerMetadata?.geminiParts;
|
|
2800
3776
|
if (rawParts) {
|
|
2801
3777
|
for (const p of rawParts) {
|
|
2802
|
-
if (p.functionCall?.id)
|
|
3778
|
+
if (p.functionCall?.id) {
|
|
3779
|
+
geminiNativeIds.add(p.functionCall.id);
|
|
3780
|
+
geminiNativeNames.set(p.functionCall.id, p.functionCall.name);
|
|
3781
|
+
}
|
|
2803
3782
|
}
|
|
2804
3783
|
}
|
|
2805
3784
|
}
|
|
@@ -2836,7 +3815,7 @@ var GeminiProvider = class {
|
|
|
2836
3815
|
}
|
|
2837
3816
|
}
|
|
2838
3817
|
} else if (msg.role === "tool") {
|
|
2839
|
-
const functionName = toolCallIdToName.get(msg.tool_call_id) ?? "unknown";
|
|
3818
|
+
const functionName = (msg.tool_call_id ? geminiNativeNames.get(msg.tool_call_id) : void 0) ?? toolCallIdToName.get(msg.tool_call_id) ?? "unknown";
|
|
2840
3819
|
const functionResponse = {
|
|
2841
3820
|
name: functionName,
|
|
2842
3821
|
response: parseGeminiFunctionResponse(msg.content)
|
|
@@ -2901,7 +3880,7 @@ var GeminiProvider = class {
|
|
|
2901
3880
|
// ---------------------------------------------------------------------------
|
|
2902
3881
|
// Internal: response parsing
|
|
2903
3882
|
// ---------------------------------------------------------------------------
|
|
2904
|
-
parseResponse(json,
|
|
3883
|
+
parseResponse(json, pricingContext) {
|
|
2905
3884
|
const candidate = json.candidates?.[0];
|
|
2906
3885
|
let content = "";
|
|
2907
3886
|
let thinkingContent = "";
|
|
@@ -2924,35 +3903,46 @@ var GeminiProvider = class {
|
|
|
2924
3903
|
}
|
|
2925
3904
|
}
|
|
2926
3905
|
}
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
3906
|
+
if (!candidate) {
|
|
3907
|
+
throw new Error("Gemini response did not include a candidate.");
|
|
3908
|
+
}
|
|
3909
|
+
if (candidate.finishReason && !["STOP", "MAX_TOKENS"].includes(candidate.finishReason)) {
|
|
3910
|
+
throw new Error(`Gemini returned non-success finish reason: ${candidate.finishReason}`);
|
|
3911
|
+
}
|
|
3912
|
+
const normalized = json.usageMetadata ? normalizeGeminiUsage(json.usageMetadata) : void 0;
|
|
3913
|
+
const effectiveModel = typeof json.modelVersion === "string" ? json.modelVersion : pricingContext.model;
|
|
3914
|
+
const cost = normalized && !normalized.hasUnmodeledBilledUsage && this.isTextOnlyGeminiResponse(candidate) && isEligibleGeminiPricing(
|
|
3915
|
+
pricingContext,
|
|
3916
|
+
isDefinitiveStandardGeminiResponseTier(json.usageMetadata?.serviceTier),
|
|
3917
|
+
json.usageMetadata?.serviceTier !== void 0 && !isDefinitiveStandardGeminiResponseTier(json.usageMetadata.serviceTier)
|
|
3918
|
+
) ? estimateGeminiCost(effectiveModel, normalized.pricingUsage) : void 0;
|
|
2937
3919
|
const rawParts = candidate?.content?.parts;
|
|
2938
3920
|
const providerMetadata = rawParts ? { geminiParts: rawParts } : void 0;
|
|
2939
3921
|
return {
|
|
2940
3922
|
content,
|
|
2941
3923
|
thinking_content: thinkingContent || void 0,
|
|
2942
3924
|
tool_calls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
2943
|
-
usage,
|
|
3925
|
+
usage: normalized?.usage,
|
|
2944
3926
|
cost,
|
|
2945
3927
|
providerMetadata
|
|
2946
3928
|
};
|
|
2947
3929
|
}
|
|
3930
|
+
isTextOnlyGeminiResponse(candidate) {
|
|
3931
|
+
return candidate?.content?.parts.every((part) => !this.isNonTextGeminiPart(part)) ?? false;
|
|
3932
|
+
}
|
|
2948
3933
|
// ---------------------------------------------------------------------------
|
|
2949
3934
|
// Internal: SSE stream parsing
|
|
2950
3935
|
// ---------------------------------------------------------------------------
|
|
2951
|
-
async *parseSSEStream(body,
|
|
3936
|
+
async *parseSSEStream(body, pricingContext) {
|
|
2952
3937
|
const reader = body.getReader();
|
|
2953
3938
|
const decoder = new TextDecoder();
|
|
2954
3939
|
let buffer = "";
|
|
2955
|
-
let
|
|
3940
|
+
let normalizedUsage;
|
|
3941
|
+
let effectiveModel = pricingContext.model;
|
|
3942
|
+
let hasDefinitiveStandardResponseTier = false;
|
|
3943
|
+
let hasInvalidResponseTier = false;
|
|
3944
|
+
let responseIsTextOnly = true;
|
|
3945
|
+
let terminalFailure;
|
|
2956
3946
|
const accumulatedParts = [];
|
|
2957
3947
|
try {
|
|
2958
3948
|
while (true) {
|
|
@@ -2972,20 +3962,21 @@ var GeminiProvider = class {
|
|
|
2972
3962
|
} catch {
|
|
2973
3963
|
continue;
|
|
2974
3964
|
}
|
|
3965
|
+
if (typeof chunk.modelVersion === "string") effectiveModel = chunk.modelVersion;
|
|
2975
3966
|
if (chunk.usageMetadata) {
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
};
|
|
3967
|
+
normalizedUsage = normalizeGeminiUsage(chunk.usageMetadata);
|
|
3968
|
+
if (chunk.usageMetadata.serviceTier !== void 0) {
|
|
3969
|
+
if (isDefinitiveStandardGeminiResponseTier(chunk.usageMetadata.serviceTier)) {
|
|
3970
|
+
hasDefinitiveStandardResponseTier = true;
|
|
3971
|
+
} else {
|
|
3972
|
+
hasInvalidResponseTier = true;
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
2985
3975
|
}
|
|
2986
3976
|
const candidate = chunk.candidates?.[0];
|
|
2987
3977
|
if (candidate?.content?.parts) {
|
|
2988
3978
|
for (const part of candidate.content.parts) {
|
|
3979
|
+
if (this.isNonTextGeminiPart(part)) responseIsTextOnly = false;
|
|
2989
3980
|
accumulatedParts.push(part);
|
|
2990
3981
|
if (part.thought && part.text) {
|
|
2991
3982
|
yield { type: "thinking_delta", content: part.text };
|
|
@@ -3001,18 +3992,23 @@ var GeminiProvider = class {
|
|
|
3001
3992
|
}
|
|
3002
3993
|
}
|
|
3003
3994
|
}
|
|
3995
|
+
if (candidate?.finishReason && !["STOP", "MAX_TOKENS"].includes(candidate.finishReason)) {
|
|
3996
|
+
terminalFailure = candidate.finishReason;
|
|
3997
|
+
}
|
|
3004
3998
|
}
|
|
3005
3999
|
}
|
|
4000
|
+
if (terminalFailure) {
|
|
4001
|
+
throw new Error(`Gemini returned non-success finish reason: ${terminalFailure}`);
|
|
4002
|
+
}
|
|
3006
4003
|
const providerMetadata = accumulatedParts.length > 0 ? { geminiParts: accumulatedParts } : void 0;
|
|
3007
4004
|
yield {
|
|
3008
4005
|
type: "done",
|
|
3009
|
-
usage,
|
|
3010
|
-
cost:
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
) : void 0,
|
|
4006
|
+
usage: normalizedUsage?.usage,
|
|
4007
|
+
cost: normalizedUsage && !normalizedUsage.hasUnmodeledBilledUsage && responseIsTextOnly && isEligibleGeminiPricing(
|
|
4008
|
+
pricingContext,
|
|
4009
|
+
hasDefinitiveStandardResponseTier,
|
|
4010
|
+
hasInvalidResponseTier
|
|
4011
|
+
) ? estimateGeminiCost(effectiveModel, normalizedUsage.pricingUsage) : void 0,
|
|
3016
4012
|
providerMetadata
|
|
3017
4013
|
};
|
|
3018
4014
|
} finally {
|
|
@@ -3029,8 +4025,6 @@ var OPENROUTER_PROFILE = {
|
|
|
3029
4025
|
envApiKey: "OPENROUTER_API_KEY",
|
|
3030
4026
|
envBaseUrl: "OPENROUTER_BASE_URL",
|
|
3031
4027
|
pricing: { kind: "from-response" },
|
|
3032
|
-
// Ask OpenRouter to report per-call cost in usage.cost.
|
|
3033
|
-
requestDefaults: { usage: { include: true } },
|
|
3034
4028
|
reasoning: {
|
|
3035
4029
|
// OpenRouter's effort vocabulary is low|medium|high; xhigh/max clamp to high.
|
|
3036
4030
|
emit: reasoningObjectEmit(
|
|
@@ -3053,7 +4047,7 @@ var AZURE_PROFILE = {
|
|
|
3053
4047
|
envApiKey: "AZURE_OPENAI_API_KEY",
|
|
3054
4048
|
envBaseUrl: "AZURE_OPENAI_BASE_URL",
|
|
3055
4049
|
authHeader: "api-key",
|
|
3056
|
-
pricing: { kind: "
|
|
4050
|
+
pricing: { kind: "unknown" },
|
|
3057
4051
|
reasoning: { emit: openaiReasoningEmit, capture: "none" },
|
|
3058
4052
|
roleFor: (role, model) => role === "system" && isOSeriesModel(model) ? "developer" : role,
|
|
3059
4053
|
maxTokensField: "max_completion_tokens",
|
|
@@ -3061,29 +4055,100 @@ var AZURE_PROFILE = {
|
|
|
3061
4055
|
};
|
|
3062
4056
|
|
|
3063
4057
|
// src/providers/profiles/xai.ts
|
|
3064
|
-
var
|
|
3065
|
-
var
|
|
4058
|
+
var GROK_45 = /* @__PURE__ */ new Set(["grok-4.5", "grok-4.5-latest", "grok-build-latest"]);
|
|
4059
|
+
var GROK_43 = /* @__PURE__ */ new Set(["grok-4.3", "grok-4.3-latest", "grok-latest"]);
|
|
4060
|
+
var GROK_420_REASONING = /* @__PURE__ */ new Set([
|
|
4061
|
+
"grok-4.20-0309-reasoning",
|
|
4062
|
+
"grok-4.20-reasoning-latest",
|
|
4063
|
+
"grok-4.20",
|
|
4064
|
+
"grok-4.20-reasoning",
|
|
4065
|
+
"grok-4.20-0309",
|
|
4066
|
+
"grok-4.20-beta-0309-reasoning",
|
|
4067
|
+
"grok-4.20-beta",
|
|
4068
|
+
"grok-4.20-beta-0309",
|
|
4069
|
+
"grok-4.20-beta-latest",
|
|
4070
|
+
"grok-4.20-beta-latest-reasoning",
|
|
4071
|
+
"grok-4.20-beta-reasoning",
|
|
4072
|
+
"grok-4.20-experimental-beta-0304-reasoning",
|
|
4073
|
+
"grok-4.20-experimental-beta-0304",
|
|
4074
|
+
"grok-4.20-experimental-beta-reasoning-latest",
|
|
4075
|
+
"grok-4.20-experimental-beta-latest",
|
|
4076
|
+
"grok-4.20-reasoning-gv2"
|
|
4077
|
+
]);
|
|
4078
|
+
var GROK_420_NON_REASONING = /* @__PURE__ */ new Set([
|
|
4079
|
+
"grok-4.20-0309-non-reasoning",
|
|
4080
|
+
"grok-4.20-non-reasoning",
|
|
4081
|
+
"grok-4.20-non-reasoning-latest",
|
|
4082
|
+
"grok-4.20-beta-non-reasoning",
|
|
4083
|
+
"grok-4.20-beta-latest-non-reasoning",
|
|
4084
|
+
"grok-4.20-experimental-beta-0304-non-reasoning",
|
|
4085
|
+
"grok-4.20-experimental-beta-non-reasoning-latest",
|
|
4086
|
+
"grok-4.20-beta-0309-non-reasoning",
|
|
4087
|
+
"grok-4.20-non-reasoning-gv2"
|
|
4088
|
+
]);
|
|
4089
|
+
var warnedIrreducible45 = /* @__PURE__ */ new Set();
|
|
4090
|
+
var warnedEffortClamp = /* @__PURE__ */ new Set();
|
|
4091
|
+
function warnIrreducible45(model) {
|
|
4092
|
+
if (warnedIrreducible45.has(model)) return;
|
|
4093
|
+
warnedIrreducible45.add(model);
|
|
4094
|
+
console.warn(
|
|
4095
|
+
`[axl] effort: 'none' on xAI ${model} maps to 'low'; Grok 4.5 cannot disable reasoning.`
|
|
4096
|
+
);
|
|
4097
|
+
}
|
|
4098
|
+
function warnEffortClamp(model, effort) {
|
|
4099
|
+
const key = `${model}:${effort}`;
|
|
4100
|
+
if (warnedEffortClamp.has(key)) return;
|
|
4101
|
+
warnedEffortClamp.add(key);
|
|
4102
|
+
console.warn(
|
|
4103
|
+
`[axl] effort: '${effort}' on xAI ${model} maps to 'high'; this model supports at most 'high'.`
|
|
4104
|
+
);
|
|
4105
|
+
}
|
|
4106
|
+
var isReasoningChatModel = (model) => GROK_45.has(model) || GROK_43.has(model) || GROK_420_REASONING.has(model);
|
|
3066
4107
|
var XAI_PROFILE = {
|
|
3067
4108
|
name: "xai",
|
|
3068
4109
|
label: "xAI",
|
|
3069
4110
|
defaultBaseUrl: "https://api.x.ai/v1",
|
|
3070
4111
|
envApiKey: "XAI_API_KEY",
|
|
3071
4112
|
envBaseUrl: "XAI_BASE_URL",
|
|
3072
|
-
pricing: { kind: "
|
|
4113
|
+
pricing: { kind: "from-response" },
|
|
3073
4114
|
reasoning: {
|
|
3074
4115
|
emit: reasoningEffortEmit((resolved, model) => {
|
|
3075
|
-
if (
|
|
3076
|
-
|
|
3077
|
-
|
|
4116
|
+
if (GROK_45.has(model)) {
|
|
4117
|
+
if (resolved.thinkingDisabled) {
|
|
4118
|
+
warnIrreducible45(model);
|
|
4119
|
+
return "low";
|
|
4120
|
+
}
|
|
4121
|
+
if (!resolved.activeEffort) return void 0;
|
|
4122
|
+
if (resolved.activeEffort === "xhigh" || resolved.activeEffort === "max") {
|
|
4123
|
+
warnEffortClamp(model, resolved.activeEffort);
|
|
4124
|
+
return "high";
|
|
4125
|
+
}
|
|
4126
|
+
return resolved.activeEffort;
|
|
4127
|
+
}
|
|
4128
|
+
if (GROK_43.has(model)) {
|
|
4129
|
+
if (resolved.thinkingDisabled) return "none";
|
|
4130
|
+
if (!resolved.activeEffort) return void 0;
|
|
4131
|
+
if (resolved.activeEffort === "xhigh" || resolved.activeEffort === "max") {
|
|
4132
|
+
warnEffortClamp(model, resolved.activeEffort);
|
|
4133
|
+
return "high";
|
|
4134
|
+
}
|
|
4135
|
+
return resolved.activeEffort;
|
|
4136
|
+
}
|
|
4137
|
+
if (GROK_420_NON_REASONING.has(model)) return void 0;
|
|
4138
|
+
return void 0;
|
|
3078
4139
|
}),
|
|
3079
4140
|
capture: "reasoning"
|
|
3080
4141
|
},
|
|
3081
4142
|
capabilities: {
|
|
3082
|
-
forbiddenParams: (model) =>
|
|
4143
|
+
forbiddenParams: (model) => isReasoningChatModel(model) ? ["stop", "presence_penalty", "frequency_penalty"] : []
|
|
3083
4144
|
}
|
|
3084
4145
|
};
|
|
3085
4146
|
|
|
3086
4147
|
// src/providers/profiles/deepseek.ts
|
|
4148
|
+
var DEEPSEEK_PRICING = {
|
|
4149
|
+
"deepseek-v4-flash": [14e-8, 28e-8, 0.02],
|
|
4150
|
+
"deepseek-v4-pro": [435e-9, 87e-8, 1 / 120]
|
|
4151
|
+
};
|
|
3087
4152
|
var isReasoner = (model) => /reasoner|v4/i.test(model);
|
|
3088
4153
|
var deepseekEmit = (_body, _resolved, model) => ({
|
|
3089
4154
|
stripTemperature: isReasoner(model)
|
|
@@ -3094,7 +4159,7 @@ var DEEPSEEK_PROFILE = {
|
|
|
3094
4159
|
defaultBaseUrl: "https://api.deepseek.com/v1",
|
|
3095
4160
|
envApiKey: "DEEPSEEK_API_KEY",
|
|
3096
4161
|
envBaseUrl: "DEEPSEEK_BASE_URL",
|
|
3097
|
-
pricing: { kind: "
|
|
4162
|
+
pricing: { kind: "table", table: DEEPSEEK_PRICING, match: "exact" },
|
|
3098
4163
|
reasoning: {
|
|
3099
4164
|
emit: deepseekEmit,
|
|
3100
4165
|
capture: "reasoning_content",
|
|
@@ -3105,27 +4170,56 @@ var DEEPSEEK_PROFILE = {
|
|
|
3105
4170
|
forbiddenParams: (model) => isReasoner(model) ? ["top_p", "presence_penalty", "frequency_penalty"] : []
|
|
3106
4171
|
}
|
|
3107
4172
|
};
|
|
4173
|
+
attachBuiltinTablePricingPolicy(DEEPSEEK_PROFILE, {
|
|
4174
|
+
canonicalBaseUrl: "https://api.deepseek.com/v1",
|
|
4175
|
+
requireCacheSplit: true
|
|
4176
|
+
});
|
|
3108
4177
|
|
|
3109
4178
|
// src/providers/profiles/mistral.ts
|
|
3110
|
-
var
|
|
4179
|
+
var MISTRAL_PRICING = {
|
|
4180
|
+
"mistral-medium-latest": [15e-7, 75e-7, 0.1],
|
|
4181
|
+
"mistral-small-latest": [15e-8, 6e-7, 0.1],
|
|
4182
|
+
"mistral-large-latest": [5e-7, 15e-7, 0.1],
|
|
4183
|
+
"devstral-medium-latest": [4e-7, 2e-6, 0.1],
|
|
4184
|
+
"devstral-small-latest": [1e-7, 3e-7, 0.1],
|
|
4185
|
+
"codestral-latest": [3e-7, 9e-7, 0.1],
|
|
4186
|
+
"magistral-medium-latest": [2e-6, 5e-6, 0.1],
|
|
4187
|
+
"magistral-small-latest": [5e-7, 15e-7, 0.1],
|
|
4188
|
+
"ministral-3b-latest": [1e-7, 1e-7, 0.1],
|
|
4189
|
+
"ministral-8b-latest": [15e-8, 15e-8, 0.1],
|
|
4190
|
+
"ministral-14b-latest": [2e-7, 2e-7, 0.1],
|
|
4191
|
+
"open-mistral-nemo": [15e-8, 15e-8, 0.1],
|
|
4192
|
+
"open-mixtral-8x7b": [7e-7, 7e-7, 0.1],
|
|
4193
|
+
"open-mixtral-8x22b": [2e-6, 6e-6, 0.1]
|
|
4194
|
+
};
|
|
4195
|
+
var acceptsReasoningEffort = (model) => /^mistral-(small|medium)/i.test(model);
|
|
3111
4196
|
var MISTRAL_PROFILE = {
|
|
3112
4197
|
name: "mistral",
|
|
3113
4198
|
label: "Mistral",
|
|
3114
4199
|
defaultBaseUrl: "https://api.mistral.ai/v1",
|
|
3115
4200
|
envApiKey: "MISTRAL_API_KEY",
|
|
3116
4201
|
envBaseUrl: "MISTRAL_BASE_URL",
|
|
3117
|
-
pricing: { kind: "
|
|
4202
|
+
pricing: { kind: "table", table: MISTRAL_PRICING, match: "exact" },
|
|
3118
4203
|
reasoning: {
|
|
3119
4204
|
emit: reasoningEffortEmit((resolved, model) => {
|
|
3120
|
-
if (!
|
|
4205
|
+
if (!acceptsReasoningEffort(model)) return void 0;
|
|
3121
4206
|
if (resolved.thinkingDisabled || !resolved.activeEffort) return void 0;
|
|
3122
4207
|
return "high";
|
|
3123
4208
|
}),
|
|
3124
4209
|
capture: "reasoning_content"
|
|
3125
4210
|
}
|
|
3126
4211
|
};
|
|
4212
|
+
attachBuiltinTablePricingPolicy(MISTRAL_PROFILE, {
|
|
4213
|
+
canonicalBaseUrl: "https://api.mistral.ai/v1"
|
|
4214
|
+
});
|
|
3127
4215
|
|
|
3128
4216
|
// src/providers/profiles/groq.ts
|
|
4217
|
+
var GROQ_PRICING = {
|
|
4218
|
+
"llama-3.1-8b-instant": [5e-8, 8e-8, 1],
|
|
4219
|
+
"llama-3.3-70b-versatile": [59e-8, 79e-8, 1],
|
|
4220
|
+
"openai/gpt-oss-120b": [15e-8, 6e-7, 0.5],
|
|
4221
|
+
"openai/gpt-oss-20b": [75e-9, 3e-7, 0.5]
|
|
4222
|
+
};
|
|
3129
4223
|
var isGptOss = (model) => /gpt-oss/i.test(model);
|
|
3130
4224
|
var GROQ_PROFILE = {
|
|
3131
4225
|
name: "groq",
|
|
@@ -3133,7 +4227,11 @@ var GROQ_PROFILE = {
|
|
|
3133
4227
|
defaultBaseUrl: "https://api.groq.com/openai/v1",
|
|
3134
4228
|
envApiKey: "GROQ_API_KEY",
|
|
3135
4229
|
envBaseUrl: "GROQ_BASE_URL",
|
|
3136
|
-
pricing: {
|
|
4230
|
+
pricing: {
|
|
4231
|
+
kind: "table",
|
|
4232
|
+
table: GROQ_PRICING,
|
|
4233
|
+
match: "exact"
|
|
4234
|
+
},
|
|
3137
4235
|
reasoning: {
|
|
3138
4236
|
emit: reasoningEffortEmit((resolved, model) => {
|
|
3139
4237
|
if (!isGptOss(model) || resolved.thinkingDisabled || !resolved.activeEffort) {
|
|
@@ -3154,6 +4252,21 @@ var GROQ_PROFILE = {
|
|
|
3154
4252
|
supportsJsonSchema: isGptOss
|
|
3155
4253
|
}
|
|
3156
4254
|
};
|
|
4255
|
+
attachBuiltinTablePricingPolicy(GROQ_PROFILE, {
|
|
4256
|
+
canonicalBaseUrl: "https://api.groq.com/openai/v1",
|
|
4257
|
+
supportedServiceTiers: ["on_demand", "flex"],
|
|
4258
|
+
functionToolsOnly: true,
|
|
4259
|
+
chargedRequestModifiers: [
|
|
4260
|
+
"browser_search",
|
|
4261
|
+
"code_interpreter",
|
|
4262
|
+
"web_search",
|
|
4263
|
+
"visit_website",
|
|
4264
|
+
"wolfram_alpha",
|
|
4265
|
+
"remote_mcp",
|
|
4266
|
+
"mcp",
|
|
4267
|
+
"compound_custom"
|
|
4268
|
+
]
|
|
4269
|
+
});
|
|
3157
4270
|
|
|
3158
4271
|
// src/providers/profiles/bedrock.ts
|
|
3159
4272
|
var isGptOss2 = (model) => /gpt-oss/i.test(model);
|
|
@@ -3225,6 +4338,7 @@ var builtinFactories = {
|
|
|
3225
4338
|
return new OpenAIProvider({
|
|
3226
4339
|
apiKey: opts.apiKey,
|
|
3227
4340
|
baseUrl: opts.baseUrl,
|
|
4341
|
+
dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
|
|
3228
4342
|
rateLimit: opts.rateLimit
|
|
3229
4343
|
});
|
|
3230
4344
|
},
|
|
@@ -3237,6 +4351,7 @@ var builtinFactories = {
|
|
|
3237
4351
|
return new OpenAIResponsesProvider({
|
|
3238
4352
|
apiKey: opts.apiKey,
|
|
3239
4353
|
baseUrl: opts.baseUrl,
|
|
4354
|
+
dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
|
|
3240
4355
|
rateLimit: opts.rateLimit
|
|
3241
4356
|
});
|
|
3242
4357
|
},
|
|
@@ -3245,6 +4360,7 @@ var builtinFactories = {
|
|
|
3245
4360
|
return new AnthropicProvider({
|
|
3246
4361
|
apiKey: opts.apiKey,
|
|
3247
4362
|
baseUrl: opts.baseUrl,
|
|
4363
|
+
dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
|
|
3248
4364
|
rateLimit: opts.rateLimit
|
|
3249
4365
|
});
|
|
3250
4366
|
},
|
|
@@ -3253,6 +4369,7 @@ var builtinFactories = {
|
|
|
3253
4369
|
return new GeminiProvider({
|
|
3254
4370
|
apiKey: opts.apiKey,
|
|
3255
4371
|
baseUrl: opts.baseUrl,
|
|
4372
|
+
dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
|
|
3256
4373
|
rateLimit: opts.rateLimit
|
|
3257
4374
|
});
|
|
3258
4375
|
}
|
|
@@ -3264,6 +4381,7 @@ function presetFactory(profile) {
|
|
|
3264
4381
|
profile,
|
|
3265
4382
|
apiKey: opts.apiKey,
|
|
3266
4383
|
baseUrl: opts.baseUrl,
|
|
4384
|
+
dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
|
|
3267
4385
|
authHeader: opts.authHeader,
|
|
3268
4386
|
rateLimit: opts.rateLimit
|
|
3269
4387
|
});
|
|
@@ -3968,7 +5086,7 @@ var COST_LEAF_SET = new Set(COST_BEARING_LEAF_TYPES);
|
|
|
3968
5086
|
function hasPositiveTokens(event) {
|
|
3969
5087
|
const t = event.tokens;
|
|
3970
5088
|
if (!t) return false;
|
|
3971
|
-
return (t.input ?? 0) + (t.output ?? 0) + (t.reasoning ?? 0) > 0;
|
|
5089
|
+
return (t.input ?? 0) + (t.output ?? 0) + (t.reasoning ?? 0) + (t.cached ?? 0) + (t.cacheWrite ?? 0) > 0;
|
|
3972
5090
|
}
|
|
3973
5091
|
function isUsableCost(cost) {
|
|
3974
5092
|
return typeof cost === "number" && Number.isFinite(cost) && cost >= 0;
|
|
@@ -6581,6 +7699,11 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
6581
7699
|
"axl.agent.cached_tokens",
|
|
6582
7700
|
usageCapture.value.cached_tokens
|
|
6583
7701
|
);
|
|
7702
|
+
if (usageCapture.value.cache_write_tokens)
|
|
7703
|
+
span.setAttribute(
|
|
7704
|
+
"axl.agent.cache_write_tokens",
|
|
7705
|
+
usageCapture.value.cache_write_tokens
|
|
7706
|
+
);
|
|
6584
7707
|
}
|
|
6585
7708
|
return r;
|
|
6586
7709
|
}
|
|
@@ -7026,7 +8149,9 @@ ${appendedSchemaText}`;
|
|
|
7026
8149
|
tokens: response.usage ? {
|
|
7027
8150
|
input: response.usage.prompt_tokens,
|
|
7028
8151
|
output: response.usage.completion_tokens,
|
|
7029
|
-
reasoning: response.usage.reasoning_tokens
|
|
8152
|
+
reasoning: response.usage.reasoning_tokens,
|
|
8153
|
+
cached: response.usage.cached_tokens,
|
|
8154
|
+
cacheWrite: response.usage.cache_write_tokens
|
|
7030
8155
|
} : void 0,
|
|
7031
8156
|
duration: Date.now() - turnStart,
|
|
7032
8157
|
data: {
|
|
@@ -10267,6 +11392,14 @@ var HttpMcpClient = class {
|
|
|
10267
11392
|
this.config = config;
|
|
10268
11393
|
this.name = config.name;
|
|
10269
11394
|
this.baseUrl = (config.uri ?? "").replace(/\/$/, "");
|
|
11395
|
+
if (this.baseUrl) {
|
|
11396
|
+
assertSafeHttpEndpoint(
|
|
11397
|
+
this.baseUrl,
|
|
11398
|
+
`MCP server "${this.name}"`,
|
|
11399
|
+
config.dangerouslyAllowInsecureHttp,
|
|
11400
|
+
"tool arguments and results"
|
|
11401
|
+
);
|
|
11402
|
+
}
|
|
10270
11403
|
}
|
|
10271
11404
|
name;
|
|
10272
11405
|
tools = [];
|
|
@@ -10296,7 +11429,9 @@ var HttpMcpClient = class {
|
|
|
10296
11429
|
method: "POST",
|
|
10297
11430
|
headers: { "Content-Type": "application/json" },
|
|
10298
11431
|
body: JSON.stringify(request),
|
|
10299
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
11432
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
11433
|
+
// Tool calls can be non-idempotent: never forward or retry their body.
|
|
11434
|
+
redirect: "manual"
|
|
10300
11435
|
});
|
|
10301
11436
|
if (!res.ok) {
|
|
10302
11437
|
throw new Error(`MCP HTTP error (${res.status}): ${await res.text()}`);
|
|
@@ -13088,6 +14223,11 @@ var OpenAIEmbedder = class {
|
|
|
13088
14223
|
}
|
|
13089
14224
|
this.model = options.model ?? "text-embedding-3-small";
|
|
13090
14225
|
this.baseUrl = options.baseUrl ?? "https://api.openai.com";
|
|
14226
|
+
assertSafeProviderBaseUrl(
|
|
14227
|
+
this.baseUrl,
|
|
14228
|
+
"OpenAI embedder",
|
|
14229
|
+
options.dangerouslyAllowInsecureHttp
|
|
14230
|
+
);
|
|
13091
14231
|
this.dimensions = options.dimensions ?? 1536;
|
|
13092
14232
|
}
|
|
13093
14233
|
async embed(texts, signal) {
|