@core-ai/mistral 0.15.0 → 0.17.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.
Files changed (2) hide show
  1. package/dist/index.js +216 -42
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  // src/chat-adapter.ts
14
14
  import {
15
15
  asObject,
16
+ getProviderMetadata,
16
17
  safeParseJsonObject,
17
18
  zodSchemaToJsonSchema
18
19
  } from "@core-ai/core-ai";
@@ -49,13 +50,31 @@ function parseMistralEmbedProviderOptions(providerOptions) {
49
50
  }
50
51
  var mistralProviderOptionsSchema = mistralGenerateProviderOptionsSchema;
51
52
 
53
+ // src/model-capabilities.ts
54
+ var NO_REASONING_CAPABILITIES = {
55
+ reasoning: {
56
+ mode: "unsupported",
57
+ supportedEfforts: [],
58
+ restrictsSamplingParams: false,
59
+ supportedToolChoices: ["auto", "none", "required", "tool"]
60
+ }
61
+ };
62
+ function getMistralModelCapabilities(_modelId) {
63
+ return NO_REASONING_CAPABILITIES;
64
+ }
65
+
52
66
  // src/chat-adapter.ts
67
+ var MISTRAL_REASONING_METADATA_NAMESPACE = "mistral";
53
68
  var DEFAULT_STRUCTURED_OUTPUT_TOOL_NAME = "core_ai_generate_object";
54
69
  var DEFAULT_STRUCTURED_OUTPUT_TOOL_DESCRIPTION = "Return a JSON object that matches the requested schema.";
55
- function convertMessages(messages) {
56
- return messages.map(convertMessage);
70
+ function convertMessages(messages, options = {}) {
71
+ const includeReasoning = options.includeReasoning ?? true;
72
+ return messages.flatMap((message) => {
73
+ const convertedMessage = convertMessage(message, includeReasoning);
74
+ return convertedMessage ? [convertedMessage] : [];
75
+ });
57
76
  }
58
- function convertMessage(message) {
77
+ function convertMessage(message, includeReasoning) {
59
78
  if (message.role === "system") {
60
79
  return {
61
80
  role: "system",
@@ -77,12 +96,26 @@ function convertMessage(message) {
77
96
  if (part.type === "text") {
78
97
  contentChunks.push({ type: "text", text: part.text });
79
98
  } else if (part.type === "reasoning" && part.text.length > 0) {
80
- contentChunks.push({
81
- type: "thinking",
82
- thinking: [{ type: "text", text: part.text }]
83
- });
99
+ const isNativeMistralReasoning = getProviderMetadata(
100
+ part.providerMetadata,
101
+ MISTRAL_REASONING_METADATA_NAMESPACE
102
+ ) != null;
103
+ if (includeReasoning && isNativeMistralReasoning) {
104
+ contentChunks.push({
105
+ type: "thinking",
106
+ thinking: [{ type: "text", text: part.text }]
107
+ });
108
+ } else {
109
+ contentChunks.push({
110
+ type: "text",
111
+ text: `<thinking>${part.text}</thinking>`
112
+ });
113
+ }
84
114
  }
85
115
  }
116
+ if (contentChunks.length === 0 && toolCalls.length === 0) {
117
+ return void 0;
118
+ }
86
119
  return {
87
120
  role: "assistant",
88
121
  content: contentChunks.length > 0 ? contentChunks : null,
@@ -127,14 +160,16 @@ function convertUserContentPart(part) {
127
160
  };
128
161
  }
129
162
  function convertTools(tools) {
130
- return Object.values(tools).map((tool) => ({
131
- type: "function",
132
- function: {
133
- name: tool.name,
134
- description: tool.description,
135
- parameters: zodSchemaToJsonSchema(tool.parameters)
136
- }
137
- }));
163
+ return Object.values(tools).map(
164
+ (tool) => ({
165
+ type: "function",
166
+ function: {
167
+ name: tool.name,
168
+ description: tool.description,
169
+ parameters: zodSchemaToJsonSchema(tool.parameters)
170
+ }
171
+ })
172
+ );
138
173
  }
139
174
  function convertToolChoice(choice) {
140
175
  if (typeof choice === "string") {
@@ -193,9 +228,12 @@ function createStreamRequest(modelId, options) {
193
228
  return mapMistralProviderOptionsToRequest(baseRequest, mistralOptions);
194
229
  }
195
230
  function createRequestBase(modelId, options) {
231
+ const capabilities = getMistralModelCapabilities(modelId);
196
232
  return {
197
233
  model: modelId,
198
- messages: convertMessages(options.messages),
234
+ messages: convertMessages(options.messages, {
235
+ includeReasoning: capabilities.reasoning.mode !== "unsupported"
236
+ }),
199
237
  ...options.tools && Object.keys(options.tools).length > 0 ? { tools: convertTools(options.tools) } : {},
200
238
  ...options.toolChoice ? { toolChoice: convertToolChoice(options.toolChoice) } : {},
201
239
  ...mapSamplingToRequestFields(options)
@@ -235,6 +273,16 @@ function mapGenerateResponse(response) {
235
273
  usage: mapUsage(response.usage)
236
274
  };
237
275
  }
276
+ if (!firstChoice.message) {
277
+ return {
278
+ parts: [],
279
+ content: null,
280
+ reasoning: null,
281
+ toolCalls: [],
282
+ finishReason: mapFinishReason(firstChoice.finishReason),
283
+ usage: mapUsage(response.usage)
284
+ };
285
+ }
238
286
  const parts = extractAssistantParts(firstChoice.message);
239
287
  const toolCalls = parts.flatMap(
240
288
  (part) => part.type === "tool-call" ? [part.toolCall] : []
@@ -535,39 +583,165 @@ function toObject(value) {
535
583
  }
536
584
 
537
585
  // src/mistral-error.ts
538
- import { RequestAbortedError } from "@mistralai/mistralai/models/errors/httpclienterrors";
539
- import { MistralError } from "@mistralai/mistralai/models/errors";
540
- import { AbortedError, ProviderError } from "@core-ai/core-ai";
586
+ import {
587
+ RequestAbortedError,
588
+ RequestTimeoutError,
589
+ MistralError
590
+ } from "@mistralai/mistralai/models/errors";
591
+ import {
592
+ AbortedError,
593
+ ContextLengthExceededError,
594
+ ModelOverloadedError,
595
+ ProviderError,
596
+ RateLimitError,
597
+ ServiceUnavailableError,
598
+ asRecord,
599
+ getErrorMessage,
600
+ getHttpStatusCode,
601
+ getRetryAfterSecondsFromError,
602
+ getString,
603
+ isAbortErrorByName,
604
+ isRateLimitStatus,
605
+ isTransientUnavailableStatus
606
+ } from "@core-ai/core-ai";
607
+ var OVERLOAD_MESSAGE_ELIGIBLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
541
608
  function wrapMistralError(error) {
542
- if (error instanceof RequestAbortedError || error instanceof Error && error.name === "AbortError") {
609
+ if (isMistralAbortError(error)) {
543
610
  return new AbortedError(error, "mistral");
544
611
  }
545
- if (error instanceof MistralError) {
546
- return new ProviderError(
547
- error.message,
548
- "mistral",
549
- error.statusCode,
550
- error
551
- );
612
+ if (isMistralTimeoutError(error)) {
613
+ return new ServiceUnavailableError(getErrorMessage(error), "mistral", {
614
+ cause: error
615
+ });
616
+ }
617
+ const message = getErrorMessage(error);
618
+ const statusCode = error instanceof MistralError ? error.statusCode : getHttpStatusCode(error, ["statusCode", "status"]);
619
+ const body = parseErrorBody(error);
620
+ const options = { statusCode, cause: error };
621
+ const contextLength = getContextLengthDetails(body, message);
622
+ if (contextLength) {
623
+ return new ContextLengthExceededError(message, "mistral", {
624
+ ...options,
625
+ ...contextLength
626
+ });
627
+ }
628
+ if (indicatesMistralOverload(message, statusCode)) {
629
+ return new ModelOverloadedError(message, "mistral", options);
630
+ }
631
+ if (isMistralRateLimit(statusCode, body?.type)) {
632
+ return new RateLimitError(message, "mistral", {
633
+ ...options,
634
+ retryAfterSeconds: getRetryAfterSecondsFromError(error)
635
+ });
636
+ }
637
+ if (isTransientUnavailableStatus(statusCode)) {
638
+ return new ServiceUnavailableError(message, "mistral", options);
639
+ }
640
+ return new ProviderError(message, "mistral", options);
641
+ }
642
+ function isMistralAbortError(error) {
643
+ return error instanceof RequestAbortedError || isAbortErrorByName(error);
644
+ }
645
+ function isMistralTimeoutError(error) {
646
+ return error instanceof RequestTimeoutError || error instanceof Error && error.name === "RequestTimeoutError";
647
+ }
648
+ function isMistralRateLimit(statusCode, errorType) {
649
+ return isRateLimitStatus(statusCode) || errorType === "rate_limit_error";
650
+ }
651
+ function indicatesMistralOverload(text, statusCode) {
652
+ if (statusCode !== void 0 && !OVERLOAD_MESSAGE_ELIGIBLE_STATUS_CODES.has(statusCode)) {
653
+ return false;
654
+ }
655
+ return /\boverloaded\b/i.test(text);
656
+ }
657
+ function getContextLengthDetails(body, message) {
658
+ const errorMessage = resolveMistralMessageText(body?.message ?? message);
659
+ const errorType = body?.type;
660
+ const isContextType = errorType === "invalid_request_error" || errorType === "invalid_request_invalid_args" || errorType === void 0;
661
+ if (!isContextType) {
662
+ return void 0;
552
663
  }
553
- return new ProviderError(
554
- error instanceof Error ? error.message : String(error),
555
- "mistral",
556
- void 0,
557
- error
664
+ const lower = errorMessage.toLowerCase();
665
+ const isContextWording = lower.includes("too large for model") || /exceeds the model's maximum context length/i.test(errorMessage);
666
+ if (!isContextWording) {
667
+ return void 0;
668
+ }
669
+ const match = errorMessage.match(
670
+ /(\d+)\s*tokens.*too large for model with (\d+) maximum context length/
558
671
  );
672
+ if (!match) {
673
+ const alternate = errorMessage.match(
674
+ /exceeds the model's maximum context length of (\d+)/i
675
+ );
676
+ if (alternate?.[1]) {
677
+ return { maxTokens: parseInt(alternate[1], 10) };
678
+ }
679
+ return {};
680
+ }
681
+ const actualTokens = match[1];
682
+ const maxTokens = match[2];
683
+ if (actualTokens === void 0 || maxTokens === void 0) {
684
+ return {};
685
+ }
686
+ return {
687
+ maxTokens: parseInt(maxTokens, 10),
688
+ actualTokens: parseInt(actualTokens, 10)
689
+ };
559
690
  }
560
-
561
- // src/model-capabilities.ts
562
- var NO_REASONING_CAPABILITIES = {
563
- reasoning: {
564
- supported: false,
565
- supportedEfforts: [],
566
- restrictsSamplingParams: false
691
+ function resolveMistralMessageText(message) {
692
+ const trimmed = message.trim();
693
+ if (!trimmed.startsWith("{")) {
694
+ return message;
567
695
  }
568
- };
569
- function getMistralModelCapabilities(_modelId) {
570
- return NO_REASONING_CAPABILITIES;
696
+ try {
697
+ const parsed = JSON.parse(trimmed);
698
+ const record = asRecord(parsed);
699
+ const nestedMessage = getString(record, "message");
700
+ if (nestedMessage) {
701
+ return nestedMessage;
702
+ }
703
+ } catch {
704
+ }
705
+ return message;
706
+ }
707
+ function parseErrorBody(error) {
708
+ const record = asRecord(error);
709
+ if (!record || !("body" in record)) {
710
+ const type = getString(record, "type");
711
+ const message = getString(record, "message");
712
+ if (type || message) {
713
+ return {
714
+ type,
715
+ message: message ? resolveMistralMessageText(message) : void 0
716
+ };
717
+ }
718
+ return void 0;
719
+ }
720
+ const body = record.body;
721
+ if (typeof body !== "string") {
722
+ if (body && typeof body === "object") {
723
+ const bodyRecord = asRecord(body);
724
+ const message = getString(bodyRecord, "message");
725
+ return {
726
+ message: message ? resolveMistralMessageText(message) : void 0,
727
+ type: getString(bodyRecord, "type")
728
+ };
729
+ }
730
+ return void 0;
731
+ }
732
+ try {
733
+ const parsed = JSON.parse(body);
734
+ const parsedRecord = asRecord(parsed);
735
+ if (parsedRecord) {
736
+ const message = getString(parsedRecord, "message");
737
+ return {
738
+ message: message ? resolveMistralMessageText(message) : void 0,
739
+ type: getString(parsedRecord, "type")
740
+ };
741
+ }
742
+ } catch {
743
+ }
744
+ return void 0;
571
745
  }
572
746
 
573
747
  // src/chat-model.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@core-ai/mistral",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "Mistral provider package for @core-ai/core-ai",
5
5
  "license": "MIT",
6
6
  "author": "Omnifact (https://omnifact.ai)",
@@ -43,8 +43,8 @@
43
43
  "test:watch": "vitest"
44
44
  },
45
45
  "dependencies": {
46
- "@core-ai/core-ai": "^0.15.0",
47
- "@mistralai/mistralai": "^1.14.0"
46
+ "@core-ai/core-ai": "^0.17.0",
47
+ "@mistralai/mistralai": "^2.4.1"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "zod": "^4.0.0"