@axlsdk/axl 0.20.0 → 0.20.1

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
@@ -875,6 +875,42 @@ 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
+
878
914
  // src/providers/openai-compatible.ts
879
915
  function resolvePerModel(value, model, fallback) {
880
916
  if (value === void 0) return fallback;
@@ -889,9 +925,13 @@ function sortedKeys(table) {
889
925
  }
890
926
  return keys;
891
927
  }
892
- function priceFromTable(table, model, promptTokens, completionTokens, cachedTokens) {
928
+ function priceFromTable(table, model, promptTokens, completionTokens, cachedTokens, match = "prefix") {
929
+ const cached = cachedTokens ?? 0;
930
+ if (!Number.isSafeInteger(promptTokens) || !Number.isSafeInteger(completionTokens) || !Number.isSafeInteger(cached) || promptTokens < 0 || completionTokens < 0 || cached < 0 || cached > promptTokens) {
931
+ return void 0;
932
+ }
893
933
  let pricing = table[model];
894
- if (!pricing) {
934
+ if (!pricing && match === "prefix") {
895
935
  for (const key of sortedKeys(table)) {
896
936
  if (model.startsWith(key)) {
897
937
  pricing = table[key];
@@ -901,7 +941,6 @@ function priceFromTable(table, model, promptTokens, completionTokens, cachedToke
901
941
  }
902
942
  if (!pricing) return void 0;
903
943
  const [inputRate, outputRate, cacheMultiplier] = pricing;
904
- const cached = cachedTokens ?? 0;
905
944
  const inputCost = (promptTokens - cached) * inputRate + cached * inputRate * cacheMultiplier;
906
945
  return inputCost + completionTokens * outputRate;
907
946
  }
@@ -1062,7 +1101,7 @@ var OpenAICompatibleProvider = class {
1062
1101
  });
1063
1102
  }
1064
1103
  const json = await res.json();
1065
- return this.parseResponse(json, options.model);
1104
+ return this.parseResponse(json, this.requestModel(body, options.model), body);
1066
1105
  }
1067
1106
  async *stream(messages, options) {
1068
1107
  const headers = this.buildHeaders(await this.resolveKey());
@@ -1090,7 +1129,7 @@ var OpenAICompatibleProvider = class {
1090
1129
  if (!res.body) {
1091
1130
  throw new Error(`${this.profile.label ?? this.profile.name} stream response has no body`);
1092
1131
  }
1093
- yield* this.parseSSEStream(res.body, options.model);
1132
+ yield* this.parseSSEStream(res.body, this.requestModel(body, options.model), body);
1094
1133
  }
1095
1134
  // ---------------------------------------------------------------------------
1096
1135
  // Request building
@@ -1111,12 +1150,13 @@ var OpenAICompatibleProvider = class {
1111
1150
  buildRequestBody(messages, options, stream) {
1112
1151
  const profile = this.profile;
1113
1152
  const resolved = resolveThinkingOptions(options);
1153
+ const effectiveModel = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
1114
1154
  const body = {
1115
- model: options.model,
1116
- messages: messages.map((m) => this.formatMessage(m, options.model)),
1155
+ model: effectiveModel,
1156
+ messages: messages.map((m) => this.formatMessage(m, effectiveModel)),
1117
1157
  stream
1118
1158
  };
1119
- const emitResult = profile.reasoning.emit(body, resolved, options.model) ?? {};
1159
+ const emitResult = profile.reasoning.emit(body, resolved, effectiveModel) ?? {};
1120
1160
  const stripTemperature = emitResult.stripTemperature ?? false;
1121
1161
  if (options.temperature !== void 0 && !stripTemperature) {
1122
1162
  body.temperature = options.temperature;
@@ -1127,7 +1167,7 @@ var OpenAICompatibleProvider = class {
1127
1167
  if (options.stop) body.stop = options.stop;
1128
1168
  if (options.tools && options.tools.length > 0) {
1129
1169
  body.tools = options.tools;
1130
- if (resolvePerModel(profile.parallelToolCalls, options.model, false)) {
1170
+ if (resolvePerModel(profile.parallelToolCalls, effectiveModel, false)) {
1131
1171
  body.parallel_tool_calls = true;
1132
1172
  }
1133
1173
  }
@@ -1135,7 +1175,7 @@ var OpenAICompatibleProvider = class {
1135
1175
  if (options.responseFormat) {
1136
1176
  const supportsSchema = resolvePerModel(
1137
1177
  profile.capabilities?.supportsJsonSchema,
1138
- options.model,
1178
+ effectiveModel,
1139
1179
  true
1140
1180
  );
1141
1181
  body.response_format = options.responseFormat.type === "json_schema" && !supportsSchema ? { type: "json_object" } : options.responseFormat;
@@ -1146,7 +1186,7 @@ var OpenAICompatibleProvider = class {
1146
1186
  if (profile.requestDefaults) Object.assign(body, profile.requestDefaults);
1147
1187
  const userKeys = options.providerOptions ? Object.keys(options.providerOptions) : [];
1148
1188
  if (options.providerOptions) Object.assign(body, options.providerOptions);
1149
- const forbidden = resolvePerModel(profile.capabilities?.forbiddenParams, options.model, []);
1189
+ const forbidden = resolvePerModel(profile.capabilities?.forbiddenParams, effectiveModel, []);
1150
1190
  for (const key of forbidden) {
1151
1191
  if (!userKeys.includes(key)) delete body[key];
1152
1192
  }
@@ -1181,6 +1221,9 @@ var OpenAICompatibleProvider = class {
1181
1221
  }
1182
1222
  return `${label} API error (${status}): ${body}`;
1183
1223
  }
1224
+ requestModel(body, fallback) {
1225
+ return typeof body.model === "string" ? body.model : fallback;
1226
+ }
1184
1227
  toUsage(raw) {
1185
1228
  if (!raw) return void 0;
1186
1229
  return {
@@ -1188,10 +1231,27 @@ var OpenAICompatibleProvider = class {
1188
1231
  completion_tokens: raw.completion_tokens,
1189
1232
  total_tokens: raw.total_tokens,
1190
1233
  reasoning_tokens: raw.completion_tokens_details?.reasoning_tokens,
1191
- cached_tokens: raw.prompt_tokens_details?.cached_tokens
1234
+ // DeepSeek reports the cache split at the top level rather than under
1235
+ // prompt_tokens_details. Preserve the normalized hit count for the
1236
+ // generic table estimator; its split is validated before pricing.
1237
+ cached_tokens: raw.prompt_cache_hit_tokens ?? raw.prompt_tokens_details?.cached_tokens,
1238
+ cache_write_tokens: raw.prompt_tokens_details?.cache_write_tokens
1192
1239
  };
1193
1240
  }
1194
- computeCost(model, usage, reportedCost) {
1241
+ reportedCost(raw) {
1242
+ if (!raw) return void 0;
1243
+ if (this.profile.name === "xai") {
1244
+ const ticks = raw.cost_in_usd_ticks;
1245
+ return typeof ticks === "number" && Number.isSafeInteger(ticks) && ticks >= 0 ? ticks / 1e10 : void 0;
1246
+ }
1247
+ return typeof raw.cost === "number" && Number.isFinite(raw.cost) && raw.cost >= 0 ? raw.cost : void 0;
1248
+ }
1249
+ /**
1250
+ * The generic profile path stays flat and public. Native subclasses may
1251
+ * override this protected seam to use a provider-internal estimator without
1252
+ * widening `PricingSource` or `ProviderProfile`.
1253
+ */
1254
+ computeCost(model, usage, reportedCost, context) {
1195
1255
  const p = this.profile.pricing;
1196
1256
  if (p.kind === "zero") return 0;
1197
1257
  if (!usage) return void 0;
@@ -1199,14 +1259,22 @@ var OpenAICompatibleProvider = class {
1199
1259
  case "unknown":
1200
1260
  return void 0;
1201
1261
  case "from-response":
1202
- return typeof reportedCost === "number" && Number.isFinite(reportedCost) ? reportedCost : void 0;
1262
+ return typeof reportedCost === "number" && Number.isFinite(reportedCost) && reportedCost >= 0 ? reportedCost : void 0;
1203
1263
  case "table": {
1264
+ if (!isBuiltinTablePricingEligible(this.profile, {
1265
+ baseUrl: this.baseUrl,
1266
+ request: context?.request,
1267
+ response: context?.response
1268
+ })) {
1269
+ return void 0;
1270
+ }
1204
1271
  const c = priceFromTable(
1205
1272
  p.table,
1206
1273
  model,
1207
1274
  usage.prompt_tokens,
1208
1275
  usage.completion_tokens,
1209
- usage.cached_tokens
1276
+ usage.cached_tokens,
1277
+ p.match
1210
1278
  );
1211
1279
  return c !== void 0 && Number.isFinite(c) ? c : void 0;
1212
1280
  }
@@ -1216,7 +1284,7 @@ var OpenAICompatibleProvider = class {
1216
1284
  }
1217
1285
  }
1218
1286
  }
1219
- parseResponse(json, model) {
1287
+ parseResponse(json, model, request) {
1220
1288
  const message = json.choices?.[0]?.message ?? {};
1221
1289
  let content = normalizeMessageContent(message.content);
1222
1290
  let thinking;
@@ -1262,7 +1330,10 @@ var OpenAICompatibleProvider = class {
1262
1330
  }
1263
1331
  }
1264
1332
  const usage = this.toUsage(json.usage);
1265
- const cost = this.computeCost(model, usage, json.usage?.cost);
1333
+ const cost = this.computeCost(json.model ?? model, usage, this.reportedCost(json.usage), {
1334
+ request,
1335
+ response: json
1336
+ });
1266
1337
  return {
1267
1338
  content,
1268
1339
  thinking_content: thinking || void 0,
@@ -1285,11 +1356,12 @@ var OpenAICompatibleProvider = class {
1285
1356
  // ---------------------------------------------------------------------------
1286
1357
  // SSE streaming
1287
1358
  // ---------------------------------------------------------------------------
1288
- async *parseSSEStream(body, model) {
1359
+ async *parseSSEStream(body, model, request) {
1289
1360
  const reader = body.getReader();
1290
1361
  const decoder = new TextDecoder();
1291
1362
  let buffer = "";
1292
1363
  let usageData;
1364
+ let pricingResponse;
1293
1365
  const capture = this.profile.reasoning.capture;
1294
1366
  const thinkScanner = capture === "think_tags" ? new ThinkTagScanner() : void 0;
1295
1367
  let reasoningText = "";
@@ -1330,7 +1402,15 @@ var OpenAICompatibleProvider = class {
1330
1402
  return {
1331
1403
  type: "done",
1332
1404
  usage,
1333
- cost: this.computeCost(model, usage, usageData?.cost),
1405
+ cost: this.computeCost(
1406
+ pricingResponse?.model ?? model,
1407
+ usage,
1408
+ this.reportedCost(usageData),
1409
+ {
1410
+ request,
1411
+ response: pricingResponse
1412
+ }
1413
+ ),
1334
1414
  providerMetadata
1335
1415
  };
1336
1416
  };
@@ -1371,8 +1451,12 @@ var OpenAICompatibleProvider = class {
1371
1451
  body: JSON.stringify(parsed.error)
1372
1452
  });
1373
1453
  }
1454
+ if (parsed.model !== void 0 || parsed.service_tier !== void 0 || parsed.serviceTier !== void 0) {
1455
+ pricingResponse = { ...pricingResponse, ...parsed };
1456
+ }
1374
1457
  if (parsed.usage) {
1375
1458
  usageData = parsed.usage;
1459
+ pricingResponse = { ...pricingResponse, ...parsed };
1376
1460
  if (!parsed.choices || parsed.choices.length === 0) continue;
1377
1461
  }
1378
1462
  const delta = parsed.choices?.[0]?.delta;
@@ -1446,38 +1530,328 @@ var OpenAICompatibleProvider = class {
1446
1530
 
1447
1531
  // src/providers/openai.ts
1448
1532
  var OPENAI_PRICING = {
1449
- // gpt-4o era — cache reads at 50% of input rate
1533
+ // Flat, exact Standard rows only. Context-tiered and cache-write-priced
1534
+ // models intentionally stay out of this compatibility view.
1450
1535
  "gpt-4o": [25e-7, 1e-5, 0.5],
1451
1536
  "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
1537
  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
1538
  "gpt-4.1": [2e-6, 8e-6, 0.25],
1460
1539
  "gpt-4.1-mini": [4e-7, 16e-7, 0.25],
1461
1540
  "gpt-4.1-nano": [1e-7, 4e-7, 0.25],
1462
- o3: [1e-5, 4e-5, 0.25],
1541
+ o3: [2e-6, 8e-6, 0.25],
1463
1542
  "o3-mini": [11e-7, 44e-7, 0.25],
1464
- "o3-pro": [2e-5, 8e-5, 0.25],
1465
1543
  "o4-mini": [11e-7, 44e-7, 0.25],
1466
- // gpt-5 era — cache reads at 10% of input rate
1467
1544
  "gpt-5": [125e-8, 1e-5, 0.1],
1468
1545
  "gpt-5-mini": [25e-8, 2e-6, 0.1],
1469
1546
  "gpt-5-nano": [5e-8, 4e-7, 0.1],
1470
1547
  "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]
1548
+ "gpt-5.2": [175e-8, 14e-6, 0.1]
1478
1549
  };
1550
+ var M = 1e6;
1551
+ var LONG_CONTEXT_BOUNDARY = 272e3;
1552
+ var withLongContext = (short) => ({
1553
+ input: short.input * 2,
1554
+ cachedInput: short.cachedInput === void 0 ? void 0 : short.cachedInput * 2,
1555
+ cacheWrite: short.cacheWrite === void 0 ? void 0 : short.cacheWrite * 2,
1556
+ output: short.output * 1.5
1557
+ });
1558
+ var DIRECT_OPENAI_CATALOG = [
1559
+ {
1560
+ aliases: ["gpt-5.6", "gpt-5.6-sol"],
1561
+ snapshotBase: "gpt-5.6-sol",
1562
+ short: { input: 5 / M, cachedInput: 0.5 / M, cacheWrite: 6.25 / M, output: 30 / M },
1563
+ long: withLongContext({
1564
+ input: 5 / M,
1565
+ cachedInput: 0.5 / M,
1566
+ cacheWrite: 6.25 / M,
1567
+ output: 30 / M
1568
+ }),
1569
+ contextBoundary: LONG_CONTEXT_BOUNDARY
1570
+ },
1571
+ {
1572
+ aliases: ["gpt-5.6-terra"],
1573
+ snapshotBase: "gpt-5.6-terra",
1574
+ short: { input: 2 / M, cachedInput: 0.2 / M, cacheWrite: 2.5 / M, output: 12 / M },
1575
+ long: withLongContext({
1576
+ input: 2 / M,
1577
+ cachedInput: 0.2 / M,
1578
+ cacheWrite: 2.5 / M,
1579
+ output: 12 / M
1580
+ }),
1581
+ contextBoundary: LONG_CONTEXT_BOUNDARY
1582
+ },
1583
+ {
1584
+ aliases: ["gpt-5.6-luna"],
1585
+ snapshotBase: "gpt-5.6-luna",
1586
+ short: { input: 0.2 / M, cachedInput: 0.02 / M, cacheWrite: 0.25 / M, output: 1.2 / M },
1587
+ long: withLongContext({
1588
+ input: 0.2 / M,
1589
+ cachedInput: 0.02 / M,
1590
+ cacheWrite: 0.25 / M,
1591
+ output: 1.2 / M
1592
+ }),
1593
+ contextBoundary: LONG_CONTEXT_BOUNDARY
1594
+ },
1595
+ // Existing direct models retain literal current Standard rows. These flat
1596
+ // rows keep native pricing aligned with the compatibility wrapper while the
1597
+ // private estimator still validates categories and billing mode strictly.
1598
+ {
1599
+ aliases: ["gpt-4o", "gpt-4o-2024-08-06", "gpt-4o-2024-11-20"],
1600
+ snapshotBase: "gpt-4o",
1601
+ short: { input: 2.5 / M, cachedInput: 1.25 / M, output: 10 / M }
1602
+ },
1603
+ {
1604
+ aliases: ["gpt-4o-mini", "gpt-4o-mini-2024-07-18"],
1605
+ snapshotBase: "gpt-4o-mini",
1606
+ short: { input: 0.15 / M, cachedInput: 0.075 / M, output: 0.6 / M }
1607
+ },
1608
+ {
1609
+ aliases: ["gpt-3.5-turbo"],
1610
+ snapshotBase: "gpt-3.5-turbo",
1611
+ short: { input: 0.5 / M, output: 1.5 / M }
1612
+ },
1613
+ {
1614
+ aliases: ["o1", "o1-2024-12-17"],
1615
+ snapshotBase: "o1",
1616
+ short: { input: 15 / M, cachedInput: 7.5 / M, output: 60 / M }
1617
+ },
1618
+ {
1619
+ aliases: ["o1-pro", "o1-pro-2025-03-19"],
1620
+ snapshotBase: "o1-pro",
1621
+ short: { input: 150 / M, output: 600 / M }
1622
+ },
1623
+ {
1624
+ aliases: ["gpt-4.1", "gpt-4.1-2025-04-14"],
1625
+ snapshotBase: "gpt-4.1",
1626
+ short: { input: 2 / M, cachedInput: 0.5 / M, output: 8 / M }
1627
+ },
1628
+ {
1629
+ aliases: ["gpt-4.1-mini", "gpt-4.1-mini-2025-04-14"],
1630
+ snapshotBase: "gpt-4.1-mini",
1631
+ short: { input: 0.4 / M, cachedInput: 0.1 / M, output: 1.6 / M }
1632
+ },
1633
+ {
1634
+ aliases: ["gpt-4.1-nano", "gpt-4.1-nano-2025-04-14"],
1635
+ snapshotBase: "gpt-4.1-nano",
1636
+ short: { input: 0.1 / M, cachedInput: 0.025 / M, output: 0.4 / M }
1637
+ },
1638
+ {
1639
+ aliases: ["o3", "o3-2025-04-16"],
1640
+ snapshotBase: "o3",
1641
+ short: { input: 2 / M, cachedInput: 0.5 / M, output: 8 / M }
1642
+ },
1643
+ {
1644
+ aliases: ["o3-mini", "o3-mini-2025-01-31"],
1645
+ snapshotBase: "o3-mini",
1646
+ short: { input: 1.1 / M, cachedInput: 0.55 / M, output: 4.4 / M }
1647
+ },
1648
+ {
1649
+ aliases: ["o3-pro", "o3-pro-2025-06-10"],
1650
+ snapshotBase: "o3-pro",
1651
+ short: { input: 20 / M, output: 80 / M }
1652
+ },
1653
+ {
1654
+ aliases: ["o4-mini", "o4-mini-2025-04-16"],
1655
+ snapshotBase: "o4-mini",
1656
+ short: { input: 1.1 / M, cachedInput: 0.275 / M, output: 4.4 / M }
1657
+ },
1658
+ {
1659
+ aliases: ["gpt-5", "gpt-5-2025-08-07"],
1660
+ snapshotBase: "gpt-5",
1661
+ short: { input: 1.25 / M, cachedInput: 0.125 / M, output: 10 / M }
1662
+ },
1663
+ {
1664
+ aliases: ["gpt-5-mini", "gpt-5-mini-2025-08-07"],
1665
+ snapshotBase: "gpt-5-mini",
1666
+ short: { input: 0.25 / M, cachedInput: 0.025 / M, output: 2 / M }
1667
+ },
1668
+ {
1669
+ aliases: ["gpt-5-nano", "gpt-5-nano-2025-08-07"],
1670
+ snapshotBase: "gpt-5-nano",
1671
+ short: { input: 0.05 / M, cachedInput: 5e-3 / M, output: 0.4 / M }
1672
+ },
1673
+ {
1674
+ aliases: ["gpt-5.1", "gpt-5.1-2025-11-13"],
1675
+ snapshotBase: "gpt-5.1",
1676
+ short: { input: 1.25 / M, cachedInput: 0.125 / M, output: 10 / M }
1677
+ },
1678
+ {
1679
+ aliases: ["gpt-5.2", "gpt-5.2-2025-12-11"],
1680
+ snapshotBase: "gpt-5.2",
1681
+ short: { input: 1.75 / M, cachedInput: 0.175 / M, output: 14 / M }
1682
+ },
1683
+ {
1684
+ aliases: ["gpt-5.4", "gpt-5.4-2026-03-05"],
1685
+ snapshotBase: "gpt-5.4",
1686
+ short: { input: 2.5 / M, cachedInput: 0.25 / M, output: 15 / M },
1687
+ long: withLongContext({ input: 2.5 / M, cachedInput: 0.25 / M, output: 15 / M }),
1688
+ contextBoundary: LONG_CONTEXT_BOUNDARY
1689
+ },
1690
+ {
1691
+ aliases: ["gpt-5.4-pro", "gpt-5.4-pro-2026-03-05"],
1692
+ snapshotBase: "gpt-5.4-pro",
1693
+ short: { input: 30 / M, output: 180 / M },
1694
+ long: withLongContext({ input: 30 / M, output: 180 / M }),
1695
+ contextBoundary: LONG_CONTEXT_BOUNDARY
1696
+ },
1697
+ {
1698
+ aliases: ["gpt-5.5", "gpt-5.5-2026-04-23"],
1699
+ snapshotBase: "gpt-5.5",
1700
+ short: { input: 5 / M, cachedInput: 0.5 / M, output: 30 / M },
1701
+ long: withLongContext({ input: 5 / M, cachedInput: 0.5 / M, output: 30 / M }),
1702
+ contextBoundary: LONG_CONTEXT_BOUNDARY
1703
+ },
1704
+ {
1705
+ aliases: ["gpt-5.5-pro", "gpt-5.5-pro-2026-04-23"],
1706
+ snapshotBase: "gpt-5.5-pro",
1707
+ short: { input: 30 / M, output: 180 / M },
1708
+ long: withLongContext({ input: 30 / M, output: 180 / M }),
1709
+ contextBoundary: LONG_CONTEXT_BOUNDARY
1710
+ },
1711
+ {
1712
+ aliases: ["gpt-5.4-mini", "gpt-5.4-mini-2026-03-17"],
1713
+ short: { input: 0.75 / M, cachedInput: 0.075 / M, output: 4.5 / M }
1714
+ },
1715
+ {
1716
+ aliases: ["gpt-5.4-nano", "gpt-5.4-nano-2026-03-17"],
1717
+ short: { input: 0.2 / M, cachedInput: 0.02 / M, output: 1.25 / M }
1718
+ },
1719
+ {
1720
+ aliases: ["gpt-5.2-pro", "gpt-5.2-pro-2025-12-11"],
1721
+ short: { input: 21 / M, output: 168 / M }
1722
+ },
1723
+ { aliases: ["gpt-5-pro", "gpt-5-pro-2025-10-06"], short: { input: 15 / M, output: 120 / M } },
1724
+ { aliases: ["gpt-3.5-turbo-instruct"], short: { input: 1.5 / M, output: 2 / M } },
1725
+ { aliases: ["davinci-002"], short: { input: 2 / M, output: 2 / M } },
1726
+ { aliases: ["babbage-002"], short: { input: 0.4 / M, output: 0.4 / M } },
1727
+ // Explicit snapshots with their own current dedicated-table rows. Do not
1728
+ // generalize these dates: a plausible-looking unlisted snapshot is unpriced.
1729
+ { aliases: ["gpt-4o-2024-05-13"], short: { input: 5 / M, output: 15 / M } },
1730
+ { aliases: ["gpt-4-turbo-2024-04-09"], short: { input: 10 / M, output: 30 / M } },
1731
+ { aliases: ["gpt-4-0613"], short: { input: 30 / M, output: 60 / M } },
1732
+ {
1733
+ aliases: ["gpt-3.5-turbo-0125"],
1734
+ short: { input: 0.5 / M, output: 1.5 / M }
1735
+ },
1736
+ { aliases: ["gpt-3.5-turbo-1106"], short: { input: 1 / M, output: 2 / M } }
1737
+ ];
1738
+ function directOpenAIModel(model) {
1739
+ return DIRECT_OPENAI_CATALOG.find((entry) => entry.aliases.includes(model));
1740
+ }
1741
+ var CANONICAL_OPENAI_BASE_URL = "https://api.openai.com/v1";
1742
+ function isTextContentPart(value, type) {
1743
+ return value !== null && typeof value === "object" && value.type === type && typeof value.text === "string";
1744
+ }
1745
+ function hasUnmodeledDirectOpenAIContent(request) {
1746
+ if ("messages" in request) {
1747
+ if (!Array.isArray(request.messages)) return true;
1748
+ for (const message of request.messages) {
1749
+ if (message === null || typeof message !== "object") return true;
1750
+ const content = message.content;
1751
+ if (content === void 0 || content === null || typeof content === "string") continue;
1752
+ if (!Array.isArray(content) || content.some((part) => !isTextContentPart(part, "text"))) {
1753
+ return true;
1754
+ }
1755
+ }
1756
+ return false;
1757
+ }
1758
+ if ("input" in request) {
1759
+ const input = request.input;
1760
+ if (typeof input === "string") return false;
1761
+ if (!Array.isArray(input)) return true;
1762
+ for (const item of input) {
1763
+ if (item === null || typeof item !== "object") return true;
1764
+ const typed = item;
1765
+ switch (typed.type) {
1766
+ case "message": {
1767
+ const content = typed.content;
1768
+ if (typeof content === "string") break;
1769
+ if (!Array.isArray(content) || content.some((part) => !isTextContentPart(part, "input_text"))) {
1770
+ return true;
1771
+ }
1772
+ break;
1773
+ }
1774
+ case "function_call":
1775
+ break;
1776
+ case "function_call_output": {
1777
+ const output = typed.output;
1778
+ if (typeof output === "string") break;
1779
+ if (!Array.isArray(output) || output.some((part) => !isTextContentPart(part, "input_text"))) {
1780
+ return true;
1781
+ }
1782
+ break;
1783
+ }
1784
+ case "custom_tool_call":
1785
+ if (typeof typed.input !== "string") return true;
1786
+ break;
1787
+ case "custom_tool_call_output":
1788
+ if (typeof typed.output !== "string") return true;
1789
+ break;
1790
+ case "reasoning":
1791
+ break;
1792
+ default:
1793
+ return true;
1794
+ }
1795
+ }
1796
+ return false;
1797
+ }
1798
+ return false;
1799
+ }
1800
+ function isEligibleDirectOpenAIContext(context) {
1801
+ const request = context?.request;
1802
+ const response = context?.response;
1803
+ if (context?.baseUrl !== void 0 && context.baseUrl !== CANONICAL_OPENAI_BASE_URL) return false;
1804
+ if (request && hasUnmodeledDirectOpenAIContent(request)) return false;
1805
+ const tier = response?.service_tier ?? response?.serviceTier ?? request?.service_tier ?? request?.serviceTier;
1806
+ if (tier !== void 0 && (typeof tier !== "string" || !["standard", "default"].includes(tier.toLowerCase()))) {
1807
+ return false;
1808
+ }
1809
+ if (["region", "inference_geo", "data_residency"].some((key) => request?.[key] !== void 0)) {
1810
+ return false;
1811
+ }
1812
+ if (["previous_response_id", "conversation", "prompt"].some((key) => request?.[key] !== void 0)) {
1813
+ return false;
1814
+ }
1815
+ const reasoning = request?.reasoning;
1816
+ if (reasoning !== null && typeof reasoning === "object" && "mode" in reasoning && reasoning.mode !== void 0) {
1817
+ return false;
1818
+ }
1819
+ 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)) {
1820
+ return false;
1821
+ }
1822
+ if (Array.isArray(request?.tools) && request.tools.some(
1823
+ (tool2) => tool2 === null || typeof tool2 !== "object" || !["function", "custom"].includes(tool2.type)
1824
+ )) {
1825
+ return false;
1826
+ }
1827
+ return true;
1828
+ }
1829
+ function estimateDirectOpenAICost(model, usage, context) {
1830
+ if (!isEligibleDirectOpenAIContext(context)) return void 0;
1831
+ const entry = directOpenAIModel(model);
1832
+ if (!entry) return void 0;
1833
+ const { prompt_tokens, completion_tokens, cached_tokens, cache_write_tokens } = usage;
1834
+ const cached = cached_tokens ?? 0;
1835
+ const cacheWrite = cache_write_tokens ?? 0;
1836
+ if (![prompt_tokens, completion_tokens, cached, cacheWrite].every(
1837
+ (count) => Number.isSafeInteger(count) && count >= 0
1838
+ ) || cached + cacheWrite > prompt_tokens) {
1839
+ return void 0;
1840
+ }
1841
+ const rates = entry.long !== void 0 && entry.contextBoundary !== void 0 && prompt_tokens > entry.contextBoundary ? entry.long : entry.short;
1842
+ if (cached > 0 && rates.cachedInput === void 0 || cacheWrite > 0 && rates.cacheWrite === void 0) {
1843
+ return void 0;
1844
+ }
1845
+ const ordinary = prompt_tokens - cached - cacheWrite;
1846
+ return ordinary * rates.input + cached * (rates.cachedInput ?? 0) + cacheWrite * (rates.cacheWrite ?? 0) + completion_tokens * rates.output;
1847
+ }
1479
1848
  function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens) {
1480
- return priceFromTable(OPENAI_PRICING, model, promptTokens, completionTokens, cachedTokens);
1849
+ return estimateDirectOpenAICost(model, {
1850
+ prompt_tokens: promptTokens,
1851
+ completion_tokens: completionTokens,
1852
+ total_tokens: promptTokens + completionTokens,
1853
+ cached_tokens: cachedTokens
1854
+ });
1481
1855
  }
1482
1856
  function isOSeriesModel(model) {
1483
1857
  return /^(o1|o3|o4-mini)/.test(model);
@@ -1485,6 +1859,15 @@ function isOSeriesModel(model) {
1485
1859
  function supportsReasoningEffort(model) {
1486
1860
  return isOSeriesModel(model) || /^gpt-5/.test(model);
1487
1861
  }
1862
+ var GPT_56_REASONING_MODELS = /* @__PURE__ */ new Set([
1863
+ "gpt-5.6",
1864
+ "gpt-5.6-sol",
1865
+ "gpt-5.6-terra",
1866
+ "gpt-5.6-luna"
1867
+ ]);
1868
+ function supportsMaxReasoningEffort(model) {
1869
+ return GPT_56_REASONING_MODELS.has(model);
1870
+ }
1488
1871
  function supportsReasoningNone(model) {
1489
1872
  return /^gpt-5\.[1-9]/.test(model);
1490
1873
  }
@@ -1494,31 +1877,46 @@ function supportsXhigh(model) {
1494
1877
  function clampReasoningEffort(model, effort) {
1495
1878
  if (model.startsWith("gpt-5-pro")) return "high";
1496
1879
  if (effort === "none" && !supportsReasoningNone(model)) return "minimal";
1880
+ if (effort === "max" && !supportsMaxReasoningEffort(model)) {
1881
+ return clampReasoningEffort(model, "xhigh");
1882
+ }
1497
1883
  if (effort === "xhigh" && !supportsXhigh(model)) return "high";
1498
1884
  return effort;
1499
1885
  }
1500
- function effortToReasoningEffort(effort) {
1501
- return effort === "max" ? "xhigh" : effort;
1886
+ function effortToReasoningEffort(effort, model) {
1887
+ return effort === "max" && model !== void 0 && supportsMaxReasoningEffort(model) ? "max" : effort === "max" ? "xhigh" : effort;
1502
1888
  }
1503
1889
  function budgetToReasoningEffort(budget) {
1504
1890
  if (budget <= 1024) return "low";
1505
1891
  if (budget <= 8192) return "medium";
1506
1892
  return "high";
1507
1893
  }
1894
+ function resolveOpenAIReasoningEffort(model, resolved) {
1895
+ if (!supportsReasoningEffort(model)) return void 0;
1896
+ if (resolved.hasBudgetOverride) {
1897
+ return clampReasoningEffort(model, budgetToReasoningEffort(resolved.thinkingBudget));
1898
+ }
1899
+ if (!resolved.thinkingDisabled && resolved.activeEffort) {
1900
+ return clampReasoningEffort(model, effortToReasoningEffort(resolved.activeEffort, model));
1901
+ }
1902
+ return resolved.thinkingDisabled ? clampReasoningEffort(model, "none") : void 0;
1903
+ }
1904
+ var warnedChatMaxDowngrades = /* @__PURE__ */ new Set();
1905
+ function resolveOpenAIChatReasoningEffort(model, resolved) {
1906
+ const effort = resolveOpenAIReasoningEffort(model, resolved);
1907
+ if (effort !== "max") return effort;
1908
+ if (resolved.activeEffort === "max" && !warnedChatMaxDowngrades.has(model)) {
1909
+ warnedChatMaxDowngrades.add(model);
1910
+ console.warn(
1911
+ `[axl] OpenAI Chat Completions does not accept effort "max" for ${model}; using "xhigh". Use the OpenAI Responses provider for native "max".`
1912
+ );
1913
+ }
1914
+ return "xhigh";
1915
+ }
1508
1916
  var openaiReasoningEmit = (body, resolved, model) => {
1509
1917
  const oSeries = isOSeriesModel(model);
1510
1918
  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
- }
1919
+ const wireEffort = resolveOpenAIChatReasoningEffort(model, resolved);
1522
1920
  if (wireEffort) body.reasoning_effort = wireEffort;
1523
1921
  return { stripTemperature: oSeries || reasoningCapable && wireEffort !== void 0 };
1524
1922
  };
@@ -1528,7 +1926,7 @@ var OPENAI_PROFILE = {
1528
1926
  defaultBaseUrl: "https://api.openai.com/v1",
1529
1927
  envApiKey: "OPENAI_API_KEY",
1530
1928
  envBaseUrl: "OPENAI_BASE_URL",
1531
- pricing: { kind: "table", table: OPENAI_PRICING },
1929
+ pricing: { kind: "table", table: OPENAI_PRICING, match: "exact" },
1532
1930
  reasoning: { emit: openaiReasoningEmit, capture: "none" },
1533
1931
  roleFor: (role, model) => role === "system" && isOSeriesModel(model) ? "developer" : role,
1534
1932
  maxTokensField: "max_completion_tokens",
@@ -1538,6 +1936,9 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
1538
1936
  constructor(options = {}) {
1539
1937
  super({ profile: OPENAI_PROFILE, ...options });
1540
1938
  }
1939
+ computeCost(model, usage, _reportedCost, context) {
1940
+ return usage ? estimateDirectOpenAICost(model, usage, { ...context, baseUrl: this.baseUrl }) : void 0;
1941
+ }
1541
1942
  };
1542
1943
 
1543
1944
  // src/providers/openai-responses.ts
@@ -1598,7 +1999,7 @@ var OpenAIResponsesProvider = class {
1598
1999
  });
1599
2000
  }
1600
2001
  const json = await res.json();
1601
- return this.parseResponse(json, options.model);
2002
+ return this.parseResponse(json, this.requestModel(body, options.model), body);
1602
2003
  }
1603
2004
  // ---------------------------------------------------------------------------
1604
2005
  // stream - SSE streaming
@@ -1630,30 +2031,23 @@ var OpenAIResponsesProvider = class {
1630
2031
  if (!res.body) {
1631
2032
  throw new Error("OpenAI Responses stream has no body");
1632
2033
  }
1633
- yield* this.parseSSEStream(res.body, options.model);
2034
+ yield* this.parseSSEStream(res.body, this.requestModel(body, options.model), body);
1634
2035
  }
1635
2036
  // ---------------------------------------------------------------------------
1636
2037
  // Internal: build request body
1637
2038
  // ---------------------------------------------------------------------------
1638
2039
  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
- }
2040
+ const effectiveModel = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
2041
+ const oSeries = isOSeriesModel(effectiveModel);
2042
+ const reasoningCapable = supportsReasoningEffort(effectiveModel);
2043
+ const resolved = resolveThinkingOptions(options);
2044
+ const { includeThoughts } = resolved;
2045
+ const wireEffort = resolveOpenAIReasoningEffort(effectiveModel, resolved);
1652
2046
  const stripTemp = oSeries || reasoningCapable && wireEffort !== void 0;
1653
2047
  const systemMessages = messages.filter((m) => m.role === "system");
1654
2048
  const nonSystemMessages = messages.filter((m) => m.role !== "system");
1655
2049
  const body = {
1656
- model: options.model,
2050
+ model: effectiveModel,
1657
2051
  input: this.buildInput(nonSystemMessages),
1658
2052
  store: false,
1659
2053
  stream
@@ -1700,6 +2094,9 @@ var OpenAIResponsesProvider = class {
1700
2094
  }
1701
2095
  return body;
1702
2096
  }
2097
+ requestModel(body, fallback) {
2098
+ return typeof body.model === "string" ? body.model : fallback;
2099
+ }
1703
2100
  // ---------------------------------------------------------------------------
1704
2101
  // Internal: message → input mapping
1705
2102
  // ---------------------------------------------------------------------------
@@ -1767,7 +2164,7 @@ var OpenAIResponsesProvider = class {
1767
2164
  // ---------------------------------------------------------------------------
1768
2165
  // Internal: parse non-streaming response
1769
2166
  // ---------------------------------------------------------------------------
1770
- parseResponse(json, model) {
2167
+ parseResponse(json, model, request) {
1771
2168
  let content = "";
1772
2169
  let thinkingContent = "";
1773
2170
  const toolCalls = [];
@@ -1804,9 +2201,14 @@ var OpenAIResponsesProvider = class {
1804
2201
  completion_tokens: json.usage.output_tokens,
1805
2202
  total_tokens: json.usage.total_tokens,
1806
2203
  reasoning_tokens: json.usage.output_tokens_details?.reasoning_tokens,
1807
- cached_tokens: json.usage.input_tokens_details?.cached_tokens
2204
+ cached_tokens: json.usage.input_tokens_details?.cached_tokens,
2205
+ cache_write_tokens: json.usage.input_tokens_details?.cache_write_tokens
1808
2206
  } : void 0;
1809
- const cost = usage ? estimateOpenAICost(model, usage.prompt_tokens, usage.completion_tokens, usage.cached_tokens) : void 0;
2207
+ const cost = usage ? estimateDirectOpenAICost(json.model ?? model, usage, {
2208
+ baseUrl: this.baseUrl,
2209
+ request,
2210
+ response: json
2211
+ }) : void 0;
1810
2212
  const providerMetadata = reasoningItems.length > 0 ? { openaiReasoningItems: reasoningItems } : void 0;
1811
2213
  return {
1812
2214
  content,
@@ -1820,7 +2222,7 @@ var OpenAIResponsesProvider = class {
1820
2222
  // ---------------------------------------------------------------------------
1821
2223
  // Internal: parse SSE stream
1822
2224
  // ---------------------------------------------------------------------------
1823
- async *parseSSEStream(body, model) {
2225
+ async *parseSSEStream(body, model, request) {
1824
2226
  const reader = body.getReader();
1825
2227
  const decoder = new TextDecoder();
1826
2228
  let buffer = "";
@@ -1848,7 +2250,7 @@ var OpenAIResponsesProvider = class {
1848
2250
  } catch {
1849
2251
  continue;
1850
2252
  }
1851
- const chunk = this.handleStreamEvent(eventType, data, model, callIdMap);
2253
+ const chunk = this.handleStreamEvent(eventType, data, model, callIdMap, request);
1852
2254
  if (chunk) {
1853
2255
  yield chunk;
1854
2256
  if (chunk.type === "done") return;
@@ -1862,7 +2264,7 @@ var OpenAIResponsesProvider = class {
1862
2264
  reader.releaseLock();
1863
2265
  }
1864
2266
  }
1865
- handleStreamEvent(eventType, data, model, callIdMap) {
2267
+ handleStreamEvent(eventType, data, model, callIdMap, request) {
1866
2268
  switch (eventType) {
1867
2269
  case "response.output_text.delta":
1868
2270
  return { type: "text_delta", content: data.delta ?? "" };
@@ -1896,19 +2298,19 @@ var OpenAIResponsesProvider = class {
1896
2298
  completion_tokens: response.usage.output_tokens,
1897
2299
  total_tokens: response.usage.total_tokens,
1898
2300
  reasoning_tokens: response.usage.output_tokens_details?.reasoning_tokens,
1899
- cached_tokens: response.usage.input_tokens_details?.cached_tokens
2301
+ cached_tokens: response.usage.input_tokens_details?.cached_tokens,
2302
+ cache_write_tokens: response.usage.input_tokens_details?.cache_write_tokens
1900
2303
  } : void 0;
1901
2304
  const reasoningItems = response?.output?.filter((item) => item.type === "reasoning") ?? [];
1902
2305
  const providerMetadata = reasoningItems.length > 0 ? { openaiReasoningItems: reasoningItems } : void 0;
1903
2306
  return {
1904
2307
  type: "done",
1905
2308
  usage,
1906
- cost: usage ? estimateOpenAICost(
1907
- model,
1908
- usage.prompt_tokens,
1909
- usage.completion_tokens,
1910
- usage.cached_tokens
1911
- ) : void 0,
2309
+ cost: usage ? estimateDirectOpenAICost(response?.model ?? model, usage, {
2310
+ baseUrl: this.baseUrl,
2311
+ request,
2312
+ response
2313
+ }) : void 0,
1912
2314
  providerMetadata
1913
2315
  };
1914
2316
  }
@@ -1942,68 +2344,357 @@ var OpenAIResponsesProvider = class {
1942
2344
 
1943
2345
  // src/providers/anthropic.ts
1944
2346
  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]
2347
+ var CLAUDE_CAPABILITIES = {
2348
+ "claude-fable-5": {
2349
+ thinking: "adaptive-always-on",
2350
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
2351
+ manualBudget: false,
2352
+ stripTemperature: true
2353
+ },
2354
+ "claude-opus-5": {
2355
+ thinking: "adaptive-default-on",
2356
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
2357
+ manualBudget: false,
2358
+ stripTemperature: true,
2359
+ disableAt: "high"
2360
+ },
2361
+ "claude-sonnet-5": {
2362
+ thinking: "adaptive-default-on",
2363
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
2364
+ manualBudget: false,
2365
+ stripTemperature: true,
2366
+ disableAt: "max"
2367
+ },
2368
+ "claude-opus-4-8": {
2369
+ thinking: "adaptive-optional",
2370
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
2371
+ manualBudget: false,
2372
+ stripTemperature: true
2373
+ },
2374
+ "claude-opus-4-7": {
2375
+ thinking: "adaptive-optional",
2376
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
2377
+ manualBudget: false,
2378
+ stripTemperature: true
2379
+ },
2380
+ "claude-opus-4-6": {
2381
+ thinking: "adaptive-optional",
2382
+ effortLevels: ["low", "medium", "high", "max"],
2383
+ manualBudget: true
2384
+ },
2385
+ "claude-sonnet-4-6": {
2386
+ thinking: "adaptive-optional",
2387
+ effortLevels: ["low", "medium", "high", "max"],
2388
+ manualBudget: true
2389
+ },
2390
+ "claude-opus-4-5": {
2391
+ thinking: "effort-only",
2392
+ effortLevels: ["low", "medium", "high"],
2393
+ manualBudget: true
2394
+ },
2395
+ "claude-opus-4-5-20251101": {
2396
+ thinking: "effort-only",
2397
+ effortLevels: ["low", "medium", "high"],
2398
+ manualBudget: true
2399
+ }
1962
2400
  };
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
- }
2401
+ var LEGACY_CLAUDE_CAPABILITY = {
2402
+ thinking: "legacy-manual",
2403
+ manualBudget: true
2404
+ };
2405
+ var LEGACY_CLAUDE_MODELS = /* @__PURE__ */ new Set([
2406
+ "claude-opus-4-1",
2407
+ "claude-opus-4-1-20250805",
2408
+ "claude-sonnet-4-5",
2409
+ "claude-sonnet-4-5-20250929",
2410
+ "claude-haiku-4-5",
2411
+ "claude-haiku-4-5-20251001",
2412
+ "claude-sonnet-4",
2413
+ "claude-sonnet-4-20250514",
2414
+ "claude-opus-4",
2415
+ "claude-opus-4-20250514",
2416
+ "claude-3-7-sonnet",
2417
+ "claude-3-7-sonnet-20250219",
2418
+ "claude-3-5-sonnet",
2419
+ "claude-3-5-sonnet-20241022",
2420
+ "claude-3-5-sonnet-20240620",
2421
+ "claude-3-5-haiku",
2422
+ "claude-3-5-haiku-20241022",
2423
+ "claude-3-opus",
2424
+ "claude-3-opus-20240229",
2425
+ "claude-3-sonnet",
2426
+ "claude-3-sonnet-20240229",
2427
+ "claude-3-haiku",
2428
+ "claude-3-haiku-20240307"
2429
+ ]);
2430
+ var PRE_INFERENCE_GEO_MODELS = /* @__PURE__ */ new Set([
2431
+ ...LEGACY_CLAUDE_MODELS,
2432
+ "claude-opus-4-5",
2433
+ "claude-opus-4-5-20251101"
2434
+ ]);
2435
+ function resolveClaudeCapability(model) {
2436
+ return CLAUDE_CAPABILITIES[model] ?? (LEGACY_CLAUDE_MODELS.has(model) ? LEGACY_CLAUDE_CAPABILITY : void 0);
2437
+ }
2438
+ var ANTHROPIC_RATES = {
2439
+ "claude-fable-5": { input: 1e-5, output: 5e-5 },
2440
+ "claude-opus-5": { input: 5e-6, output: 25e-6 },
2441
+ "claude-sonnet-5": { input: 3e-6, output: 15e-6, sonnet5Intro: true },
2442
+ "claude-opus-4-8": { input: 5e-6, output: 25e-6 },
2443
+ "claude-opus-4-7": { input: 5e-6, output: 25e-6 },
2444
+ "claude-opus-4-6": { input: 5e-6, output: 25e-6 },
2445
+ "claude-sonnet-4-6": { input: 3e-6, output: 15e-6 },
2446
+ "claude-opus-4-5": { input: 5e-6, output: 25e-6 },
2447
+ "claude-opus-4-5-20251101": { input: 5e-6, output: 25e-6 },
2448
+ "claude-opus-4-1": { input: 15e-6, output: 75e-6 },
2449
+ "claude-opus-4-1-20250805": { input: 15e-6, output: 75e-6 },
2450
+ "claude-sonnet-4-5": { input: 3e-6, output: 15e-6 },
2451
+ "claude-sonnet-4-5-20250929": { input: 3e-6, output: 15e-6 },
2452
+ "claude-haiku-4-5": { input: 1e-6, output: 5e-6 },
2453
+ "claude-haiku-4-5-20251001": { input: 1e-6, output: 5e-6 }
2454
+ };
2455
+ function isValidTokenCount(value) {
2456
+ return value !== void 0 && Number.isSafeInteger(value) && value >= 0;
2457
+ }
2458
+ function estimateAnthropicCost(model, usage, now = /* @__PURE__ */ new Date()) {
2459
+ if (!model || !isValidTokenCount(usage.inputTokens) || !isValidTokenCount(usage.outputTokens)) {
2460
+ return void 0;
1975
2461
  }
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;
2462
+ const rate = ANTHROPIC_RATES[model];
2463
+ if (!rate) return void 0;
2464
+ const cacheRead = usage.cacheReadTokens ?? 0;
2465
+ const cacheWrite5m = usage.cacheWrite5mTokens ?? 0;
2466
+ const cacheWrite1h = usage.cacheWrite1hTokens ?? 0;
2467
+ if (!isValidTokenCount(cacheRead) || !isValidTokenCount(cacheWrite5m) || !isValidTokenCount(cacheWrite1h)) {
2468
+ return void 0;
2469
+ }
2470
+ const cacheWriteTotal = safeTokenSum(cacheWrite5m, cacheWrite1h);
2471
+ if (cacheWriteTotal === void 0 || safeTokenSum(usage.inputTokens, cacheRead, cacheWriteTotal) === void 0) {
2472
+ return void 0;
2473
+ }
2474
+ if (usage.aggregateCacheWriteTokens !== void 0 && (!isValidTokenCount(usage.aggregateCacheWriteTokens) || usage.aggregateCacheWriteTokens !== cacheWriteTotal)) {
2475
+ return void 0;
2476
+ }
2477
+ const { input, output } = rate.sonnet5Intro && now < /* @__PURE__ */ new Date("2026-09-01T00:00:00Z") ? { input: 2e-6, output: 1e-5 } : rate;
2478
+ return usage.inputTokens * input + cacheRead * input * 0.1 + cacheWrite5m * input * 1.25 + cacheWrite1h * input * 2 + usage.outputTokens * output;
1982
2479
  }
1983
2480
  var THINKING_BUDGETS = {
1984
2481
  low: 1024,
1985
2482
  medium: 5e3,
1986
2483
  high: 1e4,
2484
+ xhigh: 2e4,
1987
2485
  // 30000 (not 32000) to stay under the 32K max_tokens limit on Opus 4/4.1.
1988
2486
  // With auto-bump (+1024), max_tokens becomes 31024 which fits all models.
1989
2487
  max: 3e4
1990
2488
  };
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");
2489
+ function clampAnthropicEffort(capability, effort) {
2490
+ const levels = capability.effortLevels;
2491
+ if (!levels) return effort === "max" || effort === "xhigh" ? "high" : effort;
2492
+ if (levels.includes(effort)) return effort;
2493
+ return "high";
2494
+ }
2495
+ function budgetToClaudeEffort(budget) {
2496
+ if (budget <= THINKING_BUDGETS.low) return "low";
2497
+ if (budget <= THINKING_BUDGETS.medium) return "medium";
2498
+ if (budget <= THINKING_BUDGETS.high) return "high";
2499
+ if (budget <= THINKING_BUDGETS.xhigh) return "xhigh";
2500
+ return "max";
2501
+ }
2502
+ var warnedFableDisable = /* @__PURE__ */ new Set();
2503
+ function warnFableDisable(model) {
2504
+ if (warnedFableDisable.has(model)) return;
2505
+ warnedFableDisable.add(model);
2506
+ console.warn(
2507
+ `[axl] thinking cannot be disabled on Anthropic ${model}; using adaptive thinking at effort 'low'.`
2508
+ );
1993
2509
  }
1994
- function supportsMaxEffort(model) {
1995
- return model.startsWith("claude-opus-4-8") || model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6");
2510
+ function resolveClaudeThinking(model, resolved) {
2511
+ const capability = resolveClaudeCapability(model);
2512
+ if (!capability) return { stripTemperature: false };
2513
+ const activeEffort = resolved.activeEffort ? clampAnthropicEffort(capability, resolved.activeEffort) : void 0;
2514
+ const isClaude5 = capability.thinking === "adaptive-default-on" || capability.thinking === "adaptive-always-on";
2515
+ if (isClaude5) {
2516
+ if (resolved.hasBudgetOverride) {
2517
+ const effort = clampAnthropicEffort(
2518
+ capability,
2519
+ budgetToClaudeEffort(resolved.thinkingBudget)
2520
+ );
2521
+ return {
2522
+ thinking: { type: "adaptive" },
2523
+ outputConfig: { effort },
2524
+ stripTemperature: true
2525
+ };
2526
+ }
2527
+ if (resolved.thinkingDisabled) {
2528
+ if (capability.thinking === "adaptive-always-on") {
2529
+ warnFableDisable(model);
2530
+ return {
2531
+ thinking: { type: "adaptive" },
2532
+ outputConfig: { effort: "low" },
2533
+ stripTemperature: true
2534
+ };
2535
+ }
2536
+ if (activeEffort && capability.disableAt && !isEffortAtOrBelow(activeEffort, capability.disableAt)) {
2537
+ return {
2538
+ thinking: { type: "adaptive" },
2539
+ outputConfig: { effort: activeEffort },
2540
+ stripTemperature: true
2541
+ };
2542
+ }
2543
+ return {
2544
+ thinking: { type: "disabled" },
2545
+ outputConfig: activeEffort ? { effort: activeEffort } : void 0,
2546
+ stripTemperature: true
2547
+ };
2548
+ }
2549
+ return activeEffort ? {
2550
+ thinking: { type: "adaptive" },
2551
+ outputConfig: { effort: activeEffort },
2552
+ stripTemperature: true
2553
+ } : { stripTemperature: true };
2554
+ }
2555
+ if (resolved.hasBudgetOverride) {
2556
+ if (!capability.manualBudget) {
2557
+ const effort = clampAnthropicEffort(
2558
+ capability,
2559
+ budgetToClaudeEffort(resolved.thinkingBudget)
2560
+ );
2561
+ return capability.thinking === "adaptive-optional" ? {
2562
+ thinking: { type: "adaptive" },
2563
+ outputConfig: { effort },
2564
+ stripTemperature: capability.stripTemperature ?? true
2565
+ } : { outputConfig: { effort }, stripTemperature: capability.stripTemperature ?? false };
2566
+ }
2567
+ return {
2568
+ thinking: { type: "enabled", budget_tokens: resolved.thinkingBudget },
2569
+ outputConfig: activeEffort && capability.effortLevels ? { effort: activeEffort } : void 0,
2570
+ manualBudget: resolved.thinkingBudget,
2571
+ stripTemperature: capability.stripTemperature ?? true
2572
+ };
2573
+ }
2574
+ if (resolved.thinkingDisabled) {
2575
+ return {
2576
+ outputConfig: activeEffort && capability.effortLevels ? { effort: activeEffort } : void 0,
2577
+ stripTemperature: capability.stripTemperature ?? false
2578
+ };
2579
+ }
2580
+ if (activeEffort && capability.thinking === "adaptive-optional") {
2581
+ return {
2582
+ thinking: { type: "adaptive" },
2583
+ outputConfig: { effort: activeEffort },
2584
+ stripTemperature: capability.stripTemperature ?? true
2585
+ };
2586
+ }
2587
+ if (activeEffort && capability.thinking === "effort-only") {
2588
+ return { outputConfig: { effort: activeEffort }, stripTemperature: false };
2589
+ }
2590
+ if (activeEffort) {
2591
+ const budget = THINKING_BUDGETS[activeEffort] ?? THINKING_BUDGETS.medium;
2592
+ return {
2593
+ thinking: { type: "enabled", budget_tokens: budget },
2594
+ manualBudget: budget,
2595
+ stripTemperature: true
2596
+ };
2597
+ }
2598
+ return { stripTemperature: capability.stripTemperature ?? false };
1996
2599
  }
1997
- function supportsXhigh2(model) {
1998
- return model.startsWith("claude-opus-4-8") || model.startsWith("claude-opus-4-7");
2600
+ function isEffortAtOrBelow(effort, ceiling) {
2601
+ const levels = ["low", "medium", "high", "xhigh", "max"];
2602
+ return levels.indexOf(effort) <= levels.indexOf(ceiling);
1999
2603
  }
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");
2604
+ function pricingContextFromBody(body) {
2605
+ return {
2606
+ model: typeof body.model === "string" ? body.model : void 0,
2607
+ unpricedModifier: isAnthropicModifier("inference_geo", body.inference_geo) || isAnthropicModifier("speed", body.speed) || hasAnthropicServerTool(body.tools) || body.fallbacks !== void 0
2608
+ };
2002
2609
  }
2003
- function clampAnthropicEffort(model, effort) {
2004
- if (effort === "max" && !supportsMaxEffort(model)) return "high";
2005
- if (effort === "xhigh" && !supportsXhigh2(model)) return "high";
2006
- return effort;
2610
+ function isModifiedAnthropicResponse(json, model) {
2611
+ 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);
2612
+ }
2613
+ function isAnthropicModifier(kind, value) {
2614
+ if (value === void 0) return false;
2615
+ return kind === "speed" ? value !== "standard" : value !== "global";
2616
+ }
2617
+ function isAnthropicResponseModifier(kind, value, model) {
2618
+ if (value === void 0) return false;
2619
+ if (kind === "speed") return value !== "standard";
2620
+ if (value === "global") return false;
2621
+ return value !== "not_available" || model === void 0 || !PRE_INFERENCE_GEO_MODELS.has(model);
2622
+ }
2623
+ function hasAnthropicServerTool(tools) {
2624
+ return Array.isArray(tools) && tools.some(
2625
+ (tool2) => tool2 !== null && typeof tool2 === "object" && typeof tool2.type === "string"
2626
+ );
2627
+ }
2628
+ function hasBilledUnmodeledUsage(value) {
2629
+ return value !== void 0 && value !== 0;
2630
+ }
2631
+ function hasUnmodeledIterations(value) {
2632
+ return value !== void 0;
2633
+ }
2634
+ function hasFallbackIteration(value) {
2635
+ return Array.isArray(value) && value.some(
2636
+ (iteration) => iteration !== null && typeof iteration === "object" && iteration.type === "fallback_message"
2637
+ );
2638
+ }
2639
+ function safeTokenSum(...values) {
2640
+ const total = values.reduce((sum, value) => sum + value, 0);
2641
+ return Number.isSafeInteger(total) ? total : void 0;
2642
+ }
2643
+ function normalizeAnthropicUsage(raw) {
2644
+ const inputTokens = raw.input_tokens;
2645
+ const outputTokens = raw.output_tokens;
2646
+ const cacheReadTokens = raw.cache_read_input_tokens ?? 0;
2647
+ const cacheWrite5mTokens = raw.cache_creation?.ephemeral_5m_input_tokens ?? 0;
2648
+ const cacheWrite1hTokens = raw.cache_creation?.ephemeral_1h_input_tokens ?? 0;
2649
+ if (!isValidTokenCount(inputTokens) || !isValidTokenCount(outputTokens) || !isValidTokenCount(cacheReadTokens) || !isValidTokenCount(cacheWrite5mTokens) || !isValidTokenCount(cacheWrite1hTokens)) {
2650
+ return void 0;
2651
+ }
2652
+ const hasTtlBreakdown = raw.cache_creation?.ephemeral_5m_input_tokens !== void 0 || raw.cache_creation?.ephemeral_1h_input_tokens !== void 0;
2653
+ const aggregateCacheWriteTokens = raw.cache_creation_input_tokens;
2654
+ const normalizedCacheWrite = hasTtlBreakdown ? safeTokenSum(cacheWrite5mTokens, cacheWrite1hTokens) : void 0;
2655
+ if (hasTtlBreakdown && normalizedCacheWrite === void 0) return void 0;
2656
+ const promptCacheWrite = aggregateCacheWriteTokens ?? normalizedCacheWrite ?? 0;
2657
+ if (!isValidTokenCount(promptCacheWrite)) return void 0;
2658
+ const promptTokens = safeTokenSum(inputTokens, cacheReadTokens, promptCacheWrite);
2659
+ if (promptTokens === void 0) return void 0;
2660
+ const totalTokens = safeTokenSum(promptTokens, outputTokens);
2661
+ if (totalTokens === void 0) return void 0;
2662
+ return {
2663
+ usage: {
2664
+ prompt_tokens: promptTokens,
2665
+ completion_tokens: outputTokens,
2666
+ total_tokens: totalTokens,
2667
+ cached_tokens: cacheReadTokens > 0 ? cacheReadTokens : void 0,
2668
+ // The aggregate is still useful public usage telemetry even though it
2669
+ // cannot be priced without the 5m/1h split. Keep pricingUsage separate
2670
+ // so estimateAnthropicCost continues to fail closed in that case.
2671
+ cache_write_tokens: (normalizedCacheWrite ?? aggregateCacheWriteTokens ?? 0) > 0 ? normalizedCacheWrite ?? aggregateCacheWriteTokens : void 0
2672
+ },
2673
+ pricingUsage: {
2674
+ inputTokens,
2675
+ outputTokens,
2676
+ cacheReadTokens,
2677
+ cacheWrite5mTokens,
2678
+ cacheWrite1hTokens,
2679
+ aggregateCacheWriteTokens
2680
+ }
2681
+ };
2682
+ }
2683
+ function mergeAnthropicUsage(initial, update) {
2684
+ return {
2685
+ input_tokens: update.input_tokens ?? initial?.input_tokens,
2686
+ output_tokens: update.output_tokens ?? initial?.output_tokens,
2687
+ cache_read_input_tokens: update.cache_read_input_tokens ?? initial?.cache_read_input_tokens,
2688
+ cache_creation_input_tokens: update.cache_creation_input_tokens ?? initial?.cache_creation_input_tokens,
2689
+ cache_creation: {
2690
+ ephemeral_5m_input_tokens: update.cache_creation?.ephemeral_5m_input_tokens ?? initial?.cache_creation?.ephemeral_5m_input_tokens,
2691
+ ephemeral_1h_input_tokens: update.cache_creation?.ephemeral_1h_input_tokens ?? initial?.cache_creation?.ephemeral_1h_input_tokens
2692
+ },
2693
+ speed: update.speed ?? initial?.speed,
2694
+ inference_geo: update.inference_geo ?? initial?.inference_geo,
2695
+ iterations: update.iterations ?? initial?.iterations,
2696
+ server_tool_use: update.server_tool_use ?? initial?.server_tool_use
2697
+ };
2007
2698
  }
2008
2699
  var AnthropicProvider = class {
2009
2700
  name = "anthropic";
@@ -2041,6 +2732,7 @@ var AnthropicProvider = class {
2041
2732
  async chat(messages, options) {
2042
2733
  const headers = this.buildHeaders(await this.resolveKey());
2043
2734
  const body = this.buildRequestBody(messages, options, false);
2735
+ const pricingContext = pricingContextFromBody(body);
2044
2736
  const res = await fetchWithRetry(
2045
2737
  `${this.baseUrl}/messages`,
2046
2738
  {
@@ -2063,7 +2755,7 @@ var AnthropicProvider = class {
2063
2755
  });
2064
2756
  }
2065
2757
  const json = await res.json();
2066
- return this.parseResponse(json, options.model);
2758
+ return this.parseResponse(json, pricingContext);
2067
2759
  }
2068
2760
  // ---------------------------------------------------------------------------
2069
2761
  // stream - SSE streaming completion
@@ -2071,6 +2763,7 @@ var AnthropicProvider = class {
2071
2763
  async *stream(messages, options) {
2072
2764
  const headers = this.buildHeaders(await this.resolveKey());
2073
2765
  const body = this.buildRequestBody(messages, options, true);
2766
+ const pricingContext = pricingContextFromBody(body);
2074
2767
  const res = await fetchWithRetry(
2075
2768
  `${this.baseUrl}/messages`,
2076
2769
  {
@@ -2095,7 +2788,7 @@ var AnthropicProvider = class {
2095
2788
  if (!res.body) {
2096
2789
  throw new Error("Anthropic stream response has no body");
2097
2790
  }
2098
- yield* this.parseSSEStream(res.body, options.model);
2791
+ yield* this.parseSSEStream(res.body, pricingContext);
2099
2792
  }
2100
2793
  // ---------------------------------------------------------------------------
2101
2794
  // Internal: request building
@@ -2119,11 +2812,12 @@ var AnthropicProvider = class {
2119
2812
  return `Anthropic API error (${status}): ${body}`;
2120
2813
  }
2121
2814
  buildRequestBody(messages, options, stream) {
2815
+ const effectiveModel = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
2122
2816
  const systemMessages = messages.filter((m) => m.role === "system");
2123
2817
  const nonSystemMessages = messages.filter((m) => m.role !== "system");
2124
2818
  const systemText = systemMessages.map((m) => m.content).join("\n\n");
2125
2819
  const body = {
2126
- model: options.model,
2820
+ model: effectiveModel,
2127
2821
  messages: this.mapMessages(nonSystemMessages),
2128
2822
  max_tokens: options.maxTokens ?? 4096,
2129
2823
  stream
@@ -2140,35 +2834,16 @@ var AnthropicProvider = class {
2140
2834
  if (options.toolChoice !== void 0) {
2141
2835
  body.tool_choice = this.mapToolChoice(options.toolChoice);
2142
2836
  }
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 };
2147
- 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 };
2837
+ const thinking = resolveClaudeThinking(effectiveModel, resolveThinkingOptions(options));
2838
+ if (thinking.thinking) body.thinking = thinking.thinking;
2839
+ if (thinking.outputConfig) body.output_config = thinking.outputConfig;
2840
+ if (thinking.manualBudget) {
2166
2841
  const currentMax = body.max_tokens;
2167
- if (currentMax < budget + 1024) {
2168
- body.max_tokens = budget + 1024;
2842
+ if (currentMax < thinking.manualBudget + 1024) {
2843
+ body.max_tokens = thinking.manualBudget + 1024;
2169
2844
  }
2170
2845
  }
2171
- if (options.temperature !== void 0 && !body.thinking) {
2846
+ if (options.temperature !== void 0 && !thinking.stripTemperature) {
2172
2847
  body.temperature = options.temperature;
2173
2848
  }
2174
2849
  if (options.responseFormat && options.responseFormat.type !== "text") {
@@ -2193,8 +2868,9 @@ ${jsonInstruction}` : jsonInstruction;
2193
2868
  const result = [];
2194
2869
  for (const msg of messages) {
2195
2870
  if (msg.role === "assistant") {
2871
+ const replayedThinking = this.getReplayedThinkingBlocks(msg);
2196
2872
  if (msg.tool_calls && msg.tool_calls.length > 0) {
2197
- const content = [];
2873
+ const content = [...replayedThinking];
2198
2874
  if (msg.content) {
2199
2875
  content.push({ type: "text", text: msg.content });
2200
2876
  }
@@ -2213,6 +2889,10 @@ ${jsonInstruction}` : jsonInstruction;
2213
2889
  });
2214
2890
  }
2215
2891
  result.push({ role: "assistant", content });
2892
+ } else if (replayedThinking.length > 0) {
2893
+ const content = [...replayedThinking];
2894
+ if (msg.content) content.push({ type: "text", text: msg.content });
2895
+ result.push({ role: "assistant", content });
2216
2896
  } else {
2217
2897
  result.push({ role: "assistant", content: msg.content });
2218
2898
  }
@@ -2233,6 +2913,13 @@ ${jsonInstruction}` : jsonInstruction;
2233
2913
  }
2234
2914
  return this.mergeConsecutiveRoles(result);
2235
2915
  }
2916
+ getReplayedThinkingBlocks(msg) {
2917
+ const blocks = msg.providerMetadata?.anthropicThinkingBlocks;
2918
+ if (!Array.isArray(blocks)) return [];
2919
+ return blocks.filter(
2920
+ (block) => block !== null && typeof block === "object" && (block.type === "thinking" || block.type === "redacted_thinking")
2921
+ );
2922
+ }
2236
2923
  /**
2237
2924
  * Merge consecutive messages with the same role into a single message.
2238
2925
  * This handles cases where multiple tool_result blocks need to be in one user message.
@@ -2288,16 +2975,24 @@ ${jsonInstruction}` : jsonInstruction;
2288
2975
  // ---------------------------------------------------------------------------
2289
2976
  // Internal: response parsing
2290
2977
  // ---------------------------------------------------------------------------
2291
- parseResponse(json, model) {
2978
+ parseResponse(json, pricingContext) {
2292
2979
  let content = "";
2293
2980
  let thinkingContent = "";
2294
2981
  const toolCalls = [];
2295
- for (const block of json.content) {
2982
+ const thinkingBlocks = [];
2983
+ const fallbackIndex = lastFallbackIndex(json.content);
2984
+ const hasFallbackBoundary = fallbackIndex >= 0 || hasFallbackIteration(json.usage?.iterations);
2985
+ let refused = json.stop_reason === "refusal";
2986
+ for (const [index, block] of json.content.entries()) {
2296
2987
  if (block.type === "thinking") {
2297
2988
  thinkingContent += block.thinking;
2989
+ thinkingBlocks.push(block);
2990
+ } else if (block.type === "redacted_thinking") {
2991
+ thinkingBlocks.push(block);
2298
2992
  } else if (block.type === "text") {
2299
2993
  content += block.text;
2300
2994
  } else if (block.type === "tool_use") {
2995
+ if (fallbackIndex >= 0 && index < fallbackIndex) continue;
2301
2996
  toolCalls.push({
2302
2997
  id: block.id,
2303
2998
  type: "function",
@@ -2306,37 +3001,58 @@ ${jsonInstruction}` : jsonInstruction;
2306
3001
  arguments: JSON.stringify(block.input)
2307
3002
  }
2308
3003
  });
3004
+ } else if (block.type === "fallback") {
3005
+ } else if (block.type === "refusal") {
3006
+ refused = true;
2309
3007
  }
2310
3008
  }
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;
3009
+ const normalized = json.usage ? normalizeAnthropicUsage(json.usage) : void 0;
3010
+ const effectiveModel = typeof json.model === "string" ? json.model : pricingContext.model;
3011
+ if (hasFallbackBoundary) {
3012
+ throw new Error(
3013
+ "Anthropic fallback responses cannot be continued safely; retry without fallbacks."
3014
+ );
3015
+ }
3016
+ const cost = normalized && !pricingContext.unpricedModifier && !isModifiedAnthropicResponse(json, effectiveModel) && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, normalized.pricingUsage) : void 0;
2321
3017
  return {
2322
3018
  content,
2323
3019
  thinking_content: thinkingContent || void 0,
2324
3020
  tool_calls: toolCalls.length > 0 ? toolCalls : void 0,
2325
- usage,
2326
- cost
3021
+ usage: normalized?.usage,
3022
+ cost,
3023
+ providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
2327
3024
  };
2328
3025
  }
2329
3026
  // ---------------------------------------------------------------------------
2330
3027
  // Internal: SSE stream parsing
2331
3028
  // ---------------------------------------------------------------------------
2332
- async *parseSSEStream(body, model) {
3029
+ async *parseSSEStream(body, pricingContext) {
2333
3030
  const reader = body.getReader();
2334
3031
  const decoder = new TextDecoder();
2335
3032
  let buffer = "";
2336
3033
  let currentToolId = "";
2337
3034
  let currentToolName = "";
2338
3035
  let usage;
2339
- let cacheWrite = 0;
3036
+ let pricingUsage;
3037
+ let rawUsage;
3038
+ const thinkingBlocks = [];
3039
+ let currentThinkingBlock;
3040
+ let effectiveModel = pricingContext.model;
3041
+ let unpricedModifier = pricingContext.unpricedModifier;
3042
+ let hasFallbackBoundary = false;
3043
+ let hasFallbackIterationSignal = false;
3044
+ let refused = false;
3045
+ const finalizeUsage = () => {
3046
+ const normalized = rawUsage ? normalizeAnthropicUsage(rawUsage) : void 0;
3047
+ usage = normalized?.usage;
3048
+ pricingUsage = normalized?.pricingUsage;
3049
+ };
3050
+ const doneChunk = () => ({
3051
+ type: "done",
3052
+ usage,
3053
+ cost: pricingUsage && !unpricedModifier && !hasFallbackBoundary && !refused ? estimateAnthropicCost(effectiveModel, pricingUsage) : void 0,
3054
+ providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
3055
+ });
2340
3056
  try {
2341
3057
  while (true) {
2342
3058
  const { done, value } = await reader.read();
@@ -2367,13 +3083,31 @@ ${jsonInstruction}` : jsonInstruction;
2367
3083
  name: currentToolName,
2368
3084
  arguments: ""
2369
3085
  };
3086
+ } else if (block?.type === "thinking" || block?.type === "redacted_thinking") {
3087
+ currentThinkingBlock = {
3088
+ ...block,
3089
+ type: block.type
3090
+ };
3091
+ } else if (block?.type === "fallback") {
3092
+ hasFallbackBoundary = true;
3093
+ } else if (block?.type === "refusal") {
3094
+ refused = true;
2370
3095
  }
2371
3096
  break;
2372
3097
  }
2373
3098
  case "content_block_delta": {
2374
3099
  const delta = event.delta;
2375
3100
  if (delta?.type === "thinking_delta" && delta.thinking) {
3101
+ if (currentThinkingBlock?.type === "thinking") {
3102
+ const current = currentThinkingBlock.thinking;
3103
+ currentThinkingBlock.thinking = `${typeof current === "string" ? current : ""}${delta.thinking}`;
3104
+ }
2376
3105
  yield { type: "thinking_delta", content: delta.thinking };
3106
+ } else if (delta?.type === "signature_delta" && delta.signature) {
3107
+ if (currentThinkingBlock) {
3108
+ const current = currentThinkingBlock.signature;
3109
+ currentThinkingBlock.signature = `${typeof current === "string" ? current : ""}${delta.signature}`;
3110
+ }
2377
3111
  } else if (delta?.type === "text_delta" && delta.text) {
2378
3112
  yield { type: "text_delta", content: delta.text };
2379
3113
  } else if (delta?.type === "input_json_delta" && delta.partial_json) {
@@ -2386,76 +3120,82 @@ ${jsonInstruction}` : jsonInstruction;
2386
3120
  break;
2387
3121
  }
2388
3122
  case "content_block_stop": {
3123
+ if (currentThinkingBlock) {
3124
+ thinkingBlocks.push(currentThinkingBlock);
3125
+ currentThinkingBlock = void 0;
3126
+ }
2389
3127
  currentToolId = "";
2390
3128
  currentToolName = "";
2391
3129
  break;
2392
3130
  }
2393
3131
  case "message_start": {
2394
3132
  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
- };
3133
+ rawUsage = mergeAnthropicUsage(rawUsage, event.message.usage);
2404
3134
  }
3135
+ if (typeof event.message?.model === "string") effectiveModel = event.message.model;
3136
+ const messageFallback = hasFallbackIteration(event.message?.usage?.iterations);
3137
+ if (isAnthropicResponseModifier(
3138
+ "inference_geo",
3139
+ event.message?.inference_geo,
3140
+ effectiveModel
3141
+ ) || isAnthropicResponseModifier("speed", event.message?.speed, effectiveModel) || isAnthropicResponseModifier(
3142
+ "inference_geo",
3143
+ event.message?.usage?.inference_geo,
3144
+ effectiveModel
3145
+ ) || isAnthropicResponseModifier("speed", event.message?.usage?.speed, effectiveModel) || hasBilledUnmodeledUsage(event.message?.usage?.server_tool_use) || hasUnmodeledIterations(event.message?.usage?.iterations)) {
3146
+ unpricedModifier = true;
3147
+ }
3148
+ if (messageFallback) hasFallbackIterationSignal = true;
2405
3149
  break;
2406
3150
  }
2407
3151
  case "message_delta": {
2408
3152
  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
- };
3153
+ rawUsage = mergeAnthropicUsage(rawUsage, event.usage);
3154
+ finalizeUsage();
3155
+ const terminalFallback = hasFallbackIteration(event.usage.iterations);
3156
+ if (isAnthropicResponseModifier("speed", event.usage.speed, effectiveModel) || isAnthropicResponseModifier(
3157
+ "inference_geo",
3158
+ event.usage.inference_geo,
3159
+ effectiveModel
3160
+ ) || hasBilledUnmodeledUsage(event.usage.server_tool_use) || hasUnmodeledIterations(event.usage.iterations)) {
3161
+ unpricedModifier = true;
2419
3162
  }
3163
+ if (terminalFallback) hasFallbackIterationSignal = true;
2420
3164
  }
3165
+ if (event.delta?.stop_reason === "refusal") refused = true;
2421
3166
  break;
2422
3167
  }
2423
3168
  case "message_stop": {
2424
- if (usage) {
2425
- usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
3169
+ finalizeUsage();
3170
+ if (hasFallbackBoundary || hasFallbackIterationSignal) {
3171
+ throw new Error(
3172
+ "Anthropic fallback streams cannot be continued safely; retry without fallbacks."
3173
+ );
2426
3174
  }
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
- };
3175
+ yield doneChunk();
2438
3176
  return;
2439
3177
  }
2440
3178
  }
2441
3179
  }
2442
3180
  }
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
- };
3181
+ finalizeUsage();
3182
+ if (hasFallbackBoundary || hasFallbackIterationSignal) {
3183
+ throw new Error(
3184
+ "Anthropic fallback streams cannot be continued safely; retry without fallbacks."
3185
+ );
3186
+ }
3187
+ yield doneChunk();
2454
3188
  } finally {
2455
3189
  reader.releaseLock();
2456
3190
  }
2457
3191
  }
2458
3192
  };
3193
+ function lastFallbackIndex(blocks) {
3194
+ for (let index = blocks.length - 1; index >= 0; index--) {
3195
+ if (blocks[index].type === "fallback") return index;
3196
+ }
3197
+ return -1;
3198
+ }
2459
3199
 
2460
3200
  // src/providers/gemini.ts
2461
3201
  var GEMINI_DISALLOWED_SCHEMA_KEYS = /* @__PURE__ */ new Set([
@@ -2503,36 +3243,92 @@ function parseGeminiFunctionResponse(content) {
2503
3243
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { result: parsed };
2504
3244
  }
2505
3245
  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]
3246
+ "gemini-2.5-pro": {
3247
+ input: 125e-8,
3248
+ cached: 125e-9,
3249
+ output: 1e-5,
3250
+ longContext: { input: 25e-7, cached: 25e-8, output: 15e-6 }
3251
+ },
3252
+ "gemini-2.5-flash": { input: 3e-7, cached: 3e-8, output: 25e-7 },
3253
+ "gemini-2.5-flash-lite": { input: 1e-7, cached: 1e-8, output: 4e-7 },
3254
+ "gemini-3-flash-preview": { input: 5e-7, cached: 5e-8, output: 3e-6 },
3255
+ "gemini-3.1-pro-preview": {
3256
+ input: 2e-6,
3257
+ cached: 2e-7,
3258
+ output: 12e-6,
3259
+ longContext: { input: 4e-6, cached: 4e-7, output: 18e-6 }
3260
+ },
3261
+ "gemini-3.1-pro-preview-customtools": {
3262
+ input: 2e-6,
3263
+ cached: 2e-7,
3264
+ output: 12e-6,
3265
+ longContext: { input: 4e-6, cached: 4e-7, output: 18e-6 }
3266
+ },
3267
+ "gemini-3.1-flash-lite": { input: 25e-8, cached: 25e-9, output: 15e-7 },
3268
+ "gemini-3.5-flash": { input: 15e-7, cached: 15e-8, output: 9e-6 },
3269
+ "gemini-3.5-flash-lite": { input: 3e-7, cached: 3e-8, output: 25e-7 },
3270
+ "gemini-3.6-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 }
2517
3271
  };
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
- }
3272
+ function isValidTokenCount2(value) {
3273
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
3274
+ }
3275
+ function isStandardGeminiRequestTier(value) {
3276
+ return value === void 0 || value === "SERVICE_TIER_UNSPECIFIED" || value === "SERVICE_TIER_STANDARD";
3277
+ }
3278
+ function isDefinitiveStandardGeminiResponseTier(value) {
3279
+ return value === "SERVICE_TIER_UNSPECIFIED" || value === "SERVICE_TIER_STANDARD" || value === "standard";
3280
+ }
3281
+ function isEligibleGeminiPricing(context, hasDefinitiveStandardResponseTier, hasInvalidResponseTier) {
3282
+ return context.eligibleRequest && isStandardGeminiRequestTier(context.serviceTier) && hasDefinitiveStandardResponseTier && !hasInvalidResponseTier;
3283
+ }
3284
+ function estimateGeminiCost(model, usage) {
3285
+ const pricing = GEMINI_PRICING[model];
3286
+ const cached = usage.cachedTokens ?? 0;
3287
+ if (!pricing || !isValidTokenCount2(usage.inputTokens) || !isValidTokenCount2(usage.outputTokens) || !isValidTokenCount2(cached) || cached > usage.inputTokens) {
3288
+ return void 0;
2530
3289
  }
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;
3290
+ const rate = usage.inputTokens > 2e5 && pricing.longContext ? pricing.longContext : pricing;
3291
+ return (usage.inputTokens - cached) * rate.input + cached * rate.cached + usage.outputTokens * rate.output;
3292
+ }
3293
+ function safeTokenSum2(...values) {
3294
+ const total = values.reduce((sum, value) => sum + value, 0);
3295
+ return Number.isSafeInteger(total) ? total : void 0;
3296
+ }
3297
+ function normalizeGeminiUsage(raw) {
3298
+ const inputTokens = raw.promptTokenCount;
3299
+ const completionTokens = raw.candidatesTokenCount;
3300
+ const totalTokens = raw.totalTokenCount;
3301
+ const cachedTokens = raw.cachedContentTokenCount ?? 0;
3302
+ const reasoningTokens = raw.thoughtsTokenCount ?? 0;
3303
+ const toolUsePromptTokens = raw.toolUsePromptTokenCount ?? 0;
3304
+ if (!isValidTokenCount2(inputTokens) || !isValidTokenCount2(completionTokens) || !isValidTokenCount2(totalTokens) || !isValidTokenCount2(cachedTokens) || !isValidTokenCount2(reasoningTokens) || !isValidTokenCount2(toolUsePromptTokens) || cachedTokens > inputTokens) {
3305
+ return void 0;
3306
+ }
3307
+ const billedOutputTokens = safeTokenSum2(completionTokens, reasoningTokens);
3308
+ const expectedTotal = safeTokenSum2(
3309
+ inputTokens,
3310
+ completionTokens,
3311
+ reasoningTokens,
3312
+ toolUsePromptTokens
3313
+ );
3314
+ if (billedOutputTokens === void 0 || expectedTotal === void 0 || totalTokens !== expectedTotal) {
3315
+ return void 0;
3316
+ }
3317
+ return {
3318
+ usage: {
3319
+ prompt_tokens: inputTokens,
3320
+ completion_tokens: completionTokens,
3321
+ total_tokens: totalTokens,
3322
+ cached_tokens: cachedTokens > 0 ? cachedTokens : void 0,
3323
+ reasoning_tokens: reasoningTokens > 0 ? reasoningTokens : void 0
3324
+ },
3325
+ pricingUsage: {
3326
+ inputTokens,
3327
+ outputTokens: billedOutputTokens,
3328
+ cachedTokens
3329
+ },
3330
+ hasUnmodeledBilledUsage: toolUsePromptTokens !== 0
3331
+ };
2536
3332
  }
2537
3333
  var THINKING_BUDGETS2 = {
2538
3334
  low: 1024,
@@ -2551,8 +3347,23 @@ var THINKING_LEVELS = {
2551
3347
  max: "high"
2552
3348
  // 3.x caps at 'high'
2553
3349
  };
3350
+ var GEMINI_3X_MIN_THINKING_LEVEL = /* @__PURE__ */ new Map([
3351
+ ["gemini-3-pro-preview", "minimal"],
3352
+ ["gemini-3-flash-preview", "minimal"],
3353
+ ["gemini-3.1-pro-preview", "low"],
3354
+ ["gemini-3.1-pro-preview-customtools", "low"],
3355
+ ["gemini-3.1-flash-lite", "minimal"],
3356
+ ["gemini-3.1-flash-lite-preview", "minimal"],
3357
+ ["gemini-3.5-flash", "minimal"],
3358
+ ["gemini-3.5-flash-lite", "minimal"],
3359
+ ["gemini-3.6-flash", "minimal"]
3360
+ ]);
3361
+ var GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE = /* @__PURE__ */ new Set([
3362
+ "gemini-3.5-flash-lite",
3363
+ "gemini-3.6-flash"
3364
+ ]);
2554
3365
  function isGemini3x(model) {
2555
- return /^gemini-3[.-]/.test(model);
3366
+ return GEMINI_3X_MIN_THINKING_LEVEL.has(model);
2556
3367
  }
2557
3368
  function budgetToThinkingLevel(budgetTokens) {
2558
3369
  if (budgetTokens <= 1024) return "low";
@@ -2560,8 +3371,7 @@ function budgetToThinkingLevel(budgetTokens) {
2560
3371
  return "high";
2561
3372
  }
2562
3373
  function minThinkingLevel(model) {
2563
- if (model.startsWith("gemini-3.1-pro")) return "low";
2564
- return "minimal";
3374
+ return GEMINI_3X_MIN_THINKING_LEVEL.get(model) ?? "minimal";
2565
3375
  }
2566
3376
  var _warned3xEffortNone = /* @__PURE__ */ new Set();
2567
3377
  function warnGemini3xEffortNone(model) {
@@ -2606,10 +3416,12 @@ var GeminiProvider = class {
2606
3416
  // chat - non-streaming completion
2607
3417
  // ---------------------------------------------------------------------------
2608
3418
  async chat(messages, options) {
2609
- const headers = this.buildHeaders(await this.resolveKey());
2610
3419
  const body = this.buildRequestBody(messages, options);
3420
+ const pricingContext = this.pricingContext(this.requestModel(options), body);
3421
+ this.assertSafeGemini3Continuation(messages, pricingContext.model);
3422
+ const headers = this.buildHeaders(await this.resolveKey());
2611
3423
  const res = await fetchWithRetry(
2612
- `${this.baseUrl}/models/${options.model}:generateContent`,
3424
+ `${this.baseUrl}/models/${pricingContext.model}:generateContent`,
2613
3425
  {
2614
3426
  method: "POST",
2615
3427
  headers,
@@ -2630,16 +3442,18 @@ var GeminiProvider = class {
2630
3442
  });
2631
3443
  }
2632
3444
  const json = await res.json();
2633
- return this.parseResponse(json, options.model);
3445
+ return this.parseResponse(json, pricingContext);
2634
3446
  }
2635
3447
  // ---------------------------------------------------------------------------
2636
3448
  // stream - SSE streaming completion
2637
3449
  // ---------------------------------------------------------------------------
2638
3450
  async *stream(messages, options) {
2639
- const headers = this.buildHeaders(await this.resolveKey());
2640
3451
  const body = this.buildRequestBody(messages, options);
3452
+ const pricingContext = this.pricingContext(this.requestModel(options), body);
3453
+ this.assertSafeGemini3Continuation(messages, pricingContext.model);
3454
+ const headers = this.buildHeaders(await this.resolveKey());
2641
3455
  const res = await fetchWithRetry(
2642
- `${this.baseUrl}/models/${options.model}:streamGenerateContent?alt=sse`,
3456
+ `${this.baseUrl}/models/${pricingContext.model}:streamGenerateContent?alt=sse`,
2643
3457
  {
2644
3458
  method: "POST",
2645
3459
  headers,
@@ -2662,7 +3476,7 @@ var GeminiProvider = class {
2662
3476
  if (!res.body) {
2663
3477
  throw new Error("Gemini stream response has no body");
2664
3478
  }
2665
- yield* this.parseSSEStream(res.body, options.model);
3479
+ yield* this.parseSSEStream(res.body, pricingContext);
2666
3480
  }
2667
3481
  // ---------------------------------------------------------------------------
2668
3482
  // Internal: request building
@@ -2683,7 +3497,114 @@ var GeminiProvider = class {
2683
3497
  }
2684
3498
  return `Gemini API error (${status}): ${body}`;
2685
3499
  }
3500
+ pricingContext(fallbackModel, body) {
3501
+ return {
3502
+ // Gemini's model is normally selected in the URL. providerOptions is an
3503
+ // explicit raw escape hatch, so honor a post-merge model override for both
3504
+ // the URL and pricing rather than pricing a different request.
3505
+ model: typeof body.model === "string" ? body.model : fallbackModel,
3506
+ serviceTier: body.serviceTier,
3507
+ eligibleRequest: this.baseUrl === "https://generativelanguage.googleapis.com/v1beta" && this.isTextOnlyClientRequest(body)
3508
+ };
3509
+ }
3510
+ requestModel(options) {
3511
+ return typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
3512
+ }
3513
+ assertSafeGemini3Continuation(messages, model) {
3514
+ if (GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE.has(model)) {
3515
+ const mapped = this.mapMessages(messages.filter((message) => message.role !== "system"));
3516
+ if (mapped.at(-1)?.role === "model") {
3517
+ throw new Error(
3518
+ `${model} does not support a terminal assistant/model prefill; end the request with a user or tool message.`
3519
+ );
3520
+ }
3521
+ }
3522
+ if (!isGemini3x(model)) return;
3523
+ const replayedNativeCallIds = /* @__PURE__ */ new Set();
3524
+ for (const message of messages) {
3525
+ if (message.role !== "assistant" || !Array.isArray(message.providerMetadata?.geminiParts)) {
3526
+ continue;
3527
+ }
3528
+ for (const part of message.providerMetadata.geminiParts) {
3529
+ const id = part !== null && typeof part === "object" ? part.functionCall?.id : void 0;
3530
+ if (!id) continue;
3531
+ if (replayedNativeCallIds.has(id)) {
3532
+ throw new Error(
3533
+ `${model} native functionCall ids must be globally unique across a replay.`
3534
+ );
3535
+ }
3536
+ replayedNativeCallIds.add(id);
3537
+ }
3538
+ }
3539
+ const consumedToolMessages = /* @__PURE__ */ new Set();
3540
+ for (const [assistantIndex, message] of messages.entries()) {
3541
+ if (message.role !== "assistant" || !message.tool_calls?.length) continue;
3542
+ const rawParts = message.providerMetadata?.geminiParts;
3543
+ if (!Array.isArray(rawParts)) {
3544
+ throw new Error(
3545
+ `${model} tool continuations require providerMetadata.geminiParts with thought signatures.`
3546
+ );
3547
+ }
3548
+ const nativeCalls = rawParts.flatMap((part) => {
3549
+ const functionCall = part !== null && typeof part === "object" ? part.functionCall : void 0;
3550
+ return functionCall?.id ? [{ id: functionCall.id, name: functionCall.name, part }] : [];
3551
+ });
3552
+ const expectedCalls = new Map(
3553
+ message.tool_calls.map((call) => [call.id, call.function.name])
3554
+ );
3555
+ 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)) {
3556
+ throw new Error(
3557
+ `${model} tool continuations require exact native functionCall id/name and a thoughtSignature on the first function call.`
3558
+ );
3559
+ }
3560
+ const seenResponses = /* @__PURE__ */ new Set();
3561
+ let toolIndex = assistantIndex + 1;
3562
+ while (messages[toolIndex]?.role === "tool") {
3563
+ const toolMessage = messages[toolIndex];
3564
+ const toolCallId = toolMessage.tool_call_id;
3565
+ if (!toolCallId || !expectedCalls.has(toolCallId) || seenResponses.has(toolCallId)) {
3566
+ throw new Error(
3567
+ `${model} functionResponse requires exactly one matching native functionCall id and name.`
3568
+ );
3569
+ }
3570
+ seenResponses.add(toolCallId);
3571
+ consumedToolMessages.add(toolIndex);
3572
+ toolIndex++;
3573
+ }
3574
+ if (seenResponses.size !== expectedCalls.size) {
3575
+ throw new Error(
3576
+ `${model} tool continuations require one contiguous functionResponse for every native functionCall.`
3577
+ );
3578
+ }
3579
+ }
3580
+ for (const [index, message] of messages.entries()) {
3581
+ if (message.role === "tool" && !consumedToolMessages.has(index)) {
3582
+ throw new Error(
3583
+ `${model} functionResponse requires exactly one matching native functionCall id and name.`
3584
+ );
3585
+ }
3586
+ }
3587
+ }
3588
+ isTextOnlyClientRequest(body) {
3589
+ if (!Array.isArray(body.contents) || body.cachedContent !== void 0) return false;
3590
+ if (body.contents.some(
3591
+ (content) => content === null || typeof content !== "object" || !Array.isArray(content.parts) || content.parts.some((part) => this.isNonTextGeminiPart(part))
3592
+ )) {
3593
+ return false;
3594
+ }
3595
+ if (body.tools !== void 0 && (!Array.isArray(body.tools) || body.tools.some(
3596
+ (tool2) => tool2 === null || typeof tool2 !== "object" || !Array.isArray(tool2.functionDeclarations) || Object.keys(tool2).some((key) => key !== "functionDeclarations")
3597
+ ))) {
3598
+ return false;
3599
+ }
3600
+ const modalities = body.generationConfig?.responseModalities;
3601
+ return modalities === void 0 || modalities === "TEXT" || Array.isArray(modalities) && modalities.length === 1 && modalities[0] === "TEXT";
3602
+ }
3603
+ isNonTextGeminiPart(part) {
3604
+ return part === null || typeof part !== "object" || ["inlineData", "fileData", "executableCode", "codeExecutionResult"].some((key) => key in part);
3605
+ }
2686
3606
  buildRequestBody(messages, options) {
3607
+ const requestModel = this.requestModel(options);
2687
3608
  const systemMessages = messages.filter((m) => m.role === "system");
2688
3609
  const nonSystemMessages = messages.filter((m) => m.role !== "system");
2689
3610
  const systemText = systemMessages.map((m) => m.content).join("\n\n");
@@ -2701,7 +3622,7 @@ var GeminiProvider = class {
2701
3622
  ];
2702
3623
  }
2703
3624
  const generationConfig = {};
2704
- if (options.temperature !== void 0) {
3625
+ if (options.temperature !== void 0 && !GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE.has(requestModel)) {
2705
3626
  generationConfig.temperature = options.temperature;
2706
3627
  }
2707
3628
  if (options.maxTokens !== void 0) {
@@ -2730,18 +3651,18 @@ var GeminiProvider = class {
2730
3651
  hasBudgetOverride
2731
3652
  } = resolveThinkingOptions(options);
2732
3653
  if (thinkingDisabled) {
2733
- if (isGemini3x(options.model)) {
3654
+ if (isGemini3x(requestModel)) {
2734
3655
  if (effort === "none") {
2735
- warnGemini3xEffortNone(options.model);
3656
+ warnGemini3xEffortNone(requestModel);
2736
3657
  }
2737
- generationConfig.thinkingConfig = { thinkingLevel: minThinkingLevel(options.model) };
3658
+ generationConfig.thinkingConfig = { thinkingLevel: minThinkingLevel(requestModel) };
2738
3659
  } else {
2739
3660
  generationConfig.thinkingConfig = { thinkingBudget: 0 };
2740
3661
  }
2741
3662
  if (!body.generationConfig) body.generationConfig = generationConfig;
2742
3663
  } else if (hasBudgetOverride) {
2743
3664
  const config = {};
2744
- if (isGemini3x(options.model)) {
3665
+ if (isGemini3x(requestModel)) {
2745
3666
  config.thinkingLevel = budgetToThinkingLevel(thinkingBudget);
2746
3667
  } else {
2747
3668
  config.thinkingBudget = thinkingBudget;
@@ -2751,10 +3672,10 @@ var GeminiProvider = class {
2751
3672
  if (!body.generationConfig) body.generationConfig = generationConfig;
2752
3673
  } else if (activeEffort) {
2753
3674
  const config = {};
2754
- if (isGemini3x(options.model)) {
3675
+ if (isGemini3x(requestModel)) {
2755
3676
  config.thinkingLevel = THINKING_LEVELS[activeEffort] ?? "medium";
2756
3677
  } else {
2757
- if (activeEffort === "max" && options.model.startsWith("gemini-2.5-pro")) {
3678
+ if (activeEffort === "max" && requestModel === "gemini-2.5-pro") {
2758
3679
  config.thinkingBudget = 32768;
2759
3680
  } else {
2760
3681
  config.thinkingBudget = THINKING_BUDGETS2[activeEffort] ?? 5e3;
@@ -2771,7 +3692,9 @@ var GeminiProvider = class {
2771
3692
  body.toolConfig = { functionCallingConfig: this.mapToolChoice(options.toolChoice) };
2772
3693
  }
2773
3694
  if (options.providerOptions) {
2774
- Object.assign(body, options.providerOptions);
3695
+ const bodyOptions = { ...options.providerOptions };
3696
+ delete bodyOptions.model;
3697
+ Object.assign(body, bodyOptions);
2775
3698
  }
2776
3699
  return body;
2777
3700
  }
@@ -2789,6 +3712,7 @@ var GeminiProvider = class {
2789
3712
  mapMessages(messages) {
2790
3713
  const toolCallIdToName = /* @__PURE__ */ new Map();
2791
3714
  const geminiNativeIds = /* @__PURE__ */ new Set();
3715
+ const geminiNativeNames = /* @__PURE__ */ new Map();
2792
3716
  for (const msg of messages) {
2793
3717
  if (msg.role === "assistant" && msg.tool_calls) {
2794
3718
  for (const tc of msg.tool_calls) {
@@ -2799,7 +3723,10 @@ var GeminiProvider = class {
2799
3723
  const rawParts = msg.providerMetadata?.geminiParts;
2800
3724
  if (rawParts) {
2801
3725
  for (const p of rawParts) {
2802
- if (p.functionCall?.id) geminiNativeIds.add(p.functionCall.id);
3726
+ if (p.functionCall?.id) {
3727
+ geminiNativeIds.add(p.functionCall.id);
3728
+ geminiNativeNames.set(p.functionCall.id, p.functionCall.name);
3729
+ }
2803
3730
  }
2804
3731
  }
2805
3732
  }
@@ -2836,7 +3763,7 @@ var GeminiProvider = class {
2836
3763
  }
2837
3764
  }
2838
3765
  } else if (msg.role === "tool") {
2839
- const functionName = toolCallIdToName.get(msg.tool_call_id) ?? "unknown";
3766
+ const functionName = (msg.tool_call_id ? geminiNativeNames.get(msg.tool_call_id) : void 0) ?? toolCallIdToName.get(msg.tool_call_id) ?? "unknown";
2840
3767
  const functionResponse = {
2841
3768
  name: functionName,
2842
3769
  response: parseGeminiFunctionResponse(msg.content)
@@ -2901,7 +3828,7 @@ var GeminiProvider = class {
2901
3828
  // ---------------------------------------------------------------------------
2902
3829
  // Internal: response parsing
2903
3830
  // ---------------------------------------------------------------------------
2904
- parseResponse(json, model) {
3831
+ parseResponse(json, pricingContext) {
2905
3832
  const candidate = json.candidates?.[0];
2906
3833
  let content = "";
2907
3834
  let thinkingContent = "";
@@ -2924,35 +3851,46 @@ var GeminiProvider = class {
2924
3851
  }
2925
3852
  }
2926
3853
  }
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;
3854
+ if (!candidate) {
3855
+ throw new Error("Gemini response did not include a candidate.");
3856
+ }
3857
+ if (candidate.finishReason && !["STOP", "MAX_TOKENS"].includes(candidate.finishReason)) {
3858
+ throw new Error(`Gemini returned non-success finish reason: ${candidate.finishReason}`);
3859
+ }
3860
+ const normalized = json.usageMetadata ? normalizeGeminiUsage(json.usageMetadata) : void 0;
3861
+ const effectiveModel = typeof json.modelVersion === "string" ? json.modelVersion : pricingContext.model;
3862
+ const cost = normalized && !normalized.hasUnmodeledBilledUsage && this.isTextOnlyGeminiResponse(candidate) && isEligibleGeminiPricing(
3863
+ pricingContext,
3864
+ isDefinitiveStandardGeminiResponseTier(json.usageMetadata?.serviceTier),
3865
+ json.usageMetadata?.serviceTier !== void 0 && !isDefinitiveStandardGeminiResponseTier(json.usageMetadata.serviceTier)
3866
+ ) ? estimateGeminiCost(effectiveModel, normalized.pricingUsage) : void 0;
2937
3867
  const rawParts = candidate?.content?.parts;
2938
3868
  const providerMetadata = rawParts ? { geminiParts: rawParts } : void 0;
2939
3869
  return {
2940
3870
  content,
2941
3871
  thinking_content: thinkingContent || void 0,
2942
3872
  tool_calls: toolCalls.length > 0 ? toolCalls : void 0,
2943
- usage,
3873
+ usage: normalized?.usage,
2944
3874
  cost,
2945
3875
  providerMetadata
2946
3876
  };
2947
3877
  }
3878
+ isTextOnlyGeminiResponse(candidate) {
3879
+ return candidate?.content?.parts.every((part) => !this.isNonTextGeminiPart(part)) ?? false;
3880
+ }
2948
3881
  // ---------------------------------------------------------------------------
2949
3882
  // Internal: SSE stream parsing
2950
3883
  // ---------------------------------------------------------------------------
2951
- async *parseSSEStream(body, model) {
3884
+ async *parseSSEStream(body, pricingContext) {
2952
3885
  const reader = body.getReader();
2953
3886
  const decoder = new TextDecoder();
2954
3887
  let buffer = "";
2955
- let usage;
3888
+ let normalizedUsage;
3889
+ let effectiveModel = pricingContext.model;
3890
+ let hasDefinitiveStandardResponseTier = false;
3891
+ let hasInvalidResponseTier = false;
3892
+ let responseIsTextOnly = true;
3893
+ let terminalFailure;
2956
3894
  const accumulatedParts = [];
2957
3895
  try {
2958
3896
  while (true) {
@@ -2972,20 +3910,21 @@ var GeminiProvider = class {
2972
3910
  } catch {
2973
3911
  continue;
2974
3912
  }
3913
+ if (typeof chunk.modelVersion === "string") effectiveModel = chunk.modelVersion;
2975
3914
  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
- };
3915
+ normalizedUsage = normalizeGeminiUsage(chunk.usageMetadata);
3916
+ if (chunk.usageMetadata.serviceTier !== void 0) {
3917
+ if (isDefinitiveStandardGeminiResponseTier(chunk.usageMetadata.serviceTier)) {
3918
+ hasDefinitiveStandardResponseTier = true;
3919
+ } else {
3920
+ hasInvalidResponseTier = true;
3921
+ }
3922
+ }
2985
3923
  }
2986
3924
  const candidate = chunk.candidates?.[0];
2987
3925
  if (candidate?.content?.parts) {
2988
3926
  for (const part of candidate.content.parts) {
3927
+ if (this.isNonTextGeminiPart(part)) responseIsTextOnly = false;
2989
3928
  accumulatedParts.push(part);
2990
3929
  if (part.thought && part.text) {
2991
3930
  yield { type: "thinking_delta", content: part.text };
@@ -3001,18 +3940,23 @@ var GeminiProvider = class {
3001
3940
  }
3002
3941
  }
3003
3942
  }
3943
+ if (candidate?.finishReason && !["STOP", "MAX_TOKENS"].includes(candidate.finishReason)) {
3944
+ terminalFailure = candidate.finishReason;
3945
+ }
3004
3946
  }
3005
3947
  }
3948
+ if (terminalFailure) {
3949
+ throw new Error(`Gemini returned non-success finish reason: ${terminalFailure}`);
3950
+ }
3006
3951
  const providerMetadata = accumulatedParts.length > 0 ? { geminiParts: accumulatedParts } : void 0;
3007
3952
  yield {
3008
3953
  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,
3954
+ usage: normalizedUsage?.usage,
3955
+ cost: normalizedUsage && !normalizedUsage.hasUnmodeledBilledUsage && responseIsTextOnly && isEligibleGeminiPricing(
3956
+ pricingContext,
3957
+ hasDefinitiveStandardResponseTier,
3958
+ hasInvalidResponseTier
3959
+ ) ? estimateGeminiCost(effectiveModel, normalizedUsage.pricingUsage) : void 0,
3016
3960
  providerMetadata
3017
3961
  };
3018
3962
  } finally {
@@ -3029,8 +3973,6 @@ var OPENROUTER_PROFILE = {
3029
3973
  envApiKey: "OPENROUTER_API_KEY",
3030
3974
  envBaseUrl: "OPENROUTER_BASE_URL",
3031
3975
  pricing: { kind: "from-response" },
3032
- // Ask OpenRouter to report per-call cost in usage.cost.
3033
- requestDefaults: { usage: { include: true } },
3034
3976
  reasoning: {
3035
3977
  // OpenRouter's effort vocabulary is low|medium|high; xhigh/max clamp to high.
3036
3978
  emit: reasoningObjectEmit(
@@ -3053,7 +3995,7 @@ var AZURE_PROFILE = {
3053
3995
  envApiKey: "AZURE_OPENAI_API_KEY",
3054
3996
  envBaseUrl: "AZURE_OPENAI_BASE_URL",
3055
3997
  authHeader: "api-key",
3056
- pricing: { kind: "table", table: OPENAI_PRICING },
3998
+ pricing: { kind: "unknown" },
3057
3999
  reasoning: { emit: openaiReasoningEmit, capture: "none" },
3058
4000
  roleFor: (role, model) => role === "system" && isOSeriesModel(model) ? "developer" : role,
3059
4001
  maxTokensField: "max_completion_tokens",
@@ -3061,29 +4003,100 @@ var AZURE_PROFILE = {
3061
4003
  };
3062
4004
 
3063
4005
  // src/providers/profiles/xai.ts
3064
- var acceptsReasoningEffort = (model) => model.startsWith("grok-3-mini");
3065
- var isReasoningModel = (model) => /^grok-(3-mini|4)/.test(model);
4006
+ var GROK_45 = /* @__PURE__ */ new Set(["grok-4.5", "grok-4.5-latest", "grok-build-latest"]);
4007
+ var GROK_43 = /* @__PURE__ */ new Set(["grok-4.3", "grok-4.3-latest", "grok-latest"]);
4008
+ var GROK_420_REASONING = /* @__PURE__ */ new Set([
4009
+ "grok-4.20-0309-reasoning",
4010
+ "grok-4.20-reasoning-latest",
4011
+ "grok-4.20",
4012
+ "grok-4.20-reasoning",
4013
+ "grok-4.20-0309",
4014
+ "grok-4.20-beta-0309-reasoning",
4015
+ "grok-4.20-beta",
4016
+ "grok-4.20-beta-0309",
4017
+ "grok-4.20-beta-latest",
4018
+ "grok-4.20-beta-latest-reasoning",
4019
+ "grok-4.20-beta-reasoning",
4020
+ "grok-4.20-experimental-beta-0304-reasoning",
4021
+ "grok-4.20-experimental-beta-0304",
4022
+ "grok-4.20-experimental-beta-reasoning-latest",
4023
+ "grok-4.20-experimental-beta-latest",
4024
+ "grok-4.20-reasoning-gv2"
4025
+ ]);
4026
+ var GROK_420_NON_REASONING = /* @__PURE__ */ new Set([
4027
+ "grok-4.20-0309-non-reasoning",
4028
+ "grok-4.20-non-reasoning",
4029
+ "grok-4.20-non-reasoning-latest",
4030
+ "grok-4.20-beta-non-reasoning",
4031
+ "grok-4.20-beta-latest-non-reasoning",
4032
+ "grok-4.20-experimental-beta-0304-non-reasoning",
4033
+ "grok-4.20-experimental-beta-non-reasoning-latest",
4034
+ "grok-4.20-beta-0309-non-reasoning",
4035
+ "grok-4.20-non-reasoning-gv2"
4036
+ ]);
4037
+ var warnedIrreducible45 = /* @__PURE__ */ new Set();
4038
+ var warnedEffortClamp = /* @__PURE__ */ new Set();
4039
+ function warnIrreducible45(model) {
4040
+ if (warnedIrreducible45.has(model)) return;
4041
+ warnedIrreducible45.add(model);
4042
+ console.warn(
4043
+ `[axl] effort: 'none' on xAI ${model} maps to 'low'; Grok 4.5 cannot disable reasoning.`
4044
+ );
4045
+ }
4046
+ function warnEffortClamp(model, effort) {
4047
+ const key = `${model}:${effort}`;
4048
+ if (warnedEffortClamp.has(key)) return;
4049
+ warnedEffortClamp.add(key);
4050
+ console.warn(
4051
+ `[axl] effort: '${effort}' on xAI ${model} maps to 'high'; this model supports at most 'high'.`
4052
+ );
4053
+ }
4054
+ var isReasoningChatModel = (model) => GROK_45.has(model) || GROK_43.has(model) || GROK_420_REASONING.has(model);
3066
4055
  var XAI_PROFILE = {
3067
4056
  name: "xai",
3068
4057
  label: "xAI",
3069
4058
  defaultBaseUrl: "https://api.x.ai/v1",
3070
4059
  envApiKey: "XAI_API_KEY",
3071
4060
  envBaseUrl: "XAI_BASE_URL",
3072
- pricing: { kind: "unknown" },
4061
+ pricing: { kind: "from-response" },
3073
4062
  reasoning: {
3074
4063
  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";
4064
+ if (GROK_45.has(model)) {
4065
+ if (resolved.thinkingDisabled) {
4066
+ warnIrreducible45(model);
4067
+ return "low";
4068
+ }
4069
+ if (!resolved.activeEffort) return void 0;
4070
+ if (resolved.activeEffort === "xhigh" || resolved.activeEffort === "max") {
4071
+ warnEffortClamp(model, resolved.activeEffort);
4072
+ return "high";
4073
+ }
4074
+ return resolved.activeEffort;
4075
+ }
4076
+ if (GROK_43.has(model)) {
4077
+ if (resolved.thinkingDisabled) return "none";
4078
+ if (!resolved.activeEffort) return void 0;
4079
+ if (resolved.activeEffort === "xhigh" || resolved.activeEffort === "max") {
4080
+ warnEffortClamp(model, resolved.activeEffort);
4081
+ return "high";
4082
+ }
4083
+ return resolved.activeEffort;
4084
+ }
4085
+ if (GROK_420_NON_REASONING.has(model)) return void 0;
4086
+ return void 0;
3078
4087
  }),
3079
4088
  capture: "reasoning"
3080
4089
  },
3081
4090
  capabilities: {
3082
- forbiddenParams: (model) => isReasoningModel(model) ? ["stop", "presence_penalty", "frequency_penalty"] : []
4091
+ forbiddenParams: (model) => isReasoningChatModel(model) ? ["stop", "presence_penalty", "frequency_penalty"] : []
3083
4092
  }
3084
4093
  };
3085
4094
 
3086
4095
  // src/providers/profiles/deepseek.ts
4096
+ var DEEPSEEK_PRICING = {
4097
+ "deepseek-v4-flash": [14e-8, 28e-8, 0.02],
4098
+ "deepseek-v4-pro": [435e-9, 87e-8, 1 / 120]
4099
+ };
3087
4100
  var isReasoner = (model) => /reasoner|v4/i.test(model);
3088
4101
  var deepseekEmit = (_body, _resolved, model) => ({
3089
4102
  stripTemperature: isReasoner(model)
@@ -3094,7 +4107,7 @@ var DEEPSEEK_PROFILE = {
3094
4107
  defaultBaseUrl: "https://api.deepseek.com/v1",
3095
4108
  envApiKey: "DEEPSEEK_API_KEY",
3096
4109
  envBaseUrl: "DEEPSEEK_BASE_URL",
3097
- pricing: { kind: "unknown" },
4110
+ pricing: { kind: "table", table: DEEPSEEK_PRICING, match: "exact" },
3098
4111
  reasoning: {
3099
4112
  emit: deepseekEmit,
3100
4113
  capture: "reasoning_content",
@@ -3105,27 +4118,56 @@ var DEEPSEEK_PROFILE = {
3105
4118
  forbiddenParams: (model) => isReasoner(model) ? ["top_p", "presence_penalty", "frequency_penalty"] : []
3106
4119
  }
3107
4120
  };
4121
+ attachBuiltinTablePricingPolicy(DEEPSEEK_PROFILE, {
4122
+ canonicalBaseUrl: "https://api.deepseek.com/v1",
4123
+ requireCacheSplit: true
4124
+ });
3108
4125
 
3109
4126
  // src/providers/profiles/mistral.ts
3110
- var acceptsReasoningEffort2 = (model) => /^mistral-(small|medium)/i.test(model);
4127
+ var MISTRAL_PRICING = {
4128
+ "mistral-medium-latest": [15e-7, 75e-7, 0.1],
4129
+ "mistral-small-latest": [15e-8, 6e-7, 0.1],
4130
+ "mistral-large-latest": [5e-7, 15e-7, 0.1],
4131
+ "devstral-medium-latest": [4e-7, 2e-6, 0.1],
4132
+ "devstral-small-latest": [1e-7, 3e-7, 0.1],
4133
+ "codestral-latest": [3e-7, 9e-7, 0.1],
4134
+ "magistral-medium-latest": [2e-6, 5e-6, 0.1],
4135
+ "magistral-small-latest": [5e-7, 15e-7, 0.1],
4136
+ "ministral-3b-latest": [1e-7, 1e-7, 0.1],
4137
+ "ministral-8b-latest": [15e-8, 15e-8, 0.1],
4138
+ "ministral-14b-latest": [2e-7, 2e-7, 0.1],
4139
+ "open-mistral-nemo": [15e-8, 15e-8, 0.1],
4140
+ "open-mixtral-8x7b": [7e-7, 7e-7, 0.1],
4141
+ "open-mixtral-8x22b": [2e-6, 6e-6, 0.1]
4142
+ };
4143
+ var acceptsReasoningEffort = (model) => /^mistral-(small|medium)/i.test(model);
3111
4144
  var MISTRAL_PROFILE = {
3112
4145
  name: "mistral",
3113
4146
  label: "Mistral",
3114
4147
  defaultBaseUrl: "https://api.mistral.ai/v1",
3115
4148
  envApiKey: "MISTRAL_API_KEY",
3116
4149
  envBaseUrl: "MISTRAL_BASE_URL",
3117
- pricing: { kind: "unknown" },
4150
+ pricing: { kind: "table", table: MISTRAL_PRICING, match: "exact" },
3118
4151
  reasoning: {
3119
4152
  emit: reasoningEffortEmit((resolved, model) => {
3120
- if (!acceptsReasoningEffort2(model)) return void 0;
4153
+ if (!acceptsReasoningEffort(model)) return void 0;
3121
4154
  if (resolved.thinkingDisabled || !resolved.activeEffort) return void 0;
3122
4155
  return "high";
3123
4156
  }),
3124
4157
  capture: "reasoning_content"
3125
4158
  }
3126
4159
  };
4160
+ attachBuiltinTablePricingPolicy(MISTRAL_PROFILE, {
4161
+ canonicalBaseUrl: "https://api.mistral.ai/v1"
4162
+ });
3127
4163
 
3128
4164
  // src/providers/profiles/groq.ts
4165
+ var GROQ_PRICING = {
4166
+ "llama-3.1-8b-instant": [5e-8, 8e-8, 1],
4167
+ "llama-3.3-70b-versatile": [59e-8, 79e-8, 1],
4168
+ "openai/gpt-oss-120b": [15e-8, 6e-7, 0.5],
4169
+ "openai/gpt-oss-20b": [75e-9, 3e-7, 0.5]
4170
+ };
3129
4171
  var isGptOss = (model) => /gpt-oss/i.test(model);
3130
4172
  var GROQ_PROFILE = {
3131
4173
  name: "groq",
@@ -3133,7 +4175,11 @@ var GROQ_PROFILE = {
3133
4175
  defaultBaseUrl: "https://api.groq.com/openai/v1",
3134
4176
  envApiKey: "GROQ_API_KEY",
3135
4177
  envBaseUrl: "GROQ_BASE_URL",
3136
- pricing: { kind: "unknown" },
4178
+ pricing: {
4179
+ kind: "table",
4180
+ table: GROQ_PRICING,
4181
+ match: "exact"
4182
+ },
3137
4183
  reasoning: {
3138
4184
  emit: reasoningEffortEmit((resolved, model) => {
3139
4185
  if (!isGptOss(model) || resolved.thinkingDisabled || !resolved.activeEffort) {
@@ -3154,6 +4200,21 @@ var GROQ_PROFILE = {
3154
4200
  supportsJsonSchema: isGptOss
3155
4201
  }
3156
4202
  };
4203
+ attachBuiltinTablePricingPolicy(GROQ_PROFILE, {
4204
+ canonicalBaseUrl: "https://api.groq.com/openai/v1",
4205
+ supportedServiceTiers: ["on_demand", "flex"],
4206
+ functionToolsOnly: true,
4207
+ chargedRequestModifiers: [
4208
+ "browser_search",
4209
+ "code_interpreter",
4210
+ "web_search",
4211
+ "visit_website",
4212
+ "wolfram_alpha",
4213
+ "remote_mcp",
4214
+ "mcp",
4215
+ "compound_custom"
4216
+ ]
4217
+ });
3157
4218
 
3158
4219
  // src/providers/profiles/bedrock.ts
3159
4220
  var isGptOss2 = (model) => /gpt-oss/i.test(model);
@@ -3968,7 +5029,7 @@ var COST_LEAF_SET = new Set(COST_BEARING_LEAF_TYPES);
3968
5029
  function hasPositiveTokens(event) {
3969
5030
  const t = event.tokens;
3970
5031
  if (!t) return false;
3971
- return (t.input ?? 0) + (t.output ?? 0) + (t.reasoning ?? 0) > 0;
5032
+ return (t.input ?? 0) + (t.output ?? 0) + (t.reasoning ?? 0) + (t.cached ?? 0) + (t.cacheWrite ?? 0) > 0;
3972
5033
  }
3973
5034
  function isUsableCost(cost) {
3974
5035
  return typeof cost === "number" && Number.isFinite(cost) && cost >= 0;
@@ -6581,6 +7642,11 @@ var WorkflowContext = class _WorkflowContext {
6581
7642
  "axl.agent.cached_tokens",
6582
7643
  usageCapture.value.cached_tokens
6583
7644
  );
7645
+ if (usageCapture.value.cache_write_tokens)
7646
+ span.setAttribute(
7647
+ "axl.agent.cache_write_tokens",
7648
+ usageCapture.value.cache_write_tokens
7649
+ );
6584
7650
  }
6585
7651
  return r;
6586
7652
  }
@@ -7026,7 +8092,9 @@ ${appendedSchemaText}`;
7026
8092
  tokens: response.usage ? {
7027
8093
  input: response.usage.prompt_tokens,
7028
8094
  output: response.usage.completion_tokens,
7029
- reasoning: response.usage.reasoning_tokens
8095
+ reasoning: response.usage.reasoning_tokens,
8096
+ cached: response.usage.cached_tokens,
8097
+ cacheWrite: response.usage.cache_write_tokens
7030
8098
  } : void 0,
7031
8099
  duration: Date.now() - turnStart,
7032
8100
  data: {