@hiper2d/ai-agents 0.5.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -798,15 +798,14 @@ var LLM_CONSTANTS = {
798
798
  GEMINI_PRO: "gemini-pro",
799
799
  GEMINI_FLASH: "gemini-flash",
800
800
  GEMINI_LITE: "gemini-lite",
801
- MISTRAL_LARGE: "mistral-large",
802
801
  MISTRAL_MEDIUM: "mistral-medium",
803
802
  MISTRAL_SMALL: "mistral-small",
804
- MISTRAL_MAGISTRAL: "mistral-magistral",
805
803
  GROK: "grok",
806
804
  KIMI: "kimi",
807
805
  GLM: "glm",
808
806
  GLM_FLASH: "glm-flash",
809
807
  FUGU_ULTRA: "fugu-ultra",
808
+ FUGU_MAX: "fugu-max",
810
809
  // Qwen (QwenCloud/DashScope). Stable picker ids without the version, matching the gpt/gemini
811
810
  // pattern, so future repoints don't orphan persisted ids.
812
811
  QWEN_MAX: "qwen-max",
@@ -959,36 +958,40 @@ var SupportedAiModels = {
959
958
  hasThinking: true,
960
959
  temperature: 0.7
961
960
  },
962
- // Mistral models
963
- [LLM_CONSTANTS.MISTRAL_LARGE]: {
964
- displayName: "Mistral Large 3",
965
- modelApiName: "mistral-large-latest",
966
- apiKeyName: API_KEY_CONSTANTS.MISTRAL,
967
- hasThinking: false,
968
- tags: ["fast"]
969
- },
961
+ // Mistral models. Two hybrid entries since 2026-09-18: reasoning is off by default on the
962
+ // API and switched on per request with `reasoning_effort` (see mistral-agent.ts); the trace
963
+ // comes back as a `thinking` content chunk and works with json_schema structured output
964
+ // (verified live 2026-09-18 on both). Pinned to 'high' — the only level Mistral's docs
965
+ // describe ("full thinking chunk before the final answer"); Small is cheap enough that the
966
+ // extra tokens don't matter, Medium's are priced into its free-tier band via the hybrid
967
+ // multiplier. Live 2026-09-18 (one short schema ask each): Small 1.7s with a ~600-char
968
+ // trace, Medium 3.8s with ~2000 chars — Medium's trace ran ~15x its answer length.
969
+ //
970
+ // Mistral Large 3 and Magistral Medium 1.2 were dropped 2026-09-18. Large 3 was retired by
971
+ // Mistral on 2026-08-31 (the `-latest` alias still answered, but its capabilities say
972
+ // reasoning: false and `reasoning_effort` is a 400 on it). Magistral was retired 2026-07-31
973
+ // and `magistral-medium-latest` is now literally an alias of Medium 3.5 — the model list
974
+ // returns `mistral-medium-3-5`, `mistral-medium-3`, `mistral-medium-latest` and
975
+ // `magistral-medium-latest` as one entry — so Magistral seats had silently become Medium
976
+ // seats already. Consumers map both retired ids onto the surviving pair.
970
977
  [LLM_CONSTANTS.MISTRAL_MEDIUM]: {
971
978
  displayName: "Mistral Medium 3.5",
972
- modelApiName: "mistral-medium-3",
979
+ // The docs' explicit 3.5 id. It was `mistral-medium-3` until 2026-09-18, which Mistral
980
+ // now serves as an alias of the same model (Medium 3 itself retired 2026-08-31).
981
+ modelApiName: "mistral-medium-3-5",
973
982
  apiKeyName: API_KEY_CONSTANTS.MISTRAL,
974
- hasThinking: false,
975
- tags: ["very-fast", "expensive"]
983
+ hasThinking: true,
984
+ reasoningEffort: "high",
985
+ tags: ["fast", "expensive"]
976
986
  },
977
987
  [LLM_CONSTANTS.MISTRAL_SMALL]: {
978
988
  displayName: "Mistral 4 Small",
989
+ // Resolves to mistral-small-2603 (Small 4), which also carries the magistral-small alias.
979
990
  modelApiName: "mistral-small-latest",
980
991
  apiKeyName: API_KEY_CONSTANTS.MISTRAL,
981
- hasThinking: false,
982
- tags: ["very-fast", "cheap"]
983
- },
984
- [LLM_CONSTANTS.MISTRAL_MAGISTRAL]: {
985
- displayName: "Magistral Medium 1.2",
986
- modelApiName: "magistral-medium-latest",
987
- apiKeyName: API_KEY_CONSTANTS.MISTRAL,
988
992
  hasThinking: true,
989
- // Measured very-fast (1.6s) because JSON response mode suppresses its thinking
990
- // (see mistral-agent.ts) — it effectively runs as a non-reasoning model here.
991
- tags: ["very-fast"]
993
+ reasoningEffort: "high",
994
+ tags: ["very-fast", "cheap"]
992
995
  },
993
996
  // Kimi models. Single always-reasoning entry: K3 reasons by default and the only way to stop
994
997
  // it is the undocumented K2-era `thinking: disabled` toggle, which we no longer rely on.
@@ -1048,13 +1051,37 @@ var SupportedAiModels = {
1048
1051
  // so the rate is not even guaranteed stable, and its cache hit rate was 9.3% — effectively
1049
1052
  // zero, since every hit came from a duplicate call seconds apart rather than turn-to-turn
1050
1053
  // prefix reuse. Ultra costs the same and is predictable.
1054
+ //
1055
+ // reasoningEffort is PINNED on both entries because the server default differs per model:
1056
+ // fugu-ultra defaults to `xhigh` (console.sakana.ai/models), fugu-max to `high`. Until
1057
+ // 2026-09-20 the agent sent no effort at all, so every Ultra turn ran at xhigh. Measured that
1058
+ // day on a 2k-token three-day game prompt (two samples each, chat completions, json mode):
1059
+ // ultra xhigh 148s / 265s $0.34 / $0.50 a turn (orchestration tokens included)
1060
+ // ultra high 65s / 78s $0.16 / $0.17
1061
+ // max high 6s / 10s $0.006 / $0.008
1062
+ // max xhigh 41s / 47s $0.02
1063
+ // `max_tokens` is not a latency lever for Ultra: Sakana applies it to the final response only,
1064
+ // the orchestrator "still uses maximum token limit". Effort is the only knob.
1051
1065
  [LLM_CONSTANTS.FUGU_ULTRA]: {
1052
1066
  displayName: "Sakana Fugu Ultra",
1053
1067
  modelApiName: "fugu-ultra",
1054
1068
  apiKeyName: API_KEY_CONSTANTS.FUGU,
1055
1069
  hasThinking: false,
1070
+ reasoningEffort: "high",
1056
1071
  tags: ["extremely-slow", "expensive"]
1057
1072
  },
1073
+ // Fugu Max (2026-09-20, fugu-max → fugu-max-v1.0): Sakana's "largest pool of models on the
1074
+ // cost–performance Pareto frontier" — the everyday Fugu at 5x less than Ultra's output rate
1075
+ // and a fraction of its latency. It reasons (reasoning_tokens 80-500 a turn) but, like
1076
+ // Ultra, never returns reasoning_content, hence hasThinking: false. No orchestration
1077
+ // tokens observed on any call.
1078
+ [LLM_CONSTANTS.FUGU_MAX]: {
1079
+ displayName: "Sakana Fugu Max",
1080
+ modelApiName: "fugu-max",
1081
+ apiKeyName: API_KEY_CONSTANTS.FUGU,
1082
+ hasThinking: false,
1083
+ reasoningEffort: "high"
1084
+ },
1058
1085
  // Qwen models (QwenCloud, OpenAI-compatible endpoint). Added 2026-08-05 straight into the
1059
1086
  // thinking-only catalog: their API has an `enable_thinking` toggle, we always send true, and
1060
1087
  // thinking arrives in `reasoning_content` (verified live against all three, non-streaming).
@@ -1316,13 +1343,10 @@ var MODEL_PRICING = {
1316
1343
  outputPrice: 1.5,
1317
1344
  cacheHitPrice: 0.025
1318
1345
  },
1319
- // Mistral models. Cached tokens bill at 10% of the input price (documented on the
1320
- // prompt_cache_key param in the API reference; no per-model cached prices published).
1321
- [SupportedAiModels[LLM_CONSTANTS.MISTRAL_LARGE].modelApiName]: {
1322
- inputPrice: 0.5,
1323
- outputPrice: 1.5,
1324
- cacheHitPrice: 0.05
1325
- },
1346
+ // Mistral models (mistral.ai/pricing/api, verified 2026-09-18). Cached tokens bill at 10% of
1347
+ // the input price (documented on the prompt_cache_key param; no per-model cached prices
1348
+ // published). Reasoning tokens are counted inside completion_tokens — no separate
1349
+ // reasoning_tokens field — so the output rate already covers the trace.
1326
1350
  [SupportedAiModels[LLM_CONSTANTS.MISTRAL_MEDIUM].modelApiName]: {
1327
1351
  inputPrice: 1.5,
1328
1352
  outputPrice: 7.5,
@@ -1333,11 +1357,6 @@ var MODEL_PRICING = {
1333
1357
  outputPrice: 0.6,
1334
1358
  cacheHitPrice: 0.015
1335
1359
  },
1336
- [SupportedAiModels[LLM_CONSTANTS.MISTRAL_MAGISTRAL].modelApiName]: {
1337
- inputPrice: 2,
1338
- outputPrice: 5,
1339
- cacheHitPrice: 0.2
1340
- },
1341
1360
  // Grok models. Cached price is per-model on xAI (not a uniform ratio):
1342
1361
  // grok-4.6 is $0.50/M cached vs $2.00/M input, and all rates double for prompts
1343
1362
  // >= 200K tokens, per docs.x.ai/developers/models (verified 2026-08-12).
@@ -1350,9 +1369,12 @@ var MODEL_PRICING = {
1350
1369
  extendedContextCacheHitPrice: 1,
1351
1370
  extendedContextThresholdTokens: 2e5
1352
1371
  },
1353
- // Sakana Fugu models. Base `fugu` was retired 2026-08-04 — it had no published price and
1354
- // measured out at these same ultra rates, so it has no pricing entry.
1355
- // fugu-ultra has published pricing. Above 272K context the rates roughly double.
1372
+ // Sakana Fugu models (console.sakana.ai/pricing, read 2026-09-20). Base `fugu` was retired
1373
+ // 2026-08-04 — it had no published price and measured out at these same ultra rates, so it
1374
+ // has no pricing entry. fugu-ultra: above 272K context the rates roughly double. Ultra also
1375
+ // reports "orchestration" tokens (its internal expert calls) in prompt_tokens_details /
1376
+ // completion_tokens_details, billed at these same input/output rates — FuguAgent folds them
1377
+ // into the token counts before pricing.
1356
1378
  [SupportedAiModels[LLM_CONSTANTS.FUGU_ULTRA].modelApiName]: {
1357
1379
  inputPrice: 5,
1358
1380
  outputPrice: 30,
@@ -1362,6 +1384,12 @@ var MODEL_PRICING = {
1362
1384
  extendedContextCacheHitPrice: 1,
1363
1385
  extendedContextThresholdTokens: 272e3
1364
1386
  },
1387
+ // fugu-max: flat rates, no context tiers.
1388
+ [SupportedAiModels[LLM_CONSTANTS.FUGU_MAX].modelApiName]: {
1389
+ inputPrice: 2,
1390
+ outputPrice: 6,
1391
+ cacheHitPrice: 0.25
1392
+ },
1365
1393
  // Qwen models. Rates from the official pricing page (qwencloud.com/pricing/api, read
1366
1394
  // 2026-08-30 — the page is client-rendered, so it was read by eye, not WebFetch):
1367
1395
  // qwen3.8-max $2/$6 with implicit-cache hits at $0.25; qwen3.8-flash $0.15/$0.47, hits
@@ -1412,7 +1440,11 @@ var HYBRID_THINKING_API_NAMES = /* @__PURE__ */ new Set([
1412
1440
  // hybrid by the same definition: we force reasoning on, so they carry the multiplier.
1413
1441
  SupportedAiModels[LLM_CONSTANTS.QWEN_MAX].modelApiName,
1414
1442
  SupportedAiModels[LLM_CONSTANTS.QWEN_FLASH].modelApiName,
1415
- SupportedAiModels[LLM_CONSTANTS.MINIMAX].modelApiName
1443
+ SupportedAiModels[LLM_CONSTANTS.MINIMAX].modelApiName,
1444
+ // Mistral reasons only when asked (`reasoning_effort`), and we always ask — hybrid by the
1445
+ // same definition as Qwen. Added 2026-09-18 when the two entries went thinking-on.
1446
+ SupportedAiModels[LLM_CONSTANTS.MISTRAL_MEDIUM].modelApiName,
1447
+ SupportedAiModels[LLM_CONSTANTS.MISTRAL_SMALL].modelApiName
1416
1448
  ]);
1417
1449
  function isHybridThinkingModel(modelApiName) {
1418
1450
  return HYBRID_THINKING_API_NAMES.has(modelApiName);
@@ -1474,8 +1506,9 @@ var ANTHROPIC_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
1474
1506
  var GEMINI_REASONING_EFFORTS = ["minimal", "low", "medium", "high"];
1475
1507
  var GLM_REASONING_EFFORTS = ["low", "high", "max"];
1476
1508
  var DEEPSEEK_REASONING_EFFORTS = ["low", "high", "max"];
1477
- var FUGU_REASONING_EFFORTS = ["high", "xhigh"];
1509
+ var FUGU_REASONING_EFFORTS = ["high", "xhigh", "max"];
1478
1510
  var META_REASONING_EFFORTS = ["minimal", "low", "medium", "high", "xhigh", "max"];
1511
+ var MISTRAL_REASONING_EFFORTS = ["minimal", "low", "medium", "high", "xhigh"];
1479
1512
  function clampReasoningEffort(effort, allowed) {
1480
1513
  const rank = REASONING_EFFORT_SCALE.indexOf(effort);
1481
1514
  let best = allowed[0];
@@ -1496,6 +1529,7 @@ var toGlmEffort = (effort) => clampReasoningEffort(effort, GLM_REASONING_EFFORTS
1496
1529
  var toDeepSeekEffort = (effort) => clampReasoningEffort(effort, DEEPSEEK_REASONING_EFFORTS);
1497
1530
  var toMetaEffort = (effort) => clampReasoningEffort(effort, META_REASONING_EFFORTS);
1498
1531
  var toFuguEffort = (effort) => clampReasoningEffort(effort, FUGU_REASONING_EFFORTS);
1532
+ var toMistralEffort = (effort) => clampReasoningEffort(effort, MISTRAL_REASONING_EFFORTS);
1499
1533
 
1500
1534
  // src/pricing/token-usage-utils.ts
1501
1535
  function extractTokenUsage(response) {
@@ -3228,6 +3262,9 @@ ${instruction}`);
3228
3262
  const body = await request.clone().text();
3229
3263
  const json = JSON.parse(body);
3230
3264
  json.prompt_cache_key = promptCacheKey;
3265
+ if (this.enableThinking && this.reasoningEffort) {
3266
+ json.reasoning_effort = toMistralEffort(this.reasoningEffort);
3267
+ }
3231
3268
  return new Request(request.url, {
3232
3269
  method: request.method,
3233
3270
  headers: request.headers,
@@ -3240,11 +3277,25 @@ ${instruction}`);
3240
3277
  });
3241
3278
  this.client = new Mistral({ apiKey, httpClient });
3242
3279
  }
3280
+ /**
3281
+ * History in Mistral's shape. A prior assistant turn that carries a stored trace is
3282
+ * replayed as [thinking, text] chunks, as Mistral's multi-turn guidance asks; every other
3283
+ * turn is a plain string.
3284
+ */
3243
3285
  convertToMistralMessages(messages) {
3244
- return this.prepareMessages(messages).map((msg) => ({
3245
- role: msg.role === "developer" ? "system" : msg.role,
3246
- content: msg.content
3247
- }));
3286
+ return this.prepareMessages(messages).map((msg) => {
3287
+ if (msg.role === "assistant" && msg.thinking) {
3288
+ const chunks = [
3289
+ { type: "thinking", thinking: [{ type: "text", text: msg.thinking }] },
3290
+ { type: "text", text: msg.content }
3291
+ ];
3292
+ return { role: "assistant", content: chunks };
3293
+ }
3294
+ return {
3295
+ role: msg.role === "developer" ? "system" : msg.role,
3296
+ content: msg.content
3297
+ };
3298
+ });
3248
3299
  }
3249
3300
  processReply(response) {
3250
3301
  const message = response?.choices?.[0]?.message;
@@ -3279,7 +3330,6 @@ ${instruction}`);
3279
3330
  extractTokenUsage(response) {
3280
3331
  const usage = extractMistralTokenUsage(response);
3281
3332
  if (!usage) return void 0;
3282
- this.logger(`MISTRAL_CACHE_CALIBRATION raw usage: ${JSON.stringify(response?.usage)}`);
3283
3333
  if (usage.reasoningTokens && usage.reasoningTokens > 0) {
3284
3334
  this.logger(`\u{1F9E0} Reasoning tokens used: ${usage.reasoningTokens}`);
3285
3335
  }
@@ -3315,7 +3365,7 @@ ${instruction}`);
3315
3365
  const convertedMessages = this.convertToMistralMessages(messages);
3316
3366
  if (convertedMessages.length > 0) {
3317
3367
  const lastMessage = convertedMessages[convertedMessages.length - 1];
3318
- if (lastMessage && lastMessage.content) {
3368
+ if (lastMessage && typeof lastMessage.content === "string" && lastMessage.content) {
3319
3369
  lastMessage.content += `
3320
3370
 
3321
3371
  Your response must be a valid JSON object matching this schema:
@@ -3386,9 +3436,8 @@ ${schemaDescription}`
3386
3436
  }
3387
3437
  }
3388
3438
  /**
3389
- * Plain-text ask: no schema appended, no responseFormat. Note that Magistral
3390
- * reasoning models only return thinking traces when responseFormat is NOT
3391
- * json_object, so unlike askWithZodSchema this path can surface thinking content.
3439
+ * Plain-text ask: no schema appended, no responseFormat. Reasoning (and the trace) works
3440
+ * the same way as on the schema path.
3392
3441
  */
3393
3442
  async doAskText(messages) {
3394
3443
  try {
@@ -4225,13 +4274,15 @@ var FuguAgent = class extends AbstractAgent {
4225
4274
  client;
4226
4275
  // A getter, not a field: `maxOutputTokens` can be raised after construction, and a field
4227
4276
  // initializer would snapshot the default and silently ignore the override.
4277
+ // `reasoning_effort` is re-declared as string: the OpenAI SDK's union lacks Sakana's 'max'.
4228
4278
  get defaultParams() {
4229
4279
  return {
4230
4280
  model: this.model,
4231
4281
  stream: false,
4232
- // Caps visible output only. Server-side orchestration/reasoning tokens are
4233
- // separate and unaffected by this.
4234
- max_tokens: this.maxOutputTokens
4282
+ // Caps the final reply only. Per Sakana's docs the orchestrator "still uses maximum
4283
+ // token limit", so this bounds neither Ultra's latency nor its orchestration spend.
4284
+ max_tokens: this.maxOutputTokens,
4285
+ reasoning_effort: toFuguEffort(this.reasoningEffort ?? "high")
4235
4286
  };
4236
4287
  }
4237
4288
  logTemplates = {
@@ -4256,33 +4307,6 @@ var FuguAgent = class extends AbstractAgent {
4256
4307
  content: msg.content
4257
4308
  }));
4258
4309
  }
4259
- // ───────────────────────────────────────────────────────────────────────────────────────
4260
- // TEMPORARY (cost calibration). Base `fugu` is a dynamic router with no published per-token
4261
- // price and Sakana returns NO cost field in the response — only token counts. Crucially the
4262
- // response also reports "orchestration tokens" (billed at input/output rates per Sakana's
4263
- // pricing page) that our standard TokenUsage drops. This logs the full raw breakdown to
4264
- // BetterStack under a distinctive tag so we can sum real tokens per game and, combined with
4265
- // the Sakana billing dashboard total, derive the true per-token rate. REMOVE AFTER CALIBRATION.
4266
- logRawUsageForCalibration(completion) {
4267
- const usage = completion?.usage;
4268
- if (!usage) return;
4269
- logger.info("FUGU_COST_CALIBRATION", {
4270
- tag: "FUGU_COST_CALIBRATION",
4271
- model: this.model,
4272
- agentName: this.name,
4273
- gameId: this.gameId,
4274
- userId: this.userId,
4275
- promptTokens: usage.prompt_tokens ?? 0,
4276
- completionTokens: usage.completion_tokens ?? 0,
4277
- totalTokens: usage.total_tokens ?? 0,
4278
- cachedTokens: usage.prompt_tokens_details?.cached_tokens ?? 0,
4279
- orchestrationInputTokens: usage.prompt_tokens_details?.orchestration_input_tokens ?? 0,
4280
- orchestrationInputCachedTokens: usage.prompt_tokens_details?.orchestration_input_cached_tokens ?? 0,
4281
- reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0,
4282
- orchestrationOutputTokens: usage.completion_tokens_details?.orchestration_output_tokens ?? 0,
4283
- rawUsage: usage
4284
- });
4285
- }
4286
4310
  extractThinkingAndUsage(completion) {
4287
4311
  let thinkingContent = "";
4288
4312
  const message = completion.choices[0]?.message;
@@ -4290,21 +4314,14 @@ var FuguAgent = class extends AbstractAgent {
4290
4314
  thinkingContent = message.reasoning_content;
4291
4315
  this.logger(`Captured reasoning_content (${thinkingContent.length} characters)`);
4292
4316
  }
4293
- let tokenUsage;
4294
- const usageResult = extractUsageAndCalculateCost(this.model, completion);
4295
- if (usageResult) {
4296
- tokenUsage = {
4297
- inputTokens: usageResult.usage.promptTokens,
4298
- outputTokens: usageResult.usage.completionTokens,
4299
- totalTokens: usageResult.usage.totalTokens,
4300
- costUSD: usageResult.cost,
4301
- ...usageResult.usage.cacheHitTokens !== void 0 ? { cachedInputTokens: usageResult.usage.cacheHitTokens } : {}
4302
- };
4303
- if (usageResult.usage.reasoningTokens) {
4304
- const reasoningTokens = usageResult.usage.reasoningTokens;
4305
- const finalAnswerTokens = Math.max(0, tokenUsage.outputTokens - reasoningTokens);
4317
+ const tokenUsage = extractFuguTokenUsage(this.model, completion);
4318
+ if (tokenUsage) {
4319
+ const details = completion.usage ?? {};
4320
+ const orchestrationIn = details.prompt_tokens_details?.orchestration_input_tokens ?? 0;
4321
+ const orchestrationOut = details.completion_tokens_details?.orchestration_output_tokens ?? 0;
4322
+ if (tokenUsage.reasoningTokens || orchestrationIn || orchestrationOut) {
4306
4323
  this.logger(
4307
- `Output breakdown: ${reasoningTokens} reasoning tokens, ${finalAnswerTokens} final answer tokens`
4324
+ `Usage breakdown: ${tokenUsage.reasoningTokens ?? 0} reasoning tokens; orchestration ${orchestrationIn} in / ${orchestrationOut} out (billed, folded into the totals)`
4308
4325
  );
4309
4326
  }
4310
4327
  }
@@ -4346,7 +4363,6 @@ ${schemaDescription}`;
4346
4363
  this.logger(this.logTemplates.error(this.name, apiError));
4347
4364
  throw new Error(this.errorMessages.apiError(apiError));
4348
4365
  }
4349
- this.logRawUsageForCalibration(completion);
4350
4366
  const rawReply = completion.choices[0]?.message?.content;
4351
4367
  if (!rawReply) {
4352
4368
  throw new Error(this.errorMessages.emptyResponse);
@@ -4390,7 +4406,6 @@ ${schemaDescription}`;
4390
4406
  this.logger(this.logTemplates.error(this.name, apiError));
4391
4407
  throw new Error(this.errorMessages.apiError(apiError));
4392
4408
  }
4393
- this.logRawUsageForCalibration(completion);
4394
4409
  const rawReply = completion.choices[0]?.message?.content;
4395
4410
  if (!rawReply) {
4396
4411
  throw new Error(this.errorMessages.emptyResponse);
@@ -4409,6 +4424,31 @@ ${schemaDescription}`;
4409
4424
  }
4410
4425
  }
4411
4426
  };
4427
+ function extractFuguTokenUsage(modelApiName, completion) {
4428
+ const usage = completion?.usage;
4429
+ if (!usage) {
4430
+ return void 0;
4431
+ }
4432
+ const promptDetails = usage.prompt_tokens_details ?? {};
4433
+ const completionDetails = usage.completion_tokens_details ?? {};
4434
+ const inputTokens = (usage.prompt_tokens ?? 0) + (promptDetails.orchestration_input_tokens ?? 0);
4435
+ const outputTokens = (usage.completion_tokens ?? 0) + (completionDetails.orchestration_output_tokens ?? 0);
4436
+ const cachedInputTokens = (promptDetails.cached_tokens ?? 0) + (promptDetails.orchestration_input_cached_tokens ?? 0);
4437
+ const reasoningTokens = completionDetails.reasoning_tokens ?? 0;
4438
+ return {
4439
+ inputTokens,
4440
+ outputTokens,
4441
+ totalTokens: inputTokens + outputTokens,
4442
+ cachedInputTokens,
4443
+ // Omitted rather than 0: consumers persist the object as-is (see TokenUsage).
4444
+ ...reasoningTokens > 0 ? { reasoningTokens } : {},
4445
+ costUSD: calculateModelCost(modelApiName, inputTokens, outputTokens, {
4446
+ cacheHitTokens: cachedInputTokens,
4447
+ // Context-tier selection: the request's own size, not the orchestrator's traffic.
4448
+ contextTokens: usage.total_tokens ?? inputTokens
4449
+ })
4450
+ };
4451
+ }
4412
4452
 
4413
4453
  // src/agents/qwen-agent.ts
4414
4454
  import { OpenAI as OpenAI9 } from "openai";
@@ -5060,8 +5100,6 @@ var AgentFactory = class {
5060
5100
  // Mistral models
5061
5101
  case LLM_CONSTANTS.MISTRAL_MEDIUM:
5062
5102
  case LLM_CONSTANTS.MISTRAL_SMALL:
5063
- case LLM_CONSTANTS.MISTRAL_LARGE:
5064
- case LLM_CONSTANTS.MISTRAL_MAGISTRAL:
5065
5103
  return new MistralAgent(name, instruction, model.modelApiName, key, shouldEnableThinking);
5066
5104
  case LLM_CONSTANTS.KIMI:
5067
5105
  return new KimiAgent(name, instruction, model.modelApiName, key, 0, shouldEnableThinking);
@@ -5071,6 +5109,7 @@ var AgentFactory = class {
5071
5109
  return new GlmAgent(name, instruction, model.modelApiName, key, model.temperature, shouldEnableThinking);
5072
5110
  // Sakana Fugu models — always-on reasoning, no temperature (ignored by the model)
5073
5111
  case LLM_CONSTANTS.FUGU_ULTRA:
5112
+ case LLM_CONSTANTS.FUGU_MAX:
5074
5113
  return new FuguAgent(name, instruction, model.modelApiName, key, shouldEnableThinking);
5075
5114
  // Qwen models — thinking-only (enable_thinking always sent)
5076
5115
  case LLM_CONSTANTS.QWEN_MAX:
@@ -5124,6 +5163,7 @@ export {
5124
5163
  LLM_CONSTANTS,
5125
5164
  MESSAGE_ROLE,
5126
5165
  META_REASONING_EFFORTS,
5166
+ MISTRAL_REASONING_EFFORTS,
5127
5167
  MODEL_PRICING,
5128
5168
  MetaAgent,
5129
5169
  MiniMaxAgent,
@@ -5225,6 +5265,7 @@ export {
5225
5265
  toGeminiEffort,
5226
5266
  toGlmEffort,
5227
5267
  toMetaEffort,
5268
+ toMistralEffort,
5228
5269
  toOpenAIEffort,
5229
5270
  transcribeWithGemini,
5230
5271
  transcribeWithOpenAi,