using AIStudio.Provider; namespace AIStudio.Settings; public static partial class ProviderExtensions { // // Mistral names its models after the month they were released: mistral-large-2512 is // Mistral Large 3 from December 2025. The version number lives in the marketing name only, // so matching on it misses nearly every model the API actually serves. The constants below // read as YYMM and say from which release on a family gained a capability. // private const int MISTRAL_LARGE_VISION_SINCE = 2512; // Mistral Large 3 private const int MISTRAL_LARGE_REASONING_SINCE = 2512; // Mistral Large 3 private const int MISTRAL_MEDIUM_VISION_SINCE = 2505; // Mistral Medium 3 private const int MISTRAL_MEDIUM_REASONING_SINCE = 2604; // Mistral Medium 3.5 private const int MISTRAL_SMALL_VISION_SINCE = 2503; // Mistral Small 3.1 private const int MISTRAL_SMALL_REASONING_SINCE = 2603; // Mistral Small 4 private const int MINISTRAL_VISION_SINCE = 2512; // Ministral 3 /// /// Used for families which have no reasoning at all. No release date can ever reach it. /// private const int MISTRAL_REASONING_NEVER = int.MaxValue; // // Where the "latest" aliases point to. Mistral moves them on with every release, so they // have to behave like the release they resolve to instead of carrying their own rules. // private const int MISTRAL_LARGE_LATEST = 2512; private const int MISTRAL_MEDIUM_LATEST = 2604; private const int MISTRAL_SMALL_LATEST = 2603; private const int MINISTRAL_LATEST = 2512; /// /// Mistral released its first date-named model in 2023. Anything below that is not a release /// date but a parameter count or a context size which happens to have four digits. /// private const int MISTRAL_FIRST_RELEASE_YEAR = 23; // // Mistral serves some models under their marketing version as well, and it writes the version // separator both ways: mistral-medium-3.5 and mistral-medium-3-5 are the same model. Those // names carry no release date, so we map them onto the release they stand for. The order // matters: the more specific version has to come first, otherwise "3" would swallow "3.5". // private static readonly (string VersionName, int ReleaseDate)[] MISTRAL_VERSION_NAMES = [ ("mistral-large-3", 2512), ("mistral-medium-3.5", 2604), ("mistral-medium-3-5", 2604), ("mistral-medium-3.1", 2508), ("mistral-medium-3-1", 2508), ("mistral-medium-3", 2505), ("mistral-small-4", 2603), ("mistral-small-3.2", 2506), ("mistral-small-3-2", 2506), ("mistral-small-3.1", 2503), ("mistral-small-3-1", 2503), ("mistral-small-3", 2501), ]; private static List GetModelCapabilitiesMistral(Model model) { var modelName = model.Id.ToLowerInvariant().AsSpan(); // Pixtral models are able to do process images: if (modelName.IndexOf("pixtral") is not -1) return [ Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT, Capability.TEXT_OUTPUT, Capability.FUNCTION_CALLING, Capability.CHAT_COMPLETION_API, ]; // Mistral saba: if (modelName.IndexOf("mistral-saba-") is not -1) return [ Capability.TEXT_INPUT, Capability.TEXT_OUTPUT, Capability.CHAT_COMPLETION_API, ]; // // The four families Mistral versions by release date. Ministral has to be matched before // the others, although its name does not contain "mistral" as a substring: keeping the // families together makes the block easier to read. // if (modelName.IndexOf("ministral") is not -1) return BuildMistralCapabilities(GetMistralReleaseDate(modelName, MINISTRAL_LATEST), MINISTRAL_VISION_SINCE, MISTRAL_REASONING_NEVER); if (modelName.IndexOf("mistral-large") is not -1) return BuildMistralCapabilities(GetMistralReleaseDate(modelName, MISTRAL_LARGE_LATEST), MISTRAL_LARGE_VISION_SINCE, MISTRAL_LARGE_REASONING_SINCE); if (modelName.IndexOf("mistral-medium") is not -1) return BuildMistralCapabilities(GetMistralReleaseDate(modelName, MISTRAL_MEDIUM_LATEST), MISTRAL_MEDIUM_VISION_SINCE, MISTRAL_MEDIUM_REASONING_SINCE); if (modelName.IndexOf("mistral-small") is not -1) return BuildMistralCapabilities(GetMistralReleaseDate(modelName, MISTRAL_SMALL_LATEST), MISTRAL_SMALL_VISION_SINCE, MISTRAL_SMALL_REASONING_SINCE); // Default: return GetModelCapabilitiesOpenSource(model); } /// /// Determines the release date a Mistral model belongs to. /// /// The lowercase model name to inspect. /// The release the family's "latest" alias points to. /// The release date as YYMM, or 0 when the name carries none. private static int GetMistralReleaseDate(ReadOnlySpan modelName, int latestReleaseDate) { // The "latest" alias always points to the newest release of its family: if (modelName.IndexOf("-latest") is not -1) return latestReleaseDate; foreach (var (versionName, releaseDate) in MISTRAL_VERSION_NAMES) if (modelName.IndexOf(versionName) is not -1) return releaseDate; return ReadMistralReleaseDate(modelName); } /// /// Reads the four-digit release date out of a Mistral model name. /// /// /// The block has to be exactly four digits long and has to read as a plausible year and month. /// Without that, the size of a model would be mistaken for its release: ministral-14b-2512 /// must resolve to 2512 and not to anything the "14b" part could be read as. /// /// The lowercase model name to inspect. /// The release date as YYMM, or 0 when the name carries none. private static int ReadMistralReleaseDate(ReadOnlySpan modelName) { for (var index = 0; index + 4 <= modelName.Length; index++) { // A digit next to the block means the block is longer than four digits: if (index > 0 && char.IsAsciiDigit(modelName[index - 1])) continue; if (index + 4 < modelName.Length && char.IsAsciiDigit(modelName[index + 4])) continue; var candidate = modelName.Slice(index, 4); if (!char.IsAsciiDigit(candidate[0]) || !char.IsAsciiDigit(candidate[1]) || !char.IsAsciiDigit(candidate[2]) || !char.IsAsciiDigit(candidate[3])) continue; var releaseDate = int.Parse(candidate); var year = releaseDate / 100; var month = releaseDate % 100; if (year < MISTRAL_FIRST_RELEASE_YEAR || month is < 1 or > 12) continue; return releaseDate; } return 0; } /// /// Builds the capabilities of a Mistral model from its release date. /// /// /// A model whose release date we cannot read gets neither image input nor reasoning. That is /// the safe direction: offering an ability the model does not have would fail the request, /// whereas a missing one can be added by hand through the capability overrides. /// /// The release date of the model as YYMM, or 0 when unknown. /// The release from which this family accepts images. /// The release from which this family can reason. /// The capabilities of the model. private static List BuildMistralCapabilities(int releaseDate, int visionSince, int reasoningSince) { List capabilities = [Capability.TEXT_INPUT, Capability.FUNCTION_CALLING, Capability.CHAT_COMPLETION_API, Capability.TEXT_OUTPUT]; if (releaseDate >= visionSince) capabilities.Add(Capability.MULTIPLE_IMAGE_INPUT); if (releaseDate >= reasoningSince) capabilities.Add(Capability.OPTIONAL_REASONING); return capabilities; } }