@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/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: options.model,
1116
- messages: messages.map((m) => this.formatMessage(m, options.model)),
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, options.model) ?? {};
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, options.model, false)) {
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
- options.model,
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, options.model, []);
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
- cached_tokens: raw.prompt_tokens_details?.cached_tokens
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
- computeCost(model, usage, reportedCost) {
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?.cost);
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(model, usage, usageData?.cost),
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
- // gpt-4o era — cache reads at 50% of input rate
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: [1e-5, 4e-5, 0.25],
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 priceFromTable(OPENAI_PRICING, model, promptTokens, completionTokens, cachedTokens);
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 { thinkingBudget, thinkingDisabled, activeEffort, hasBudgetOverride } = resolved;
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 oSeries = isOSeriesModel(options.model);
1640
- const reasoningCapable = supportsReasoningEffort(options.model);
1641
- const { thinkingBudget, includeThoughts, thinkingDisabled, activeEffort, hasBudgetOverride } = resolveThinkingOptions(options);
1642
- let wireEffort;
1643
- if (reasoningCapable) {
1644
- if (hasBudgetOverride) {
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: options.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 ? estimateOpenAICost(model, usage.prompt_tokens, usage.completion_tokens, usage.cached_tokens) : void 0;
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 ? estimateOpenAICost(
1907
- model,
1908
- usage.prompt_tokens,
1909
- usage.completion_tokens,
1910
- usage.cached_tokens
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 ANTHROPIC_PRICING = {
1946
- "claude-opus-4-8": [5e-6, 25e-6],
1947
- "claude-opus-4-7": [5e-6, 25e-6],
1948
- "claude-opus-4-6": [5e-6, 25e-6],
1949
- "claude-sonnet-4-6": [3e-6, 15e-6],
1950
- "claude-opus-4-5": [5e-6, 25e-6],
1951
- "claude-opus-4-1": [15e-6, 75e-6],
1952
- "claude-sonnet-4-5": [3e-6, 15e-6],
1953
- "claude-haiku-4-5": [1e-6, 5e-6],
1954
- "claude-sonnet-4": [3e-6, 15e-6],
1955
- "claude-opus-4": [15e-6, 75e-6],
1956
- "claude-3-7-sonnet": [3e-6, 15e-6],
1957
- "claude-3-5-sonnet": [3e-6, 15e-6],
1958
- "claude-3-5-haiku": [8e-7, 4e-6],
1959
- "claude-3-opus": [15e-6, 75e-6],
1960
- "claude-3-sonnet": [3e-6, 15e-6],
1961
- "claude-3-haiku": [25e-8, 125e-8]
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 ANTHROPIC_PRICING_KEYS_BY_LENGTH = Object.keys(ANTHROPIC_PRICING).sort(
1964
- (a, b) => b.length - a.length
1965
- );
1966
- function estimateAnthropicCost(model, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens) {
1967
- let pricing = ANTHROPIC_PRICING[model];
1968
- if (!pricing) {
1969
- for (const key of ANTHROPIC_PRICING_KEYS_BY_LENGTH) {
1970
- if (model.startsWith(key)) {
1971
- pricing = ANTHROPIC_PRICING[key];
1972
- break;
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
- if (!pricing) return void 0;
1977
- const [inputRate, outputRate] = pricing;
1978
- const cacheRead = cacheReadTokens ?? 0;
1979
- const cacheWrite = cacheWriteTokens ?? 0;
1980
- const inputCost = (inputTokens - cacheRead - cacheWrite) * inputRate + cacheRead * inputRate * 0.1 + cacheWrite * inputRate * 1.25;
1981
- return inputCost + outputTokens * outputRate;
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 supportsAdaptiveThinking(model) {
1992
- return model.startsWith("claude-opus-4-8") || model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6") || model.startsWith("claude-sonnet-4-6");
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 supportsMaxEffort(model) {
1995
- return model.startsWith("claude-opus-4-8") || model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6");
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 supportsXhigh2(model) {
1998
- return model.startsWith("claude-opus-4-8") || model.startsWith("claude-opus-4-7");
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 supportsEffort(model) {
2001
- return model.startsWith("claude-opus-4-8") || model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6") || model.startsWith("claude-sonnet-4-6") || model.startsWith("claude-opus-4-5");
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 clampAnthropicEffort(model, effort) {
2004
- if (effort === "max" && !supportsMaxEffort(model)) return "high";
2005
- if (effort === "xhigh" && !supportsXhigh2(model)) return "high";
2006
- return effort;
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, options.model);
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, options.model);
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: options.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 { thinkingBudget, thinkingDisabled, activeEffort, hasBudgetOverride } = resolveThinkingOptions(options);
2144
- const resolvedEffort = activeEffort ? clampAnthropicEffort(options.model, activeEffort) : void 0;
2145
- if (hasBudgetOverride) {
2146
- body.thinking = { type: "enabled", budget_tokens: thinkingBudget };
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 < thinkingBudget + 1024) {
2149
- body.max_tokens = thinkingBudget + 1024;
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 && !body.thinking) {
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, model) {
3025
+ parseResponse(json, pricingContext) {
2292
3026
  let content = "";
2293
3027
  let thinkingContent = "";
2294
3028
  const toolCalls = [];
2295
- for (const block of json.content) {
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 cacheRead = json.usage?.cache_read_input_tokens ?? 0;
2312
- const cacheWrite = json.usage?.cache_creation_input_tokens ?? 0;
2313
- const totalInput = (json.usage?.input_tokens ?? 0) + cacheRead + cacheWrite;
2314
- const usage = json.usage ? {
2315
- prompt_tokens: totalInput,
2316
- completion_tokens: json.usage.output_tokens,
2317
- total_tokens: totalInput + json.usage.output_tokens,
2318
- cached_tokens: cacheRead > 0 ? cacheRead : void 0
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, model) {
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 cacheWrite = 0;
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
- const cacheRead = event.message.usage.cache_read_input_tokens ?? 0;
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
- const outputTokens = event.usage.output_tokens ?? 0;
2410
- if (usage) {
2411
- usage.completion_tokens = outputTokens;
2412
- usage.total_tokens = usage.prompt_tokens + outputTokens;
2413
- } else {
2414
- usage = {
2415
- prompt_tokens: 0,
2416
- completion_tokens: outputTokens,
2417
- total_tokens: outputTokens
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
- if (usage) {
2425
- usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
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
- yield {
2444
- type: "done",
2445
- usage,
2446
- cost: usage ? estimateAnthropicCost(
2447
- model,
2448
- usage.prompt_tokens,
2449
- usage.completion_tokens,
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": [125e-8, 1e-5],
2507
- "gemini-2.5-flash": [3e-7, 25e-7],
2508
- "gemini-2.5-flash-lite": [1e-7, 4e-7],
2509
- "gemini-2.0-flash": [1e-7, 4e-7],
2510
- "gemini-2.0-flash-lite": [1e-7, 4e-7],
2511
- "gemini-3-pro-preview": [2e-6, 12e-6],
2512
- "gemini-3-flash-preview": [5e-7, 3e-6],
2513
- "gemini-3.1-pro-preview": [2e-6, 12e-6],
2514
- "gemini-3.1-flash-lite": [25e-8, 15e-7],
2515
- "gemini-3.1-flash-lite-preview": [25e-8, 15e-7],
2516
- "gemini-3.5-flash": [15e-7, 9e-6]
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
- var GEMINI_PRICING_KEYS_BY_LENGTH = Object.keys(GEMINI_PRICING).sort(
2519
- (a, b) => b.length - a.length
2520
- );
2521
- function estimateGeminiCost(model, inputTokens, outputTokens, cachedTokens) {
2522
- let pricing = GEMINI_PRICING[model];
2523
- if (!pricing) {
2524
- for (const key of GEMINI_PRICING_KEYS_BY_LENGTH) {
2525
- if (model.startsWith(key)) {
2526
- pricing = GEMINI_PRICING[key];
2527
- break;
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
- if (!pricing) return void 0;
2532
- const [inputRate, outputRate] = pricing;
2533
- const cached = cachedTokens ?? 0;
2534
- const inputCost = (inputTokens - cached) * inputRate + cached * inputRate * 0.1;
2535
- return inputCost + outputTokens * outputRate;
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 /^gemini-3[.-]/.test(model);
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
- if (model.startsWith("gemini-3.1-pro")) return "low";
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/${options.model}:generateContent`,
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, options.model);
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/${options.model}:streamGenerateContent?alt=sse`,
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, options.model);
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(options.model)) {
3706
+ if (isGemini3x(requestModel)) {
2734
3707
  if (effort === "none") {
2735
- warnGemini3xEffortNone(options.model);
3708
+ warnGemini3xEffortNone(requestModel);
2736
3709
  }
2737
- generationConfig.thinkingConfig = { thinkingLevel: minThinkingLevel(options.model) };
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(options.model)) {
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(options.model)) {
3727
+ if (isGemini3x(requestModel)) {
2755
3728
  config.thinkingLevel = THINKING_LEVELS[activeEffort] ?? "medium";
2756
3729
  } else {
2757
- if (activeEffort === "max" && options.model.startsWith("gemini-2.5-pro")) {
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
- Object.assign(body, options.providerOptions);
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) geminiNativeIds.add(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, model) {
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
- const cachedTokens = json.usageMetadata?.cachedContentTokenCount;
2928
- const reasoningTokens = json.usageMetadata?.thoughtsTokenCount;
2929
- const usage = json.usageMetadata ? {
2930
- prompt_tokens: json.usageMetadata.promptTokenCount ?? 0,
2931
- completion_tokens: json.usageMetadata.candidatesTokenCount ?? 0,
2932
- total_tokens: json.usageMetadata.totalTokenCount ?? 0,
2933
- cached_tokens: cachedTokens && cachedTokens > 0 ? cachedTokens : void 0,
2934
- reasoning_tokens: reasoningTokens && reasoningTokens > 0 ? reasoningTokens : void 0
2935
- } : void 0;
2936
- const cost = usage ? estimateGeminiCost(model, usage.prompt_tokens, usage.completion_tokens, usage.cached_tokens) : void 0;
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, model) {
3936
+ async *parseSSEStream(body, pricingContext) {
2952
3937
  const reader = body.getReader();
2953
3938
  const decoder = new TextDecoder();
2954
3939
  let buffer = "";
2955
- let usage;
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
- const cached = chunk.usageMetadata.cachedContentTokenCount;
2977
- const reasoning = chunk.usageMetadata.thoughtsTokenCount;
2978
- usage = {
2979
- prompt_tokens: chunk.usageMetadata.promptTokenCount ?? 0,
2980
- completion_tokens: chunk.usageMetadata.candidatesTokenCount ?? 0,
2981
- total_tokens: chunk.usageMetadata.totalTokenCount ?? 0,
2982
- cached_tokens: cached && cached > 0 ? cached : void 0,
2983
- reasoning_tokens: reasoning && reasoning > 0 ? reasoning : void 0
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: usage ? estimateGeminiCost(
3011
- model,
3012
- usage.prompt_tokens,
3013
- usage.completion_tokens,
3014
- usage.cached_tokens
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: "table", table: OPENAI_PRICING },
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 acceptsReasoningEffort = (model) => model.startsWith("grok-3-mini");
3065
- var isReasoningModel = (model) => /^grok-(3-mini|4)/.test(model);
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: "unknown" },
4113
+ pricing: { kind: "from-response" },
3073
4114
  reasoning: {
3074
4115
  emit: reasoningEffortEmit((resolved, model) => {
3075
- if (!acceptsReasoningEffort(model)) return void 0;
3076
- if (resolved.thinkingDisabled || !resolved.activeEffort) return void 0;
3077
- return resolved.activeEffort === "low" ? "low" : "high";
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) => isReasoningModel(model) ? ["stop", "presence_penalty", "frequency_penalty"] : []
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: "unknown" },
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 acceptsReasoningEffort2 = (model) => /^mistral-(small|medium)/i.test(model);
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: "unknown" },
4202
+ pricing: { kind: "table", table: MISTRAL_PRICING, match: "exact" },
3118
4203
  reasoning: {
3119
4204
  emit: reasoningEffortEmit((resolved, model) => {
3120
- if (!acceptsReasoningEffort2(model)) return void 0;
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: { kind: "unknown" },
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) {