@hiper2d/ai-agents 0.5.2 → 0.6.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,10 +798,8 @@ 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",
@@ -959,36 +957,40 @@ var SupportedAiModels = {
959
957
  hasThinking: true,
960
958
  temperature: 0.7
961
959
  },
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
- },
960
+ // Mistral models. Two hybrid entries since 2026-09-18: reasoning is off by default on the
961
+ // API and switched on per request with `reasoning_effort` (see mistral-agent.ts); the trace
962
+ // comes back as a `thinking` content chunk and works with json_schema structured output
963
+ // (verified live 2026-09-18 on both). Pinned to 'high' — the only level Mistral's docs
964
+ // describe ("full thinking chunk before the final answer"); Small is cheap enough that the
965
+ // extra tokens don't matter, Medium's are priced into its free-tier band via the hybrid
966
+ // multiplier. Live 2026-09-18 (one short schema ask each): Small 1.7s with a ~600-char
967
+ // trace, Medium 3.8s with ~2000 chars — Medium's trace ran ~15x its answer length.
968
+ //
969
+ // Mistral Large 3 and Magistral Medium 1.2 were dropped 2026-09-18. Large 3 was retired by
970
+ // Mistral on 2026-08-31 (the `-latest` alias still answered, but its capabilities say
971
+ // reasoning: false and `reasoning_effort` is a 400 on it). Magistral was retired 2026-07-31
972
+ // and `magistral-medium-latest` is now literally an alias of Medium 3.5 — the model list
973
+ // returns `mistral-medium-3-5`, `mistral-medium-3`, `mistral-medium-latest` and
974
+ // `magistral-medium-latest` as one entry — so Magistral seats had silently become Medium
975
+ // seats already. Consumers map both retired ids onto the surviving pair.
970
976
  [LLM_CONSTANTS.MISTRAL_MEDIUM]: {
971
977
  displayName: "Mistral Medium 3.5",
972
- modelApiName: "mistral-medium-3",
978
+ // The docs' explicit 3.5 id. It was `mistral-medium-3` until 2026-09-18, which Mistral
979
+ // now serves as an alias of the same model (Medium 3 itself retired 2026-08-31).
980
+ modelApiName: "mistral-medium-3-5",
973
981
  apiKeyName: API_KEY_CONSTANTS.MISTRAL,
974
- hasThinking: false,
975
- tags: ["very-fast", "expensive"]
982
+ hasThinking: true,
983
+ reasoningEffort: "high",
984
+ tags: ["fast", "expensive"]
976
985
  },
977
986
  [LLM_CONSTANTS.MISTRAL_SMALL]: {
978
987
  displayName: "Mistral 4 Small",
988
+ // Resolves to mistral-small-2603 (Small 4), which also carries the magistral-small alias.
979
989
  modelApiName: "mistral-small-latest",
980
990
  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
991
  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"]
992
+ reasoningEffort: "high",
993
+ tags: ["very-fast", "cheap"]
992
994
  },
993
995
  // Kimi models. Single always-reasoning entry: K3 reasons by default and the only way to stop
994
996
  // it is the undocumented K2-era `thinking: disabled` toggle, which we no longer rely on.
@@ -1316,13 +1318,10 @@ var MODEL_PRICING = {
1316
1318
  outputPrice: 1.5,
1317
1319
  cacheHitPrice: 0.025
1318
1320
  },
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
- },
1321
+ // Mistral models (mistral.ai/pricing/api, verified 2026-09-18). Cached tokens bill at 10% of
1322
+ // the input price (documented on the prompt_cache_key param; no per-model cached prices
1323
+ // published). Reasoning tokens are counted inside completion_tokens — no separate
1324
+ // reasoning_tokens field — so the output rate already covers the trace.
1326
1325
  [SupportedAiModels[LLM_CONSTANTS.MISTRAL_MEDIUM].modelApiName]: {
1327
1326
  inputPrice: 1.5,
1328
1327
  outputPrice: 7.5,
@@ -1333,11 +1332,6 @@ var MODEL_PRICING = {
1333
1332
  outputPrice: 0.6,
1334
1333
  cacheHitPrice: 0.015
1335
1334
  },
1336
- [SupportedAiModels[LLM_CONSTANTS.MISTRAL_MAGISTRAL].modelApiName]: {
1337
- inputPrice: 2,
1338
- outputPrice: 5,
1339
- cacheHitPrice: 0.2
1340
- },
1341
1335
  // Grok models. Cached price is per-model on xAI (not a uniform ratio):
1342
1336
  // grok-4.6 is $0.50/M cached vs $2.00/M input, and all rates double for prompts
1343
1337
  // >= 200K tokens, per docs.x.ai/developers/models (verified 2026-08-12).
@@ -1412,7 +1406,11 @@ var HYBRID_THINKING_API_NAMES = /* @__PURE__ */ new Set([
1412
1406
  // hybrid by the same definition: we force reasoning on, so they carry the multiplier.
1413
1407
  SupportedAiModels[LLM_CONSTANTS.QWEN_MAX].modelApiName,
1414
1408
  SupportedAiModels[LLM_CONSTANTS.QWEN_FLASH].modelApiName,
1415
- SupportedAiModels[LLM_CONSTANTS.MINIMAX].modelApiName
1409
+ SupportedAiModels[LLM_CONSTANTS.MINIMAX].modelApiName,
1410
+ // Mistral reasons only when asked (`reasoning_effort`), and we always ask — hybrid by the
1411
+ // same definition as Qwen. Added 2026-09-18 when the two entries went thinking-on.
1412
+ SupportedAiModels[LLM_CONSTANTS.MISTRAL_MEDIUM].modelApiName,
1413
+ SupportedAiModels[LLM_CONSTANTS.MISTRAL_SMALL].modelApiName
1416
1414
  ]);
1417
1415
  function isHybridThinkingModel(modelApiName) {
1418
1416
  return HYBRID_THINKING_API_NAMES.has(modelApiName);
@@ -1476,6 +1474,7 @@ var GLM_REASONING_EFFORTS = ["low", "high", "max"];
1476
1474
  var DEEPSEEK_REASONING_EFFORTS = ["low", "high", "max"];
1477
1475
  var FUGU_REASONING_EFFORTS = ["high", "xhigh"];
1478
1476
  var META_REASONING_EFFORTS = ["minimal", "low", "medium", "high", "xhigh", "max"];
1477
+ var MISTRAL_REASONING_EFFORTS = ["minimal", "low", "medium", "high", "xhigh"];
1479
1478
  function clampReasoningEffort(effort, allowed) {
1480
1479
  const rank = REASONING_EFFORT_SCALE.indexOf(effort);
1481
1480
  let best = allowed[0];
@@ -1496,6 +1495,7 @@ var toGlmEffort = (effort) => clampReasoningEffort(effort, GLM_REASONING_EFFORTS
1496
1495
  var toDeepSeekEffort = (effort) => clampReasoningEffort(effort, DEEPSEEK_REASONING_EFFORTS);
1497
1496
  var toMetaEffort = (effort) => clampReasoningEffort(effort, META_REASONING_EFFORTS);
1498
1497
  var toFuguEffort = (effort) => clampReasoningEffort(effort, FUGU_REASONING_EFFORTS);
1498
+ var toMistralEffort = (effort) => clampReasoningEffort(effort, MISTRAL_REASONING_EFFORTS);
1499
1499
 
1500
1500
  // src/pricing/token-usage-utils.ts
1501
1501
  function extractTokenUsage(response) {
@@ -3228,6 +3228,9 @@ ${instruction}`);
3228
3228
  const body = await request.clone().text();
3229
3229
  const json = JSON.parse(body);
3230
3230
  json.prompt_cache_key = promptCacheKey;
3231
+ if (this.enableThinking && this.reasoningEffort) {
3232
+ json.reasoning_effort = toMistralEffort(this.reasoningEffort);
3233
+ }
3231
3234
  return new Request(request.url, {
3232
3235
  method: request.method,
3233
3236
  headers: request.headers,
@@ -3240,11 +3243,25 @@ ${instruction}`);
3240
3243
  });
3241
3244
  this.client = new Mistral({ apiKey, httpClient });
3242
3245
  }
3246
+ /**
3247
+ * History in Mistral's shape. A prior assistant turn that carries a stored trace is
3248
+ * replayed as [thinking, text] chunks, as Mistral's multi-turn guidance asks; every other
3249
+ * turn is a plain string.
3250
+ */
3243
3251
  convertToMistralMessages(messages) {
3244
- return this.prepareMessages(messages).map((msg) => ({
3245
- role: msg.role === "developer" ? "system" : msg.role,
3246
- content: msg.content
3247
- }));
3252
+ return this.prepareMessages(messages).map((msg) => {
3253
+ if (msg.role === "assistant" && msg.thinking) {
3254
+ const chunks = [
3255
+ { type: "thinking", thinking: [{ type: "text", text: msg.thinking }] },
3256
+ { type: "text", text: msg.content }
3257
+ ];
3258
+ return { role: "assistant", content: chunks };
3259
+ }
3260
+ return {
3261
+ role: msg.role === "developer" ? "system" : msg.role,
3262
+ content: msg.content
3263
+ };
3264
+ });
3248
3265
  }
3249
3266
  processReply(response) {
3250
3267
  const message = response?.choices?.[0]?.message;
@@ -3279,7 +3296,6 @@ ${instruction}`);
3279
3296
  extractTokenUsage(response) {
3280
3297
  const usage = extractMistralTokenUsage(response);
3281
3298
  if (!usage) return void 0;
3282
- this.logger(`MISTRAL_CACHE_CALIBRATION raw usage: ${JSON.stringify(response?.usage)}`);
3283
3299
  if (usage.reasoningTokens && usage.reasoningTokens > 0) {
3284
3300
  this.logger(`\u{1F9E0} Reasoning tokens used: ${usage.reasoningTokens}`);
3285
3301
  }
@@ -3315,7 +3331,7 @@ ${instruction}`);
3315
3331
  const convertedMessages = this.convertToMistralMessages(messages);
3316
3332
  if (convertedMessages.length > 0) {
3317
3333
  const lastMessage = convertedMessages[convertedMessages.length - 1];
3318
- if (lastMessage && lastMessage.content) {
3334
+ if (lastMessage && typeof lastMessage.content === "string" && lastMessage.content) {
3319
3335
  lastMessage.content += `
3320
3336
 
3321
3337
  Your response must be a valid JSON object matching this schema:
@@ -3386,9 +3402,8 @@ ${schemaDescription}`
3386
3402
  }
3387
3403
  }
3388
3404
  /**
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.
3405
+ * Plain-text ask: no schema appended, no responseFormat. Reasoning (and the trace) works
3406
+ * the same way as on the schema path.
3392
3407
  */
3393
3408
  async doAskText(messages) {
3394
3409
  try {
@@ -4412,6 +4427,18 @@ ${schemaDescription}`;
4412
4427
 
4413
4428
  // src/agents/qwen-agent.ts
4414
4429
  import { OpenAI as OpenAI9 } from "openai";
4430
+ function qwenRefusalFrom(model, apiError) {
4431
+ const message = apiError instanceof Error ? apiError.message : String(apiError);
4432
+ if (!/DataInspectionFailed|inappropriate content/i.test(message)) {
4433
+ return void 0;
4434
+ }
4435
+ const detail = message.replace(/^[\s\S]*DataInspectionFailed:\s*/, "").trim();
4436
+ return new ModelRefusalError(
4437
+ model,
4438
+ `${model} refused the prompt (refusalReason: DataInspectionFailed${detail ? `; ${detail}` : ""})`,
4439
+ "DataInspectionFailed"
4440
+ );
4441
+ }
4415
4442
  var QwenAgent = class extends AbstractAgent {
4416
4443
  client;
4417
4444
  // A getter, not a field: `maxOutputTokens` can be raised after construction, and a field
@@ -4536,7 +4563,7 @@ ${schemaDescription}`;
4536
4563
  completion = await this.client.chat.completions.create(params);
4537
4564
  } catch (apiError) {
4538
4565
  this.logger(this.logTemplates.error(this.name, apiError));
4539
- throw new Error(this.errorMessages.apiError(apiError));
4566
+ throw qwenRefusalFrom(this.model, apiError) ?? new Error(this.errorMessages.apiError(apiError));
4540
4567
  }
4541
4568
  const rawReply = completion.choices[0]?.message?.content;
4542
4569
  if (!rawReply) {
@@ -4556,6 +4583,9 @@ ${schemaDescription}`;
4556
4583
  return [validated, thinkingContent, tokenUsage];
4557
4584
  } catch (error) {
4558
4585
  this.logger(this.logTemplates.error(this.name, error));
4586
+ if (error instanceof ModelError) {
4587
+ throw error;
4588
+ }
4559
4589
  throw new Error(this.errorMessages.apiError(error));
4560
4590
  }
4561
4591
  }
@@ -4589,7 +4619,7 @@ ${openAIMessages[0].content}`;
4589
4619
  completion = await this.client.chat.completions.create(params);
4590
4620
  } catch (apiError) {
4591
4621
  this.logger(this.logTemplates.error(this.name, apiError));
4592
- throw new Error(this.errorMessages.apiError(apiError));
4622
+ throw qwenRefusalFrom(this.model, apiError) ?? new Error(this.errorMessages.apiError(apiError));
4593
4623
  }
4594
4624
  const rawReply = completion.choices[0]?.message?.content;
4595
4625
  if (!rawReply) {
@@ -4605,6 +4635,9 @@ ${openAIMessages[0].content}`;
4605
4635
  return [reply, thinkingContent, tokenUsage];
4606
4636
  } catch (error) {
4607
4637
  this.logger(this.logTemplates.error(this.name, error));
4638
+ if (error instanceof ModelError) {
4639
+ throw error;
4640
+ }
4608
4641
  throw new Error(this.errorMessages.apiError(error));
4609
4642
  }
4610
4643
  }
@@ -5042,8 +5075,6 @@ var AgentFactory = class {
5042
5075
  // Mistral models
5043
5076
  case LLM_CONSTANTS.MISTRAL_MEDIUM:
5044
5077
  case LLM_CONSTANTS.MISTRAL_SMALL:
5045
- case LLM_CONSTANTS.MISTRAL_LARGE:
5046
- case LLM_CONSTANTS.MISTRAL_MAGISTRAL:
5047
5078
  return new MistralAgent(name, instruction, model.modelApiName, key, shouldEnableThinking);
5048
5079
  case LLM_CONSTANTS.KIMI:
5049
5080
  return new KimiAgent(name, instruction, model.modelApiName, key, 0, shouldEnableThinking);
@@ -5106,6 +5137,7 @@ export {
5106
5137
  LLM_CONSTANTS,
5107
5138
  MESSAGE_ROLE,
5108
5139
  META_REASONING_EFFORTS,
5140
+ MISTRAL_REASONING_EFFORTS,
5109
5141
  MODEL_PRICING,
5110
5142
  MetaAgent,
5111
5143
  MiniMaxAgent,
@@ -5207,6 +5239,7 @@ export {
5207
5239
  toGeminiEffort,
5208
5240
  toGlmEffort,
5209
5241
  toMetaEffort,
5242
+ toMistralEffort,
5210
5243
  toOpenAIEffort,
5211
5244
  transcribeWithGemini,
5212
5245
  transcribeWithOpenAi,